Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ export const counterRateOperation: OperationDefinition<
defaultMessage: 'Counter rate',
}),
input: 'fullReference',
description: i18n.translate('xpack.lens.indexPattern.counterRate.description', {
defaultMessage:
'An aggregation that calculates a rate of documents or a field in each date_histogram bucket.',
}),
selectionStyle: 'field',
requiredReferences: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ export const cumulativeSumOperation: OperationDefinition<
defaultMessage: 'Cumulative sum',
}),
input: 'fullReference',
description: i18n.translate('xpack.lens.indexPattern.cumulativeSum.description', {
defaultMessage:
'An aggregation that calculates the cumulative sum of a specified field in each date_histogram bucket. Cumulative sums always start with 0.',
}),
selectionStyle: 'field',
requiredReferences: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ export const derivativeOperation: OperationDefinition<
defaultMessage: 'Differences',
}),
input: 'fullReference',
description: i18n.translate('xpack.lens.indexPattern.derivative.description', {
defaultMessage:
'An aggregation that calculates the difference over numeric values of a specified field between each pair of date_histogram buckets. Derivative always start with an undefined value for the first bucket, and it requires a minimum of two buckets.',
}),
selectionStyle: 'full',
requiredReferences: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,22 @@ export const movingAverageOperation: OperationDefinition<
}),
input: 'fullReference',
selectionStyle: 'full',
description: i18n.translate('xpack.lens.indexPattern.movingAverage.description', {
defaultMessage:
'Given an ordered series of data, the aggregation will slide a window across the data and emit the average value of that window. The default window value is {defaultValue}',
values: {
defaultValue: WINDOW_DEFAULT_VALUE,
},
}),
requiredReferences: [
{
input: ['field', 'managedReference'],
validateMetadata: (meta) => meta.dataType === 'number' && !meta.isBucketed,
},
],
operationParams: [{ name: 'window', type: 'number', required: true }],
operationParams: [
{ name: 'window', type: 'number', required: false, defaultValue: WINDOW_DEFAULT_VALUE },
],
getPossibleOperation: (indexPattern) => {
if (hasDateField(indexPattern)) {
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ export const cardinalityOperation: OperationDefinition<CardinalityIndexPatternCo
defaultMessage: 'Unique count',
}),
input: 'field',
description: i18n.translate('xpack.lens.indexPattern.cardinality.description', {
defaultMessage:
'A single-value metrics aggregation that calculates an approximate count of distinct values.',
}),
getPossibleOperationForField: ({ aggregationRestrictions, aggregatable, type }) => {
if (
supportedTypes.has(type) &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export const countOperation: OperationDefinition<CountIndexPatternColumn, 'field
defaultMessage: 'Count',
}),
input: 'field',
description: i18n.translate('xpack.lens.indexPattern.count.description', {
defaultMessage: 'A metric aggregation that calculates the number of documents.',
}),
getErrorMessage: (layer, columnId, indexPattern) =>
getInvalidFieldMessage(layer.columns[columnId] as FieldBasedIndexPatternColumn, indexPattern),
onFieldChange: (oldColumn, field) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,24 @@ import {
EuiText,
EuiSelectable,
EuiSelectableOption,
EuiCode,
EuiSpacer,
EuiMarkdownFormat,
EuiTitle,
} from '@elastic/eui';
import { Markdown } from '../../../../../../../../../src/plugins/kibana_react/public';
import { GenericOperationDefinition, ParamEditorProps } from '../../index';
import { IndexPattern } from '../../../../types';
import { tinymathFunctions } from '../util';
import { getPossibleFunctions } from './math_completion';
import { hasFunctionFieldArgument } from '../validation';

import { FormulaIndexPatternColumn } from '../formula';
import type {
GenericOperationDefinition,
IndexPatternColumn,
OperationDefinition,
ParamEditorProps,
} from '../../index';
import type { IndexPattern } from '../../../../types';
import type { FormulaIndexPatternColumn } from '../formula';

function FormulaHelp({
indexPattern,
Expand All @@ -41,7 +51,16 @@ function FormulaHelp({
.filter((key) => key in tinymathFunctions)
.map((key) => ({
label: `${key}`,
description: <Markdown markdown={tinymathFunctions[key].help} />,
description: (
<>
<EuiTitle size="s">
<h3>{getFunctionSignatureLabel(key, operationDefinitionMap)}</h3>
</EuiTitle>
<EuiMarkdownFormat>
{tinymathFunctions[key].help.replace(/\n/g, '\n\n')}
</EuiMarkdownFormat>
</>
),
checked: selectedFunction === key ? ('on' as const) : undefined,
}))
);
Expand All @@ -65,7 +84,9 @@ function FormulaHelp({
return (
<>
<EuiPopoverTitle className="lnsFormula__docsHeader" paddingSize="s">
Formula reference
{i18n.translate('xpack.lens.formulaReference', {
defaultMessage: 'Formula reference',
})}
</EuiPopoverTitle>

<EuiFlexGroup className="lnsFormula__docsContent" gutterSize="none" responsive={false}>
Expand Down Expand Up @@ -149,36 +170,127 @@ Use the symbols +, -, /, and * to perform basic math.

export const MemoizedFormulaHelp = React.memo(FormulaHelp);

// TODO: i18n this whole thing, or move examples into the operation definitions with i18n
function getHelpText(
export function getFunctionSignatureLabel(
name: string,
operationDefinitionMap: ParamEditorProps<FormulaIndexPatternColumn>['operationDefinitionMap'],
firstParam?: { label: string | [number, number] } | null
): string {
if (tinymathFunctions[name]) {
return `${name}(${tinymathFunctions[name].positionalArguments
.map(({ name: argName, optional }) => `${argName}${optional ? '?' : ''}`)
.join(', ')})`;
}
if (operationDefinitionMap[name]) {
const def = operationDefinitionMap[name];
if ('operationParams' in def && def.operationParams) {
return `${name}(${firstParam ? firstParam.label + ', ' : ''}${def.operationParams.map(
({ name: argName, type, required }) => `${argName}${required ? '' : '?'}=${type}`
)})`;
}
return `${name}(${firstParam ? firstParam.label : ''})`;
}
return '';
}

function getFunctionArgumentsStringified(
params: Required<
OperationDefinition<IndexPatternColumn, 'field' | 'fullReference'>
>['operationParams']
) {
return params
.map(
({ name, type: argType, defaultValue = 5 }) =>
`${name}=${argType === 'string' ? `"${defaultValue}"` : defaultValue}`
)
.join(', ');
}

/**
* Get an array of strings containing all possible information about a specific
* operation type: examples and infos.
*/
export function getHelpTextContent(
type: string,
operationDefinitionMap: ParamEditorProps<FormulaIndexPatternColumn>['operationDefinitionMap']
) {
): { description: string; examples: string[] } {
const definition = operationDefinitionMap[type];
const description: string = definition.description ?? '';
// as for the time being just add examples text.
// Later will enrich with more information taken from the operation definitions.
const examples: string[] = [];

if (type === 'count') {
return (
<EuiText size="s">
<p>Example: count()</p>
</EuiText>
);
if (!hasFunctionFieldArgument(type)) {
// ideally this should have the same example automation as the operations below
examples.push(`${type}()`);
return { description, examples };
}
if (definition.input === 'field') {
const mandatoryArgs = definition.operationParams?.filter(({ required }) => required) || [];
if (mandatoryArgs.length === 0) {
examples.push(`${type}(bytes)`);
}
if (mandatoryArgs.length) {
const additionalArgs = getFunctionArgumentsStringified(mandatoryArgs);
examples.push(`${type}(bytes, ${additionalArgs})`);
}
if (definition.operationParams && mandatoryArgs.length !== definition.operationParams.length) {
const additionalArgs = getFunctionArgumentsStringified(definition.operationParams);
examples.push(`${type}(bytes, ${additionalArgs})`);
}
}
if (definition.input === 'fullReference') {
const mandatoryArgs = definition.operationParams?.filter(({ required }) => required) || [];
if (mandatoryArgs.length === 0) {
examples.push(`${type}(sum(bytes))`);
}
if (mandatoryArgs.length) {
const additionalArgs = getFunctionArgumentsStringified(mandatoryArgs);
examples.push(`${type}(sum(bytes), ${additionalArgs})`);
}
if (definition.operationParams && mandatoryArgs.length !== definition.operationParams.length) {
const additionalArgs = getFunctionArgumentsStringified(definition.operationParams);
examples.push(`${type}(sum(bytes), ${additionalArgs})`);
}
}
return { description, examples };
}

function getHelpText(
type: string,
operationDefinitionMap: ParamEditorProps<FormulaIndexPatternColumn>['operationDefinitionMap']
) {
const { description, examples } = getHelpTextContent(type, operationDefinitionMap);
const def = operationDefinitionMap[type];
const firstParam = hasFunctionFieldArgument(type)
? {
label: def.input === 'field' ? 'field' : def.input === 'fullReference' ? 'function' : '',
}
: null;
return (
<EuiText size="s">
{definition.input === 'field' ? <p>Example: {type}(bytes)</p> : null}
{definition.input === 'fullReference' && !('operationParams' in definition) ? (
<p>Example: {type}(sum(bytes))</p>
) : null}

{'operationParams' in definition && definition.operationParams ? (
<p>
<p>
Example: {type}(sum(bytes),{' '}
{definition.operationParams.map((p) => `${p.name}=5`).join(', ')})
</p>
</p>
) : null}
</EuiText>
<>
<EuiTitle size="s">
<h3>{getFunctionSignatureLabel(type, operationDefinitionMap, firstParam)}</h3>
</EuiTitle>
<EuiText size="s">
{description}
{examples.length ? (
<>
<EuiSpacer />
<p>
<b>
{i18n.translate('xpack.lens.formulaExamples', {
defaultMessage: 'Examples',
})}
</b>
</p>
{examples.map((example) => (
<p key={example}>
<EuiCode>{example}</EuiCode>
</p>
))}
</>
) : null}
</EuiText>
</>
);
}
Loading