mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Loki: Add unwrap with conversion function to builder (#52639)
* Loki: Add unwrap with conversion operator to builder * Update explain section * Update test
This commit is contained in:
parent
9d6994c565
commit
53b8e528fc
@ -323,16 +323,30 @@ export function getOperationDefinitions(): QueryBuilderOperationDef[] {
|
||||
{
|
||||
id: LokiOperationId.Unwrap,
|
||||
name: 'Unwrap',
|
||||
params: [{ name: 'Identifier', type: 'string', hideName: true, minWidth: 16, placeholder: 'Label key' }],
|
||||
defaultParams: [''],
|
||||
params: [
|
||||
{ name: 'Identifier', type: 'string', hideName: true, minWidth: 16, placeholder: 'Label key' },
|
||||
{
|
||||
name: 'Conversion function',
|
||||
hideName: true,
|
||||
type: 'string',
|
||||
options: ['duration', 'duration_seconds', 'bytes'],
|
||||
optional: true,
|
||||
},
|
||||
],
|
||||
defaultParams: ['', ''],
|
||||
alternativesKey: 'format',
|
||||
category: LokiVisualQueryOperationCategory.Formats,
|
||||
orderRank: LokiOperationOrder.Unwrap,
|
||||
renderer: (op, def, innerExpr) => `${innerExpr} | unwrap ${op.params[0]}`,
|
||||
renderer: (op, def, innerExpr) =>
|
||||
`${innerExpr} | unwrap ${op.params[1] ? `${op.params[1]}(${op.params[0]})` : op.params[0]}`,
|
||||
addOperationHandler: addLokiOperation,
|
||||
explainHandler: (op) => {
|
||||
let label = String(op.params[0]).length > 0 ? op.params[0] : '<label>';
|
||||
return `Use the extracted label \`${label}\` as sample values instead of log lines for the subsequent range aggregation.`;
|
||||
return `Use the extracted label \`${label}\` as sample values instead of log lines for the subsequent range aggregation.${
|
||||
op.params[1]
|
||||
? ` Conversion function \`${op.params[1]}\` wrapping \`${label}\` will attempt to convert this label from a specific format (e.g. 3k, 500ms).`
|
||||
: ''
|
||||
}`;
|
||||
},
|
||||
},
|
||||
...binaryScalarOperations,
|
||||
|
@ -217,7 +217,7 @@ describe('buildVisualQueryFromString', () => {
|
||||
],
|
||||
operations: [
|
||||
{ id: 'logfmt', params: [] },
|
||||
{ id: 'unwrap', params: ['bytes_processed'] },
|
||||
{ id: 'unwrap', params: ['bytes_processed', ''] },
|
||||
{ id: 'sum_over_time', params: ['1m'] },
|
||||
],
|
||||
})
|
||||
@ -238,7 +238,7 @@ describe('buildVisualQueryFromString', () => {
|
||||
],
|
||||
operations: [
|
||||
{ id: 'logfmt', params: [] },
|
||||
{ id: 'unwrap', params: ['duration'] },
|
||||
{ id: 'unwrap', params: ['duration', ''] },
|
||||
{ id: '__label_filter_no_errors', params: [] },
|
||||
{ id: 'sum_over_time', params: ['1m'] },
|
||||
],
|
||||
@ -260,7 +260,7 @@ describe('buildVisualQueryFromString', () => {
|
||||
],
|
||||
operations: [
|
||||
{ id: 'logfmt', params: [] },
|
||||
{ id: 'unwrap', params: ['duration'] },
|
||||
{ id: 'unwrap', params: ['duration', ''] },
|
||||
{ id: '__label_filter', params: ['label', '=', 'value'] },
|
||||
{ id: 'sum_over_time', params: ['1m'] },
|
||||
],
|
||||
@ -268,18 +268,26 @@ describe('buildVisualQueryFromString', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('returns error for query with unwrap and conversion operation', () => {
|
||||
it('parses query with unwrap and conversion function', () => {
|
||||
const context = buildVisualQueryFromString(
|
||||
'sum_over_time({app="frontend"} | logfmt | unwrap duration(label) [5m])'
|
||||
);
|
||||
expect(context.errors).toEqual([
|
||||
{
|
||||
text: 'Unwrap with conversion operator not supported in query builder: | unwrap duration(label)',
|
||||
from: 40,
|
||||
to: 64,
|
||||
parentType: 'LogRangeExpr',
|
||||
},
|
||||
]);
|
||||
expect(context).toEqual(
|
||||
noErrors({
|
||||
labels: [
|
||||
{
|
||||
op: '=',
|
||||
value: 'frontend',
|
||||
label: 'app',
|
||||
},
|
||||
],
|
||||
operations: [
|
||||
{ id: 'logfmt', params: [] },
|
||||
{ id: 'unwrap', params: ['label', 'duration'] },
|
||||
{ id: 'sum_over_time', params: ['5m'] },
|
||||
],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('parses metrics query with function', () => {
|
||||
|
@ -323,16 +323,21 @@ function handleUnwrapExpr(
|
||||
}
|
||||
|
||||
if (unwrapChild) {
|
||||
if (unwrapChild?.nextSibling?.type.name === 'ConvOp') {
|
||||
if (unwrapChild.nextSibling?.type.name === 'ConvOp') {
|
||||
const convOp = unwrapChild.nextSibling;
|
||||
const identifier = convOp.nextSibling;
|
||||
return {
|
||||
error: 'Unwrap with conversion operator not supported in query builder',
|
||||
operation: {
|
||||
id: 'unwrap',
|
||||
params: [getString(expr, identifier), getString(expr, convOp)],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
operation: {
|
||||
id: 'unwrap',
|
||||
params: [getString(expr, unwrapChild?.nextSibling)],
|
||||
params: [getString(expr, unwrapChild?.nextSibling), ''],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React, { ComponentType } from 'react';
|
||||
|
||||
import { SelectableValue, toOption } from '@grafana/data';
|
||||
import { AutoSizeInput, Checkbox, Select } from '@grafana/ui';
|
||||
import { GrafanaTheme2, SelectableValue, toOption } from '@grafana/data';
|
||||
import { AutoSizeInput, Button, Checkbox, Select, Stack, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { QueryBuilderOperationParamDef, QueryBuilderOperationParamEditorProps } from '../shared/types';
|
||||
|
||||
@ -63,6 +64,7 @@ function SelectInputParamEditor({
|
||||
operationIndex,
|
||||
onChange,
|
||||
}: QueryBuilderOperationParamEditorProps) {
|
||||
const styles = useStyles2(getStyles);
|
||||
let selectOptions = paramDef.options as Array<SelectableValue<any>>;
|
||||
|
||||
if (!selectOptions[0]?.label) {
|
||||
@ -74,14 +76,53 @@ function SelectInputParamEditor({
|
||||
|
||||
let valueOption = selectOptions.find((x) => x.value === value) ?? toOption(value as string);
|
||||
|
||||
// If we have optional options param and don't have value, we want to render button with which we add optional options.
|
||||
// This makes it easier to understand what needs to be selected and what is optional.
|
||||
if (!value && paramDef.optional) {
|
||||
return (
|
||||
<div className={styles.optionalParam}>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
title={`Add ${paramDef.name}`}
|
||||
icon="plus"
|
||||
onClick={() => onChange(index, selectOptions[0].value)}
|
||||
>
|
||||
{paramDef.name}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Select
|
||||
id={getOperationParamId(operationIndex, index)}
|
||||
value={valueOption}
|
||||
options={selectOptions}
|
||||
placeholder={paramDef.placeholder}
|
||||
allowCustomValue={true}
|
||||
onChange={(value) => onChange(index, value.value!)}
|
||||
/>
|
||||
<Stack gap={0.5} direction="row" alignItems="center" wrap={false}>
|
||||
<Select
|
||||
id={getOperationParamId(operationIndex, index)}
|
||||
value={valueOption}
|
||||
options={selectOptions}
|
||||
placeholder={paramDef.placeholder}
|
||||
allowCustomValue={true}
|
||||
onChange={(value) => onChange(index, value.value!)}
|
||||
/>
|
||||
{paramDef.optional && (
|
||||
<Button
|
||||
data-testid={`operations.${index}.remove-param`}
|
||||
size="sm"
|
||||
fill="text"
|
||||
icon="times"
|
||||
variant="secondary"
|
||||
title={`Remove ${paramDef.name}`}
|
||||
onClick={() => onChange(index, '')}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
optionalParam: css({
|
||||
marginTop: theme.spacing(1),
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user