mirror of
https://github.com/grafana/grafana.git
synced 2025-09-25 20:54:10 +08:00
Loki: Allow aliasing Loki queries in dashboard (#25706)
* Loki: Add Legend field to query editor * Loki: Basic test for legend field * Loki: Mention legend is only for metric queries * Loki: Fix absolute timerange never updating
This commit is contained in:
@ -0,0 +1,66 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import { toUtc } from '@grafana/data';
|
||||||
|
|
||||||
|
import { LokiQueryEditor } from './LokiQueryEditor';
|
||||||
|
import { LokiDatasource } from '../datasource';
|
||||||
|
import { LokiQuery } from '../types';
|
||||||
|
|
||||||
|
const createMockRequestRange = (from: string, to: string) => {
|
||||||
|
return {
|
||||||
|
request: {
|
||||||
|
range: {
|
||||||
|
from: toUtc(from, 'YYYY-MM-DD'),
|
||||||
|
to: toUtc(to, 'YYYY-MM-DD'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const setup = (propOverrides?: object) => {
|
||||||
|
const datasourceMock: unknown = {};
|
||||||
|
const datasource: LokiDatasource = datasourceMock as LokiDatasource;
|
||||||
|
const onRunQuery = jest.fn();
|
||||||
|
const onChange = jest.fn();
|
||||||
|
|
||||||
|
const query: LokiQuery = {
|
||||||
|
expr: '',
|
||||||
|
refId: 'A',
|
||||||
|
legendFormat: 'My Legend',
|
||||||
|
};
|
||||||
|
|
||||||
|
const data = createMockRequestRange('2020-01-01', '2020-01-02');
|
||||||
|
|
||||||
|
const props: any = {
|
||||||
|
datasource,
|
||||||
|
onChange,
|
||||||
|
onRunQuery,
|
||||||
|
query,
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.assign(props, propOverrides);
|
||||||
|
|
||||||
|
const wrapper = shallow(<LokiQueryEditor {...props} />);
|
||||||
|
const instance = wrapper.instance() as LokiQueryEditor;
|
||||||
|
|
||||||
|
return {
|
||||||
|
instance,
|
||||||
|
wrapper,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Render LokiQueryEditor with legend', () => {
|
||||||
|
it('should render', () => {
|
||||||
|
const { wrapper } = setup();
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update absolute timerange', () => {
|
||||||
|
const { wrapper } = setup();
|
||||||
|
wrapper.setProps({
|
||||||
|
data: createMockRequestRange('2019-01-01', '2020-01-02'),
|
||||||
|
});
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
@ -1,45 +1,105 @@
|
|||||||
// Libraries
|
// Libraries
|
||||||
import React, { memo } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { AbsoluteTimeRange, QueryEditorProps } from '@grafana/data';
|
import { AbsoluteTimeRange, QueryEditorProps, PanelData } from '@grafana/data';
|
||||||
|
import { InlineFormLabel } from '@grafana/ui';
|
||||||
import { LokiDatasource } from '../datasource';
|
import { LokiDatasource } from '../datasource';
|
||||||
import { LokiQuery } from '../types';
|
import { LokiQuery } from '../types';
|
||||||
import { LokiQueryField } from './LokiQueryField';
|
import { LokiQueryField } from './LokiQueryField';
|
||||||
|
|
||||||
type Props = QueryEditorProps<LokiDatasource, LokiQuery>;
|
type Props = QueryEditorProps<LokiDatasource, LokiQuery>;
|
||||||
|
|
||||||
export const LokiQueryEditor = memo(function LokiQueryEditor(props: Props) {
|
interface State {
|
||||||
const { query, data, datasource, onChange, onRunQuery } = props;
|
legendFormat: string;
|
||||||
|
}
|
||||||
|
|
||||||
let absolute: AbsoluteTimeRange;
|
export class LokiQueryEditor extends PureComponent<Props, State> {
|
||||||
|
// Query target to be modified and used for queries
|
||||||
|
query: LokiQuery;
|
||||||
|
|
||||||
if (data && data.request) {
|
constructor(props: Props) {
|
||||||
const { range } = data.request;
|
super(props);
|
||||||
absolute = {
|
// Use default query to prevent undefined input values
|
||||||
from: range.from.valueOf(),
|
const defaultQuery: Partial<LokiQuery> = { expr: '', legendFormat: '' };
|
||||||
to: range.to.valueOf(),
|
const query = Object.assign({}, defaultQuery, props.query);
|
||||||
};
|
this.query = query;
|
||||||
} else {
|
// Query target properties that are fully controlled inputs
|
||||||
absolute = {
|
this.state = {
|
||||||
from: Date.now() - 10000,
|
// Fully controlled text inputs
|
||||||
to: Date.now(),
|
legendFormat: query.legendFormat,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
calcAbsoluteRange = (data: PanelData): AbsoluteTimeRange => {
|
||||||
<div>
|
if (data && data.request) {
|
||||||
<LokiQueryField
|
const { range } = data.request;
|
||||||
datasource={datasource}
|
return {
|
||||||
query={query}
|
from: range.from.valueOf(),
|
||||||
onChange={onChange}
|
to: range.to.valueOf(),
|
||||||
onRunQuery={onRunQuery}
|
};
|
||||||
history={[]}
|
}
|
||||||
data={data}
|
|
||||||
absoluteRange={absolute}
|
return {
|
||||||
/>
|
from: Date.now() - 10000,
|
||||||
</div>
|
to: Date.now(),
|
||||||
);
|
};
|
||||||
});
|
};
|
||||||
|
|
||||||
|
onFieldChange = (query: LokiQuery, override?: any) => {
|
||||||
|
this.query.expr = query.expr;
|
||||||
|
};
|
||||||
|
|
||||||
|
onLegendChange = (e: React.SyntheticEvent<HTMLInputElement>) => {
|
||||||
|
const legendFormat = e.currentTarget.value;
|
||||||
|
this.query.legendFormat = legendFormat;
|
||||||
|
this.setState({ legendFormat });
|
||||||
|
};
|
||||||
|
|
||||||
|
onRunQuery = () => {
|
||||||
|
const { query } = this;
|
||||||
|
this.props.onChange(query);
|
||||||
|
this.props.onRunQuery();
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { datasource, query, data } = this.props;
|
||||||
|
const { legendFormat } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<LokiQueryField
|
||||||
|
datasource={datasource}
|
||||||
|
query={query}
|
||||||
|
onChange={this.onFieldChange}
|
||||||
|
onRunQuery={this.onRunQuery}
|
||||||
|
history={[]}
|
||||||
|
data={data}
|
||||||
|
absoluteRange={this.calcAbsoluteRange(data)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="gf-form-inline">
|
||||||
|
<div className="gf-form">
|
||||||
|
<InlineFormLabel
|
||||||
|
width={7}
|
||||||
|
tooltip="Controls the name of the time series, using name or pattern. For example
|
||||||
|
{{hostname}} will be replaced with label value for the label hostname. The legend only applies to metric queries."
|
||||||
|
>
|
||||||
|
Legend
|
||||||
|
</InlineFormLabel>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="gf-form-input"
|
||||||
|
placeholder="legend format"
|
||||||
|
value={legendFormat}
|
||||||
|
onChange={this.onLegendChange}
|
||||||
|
onBlur={this.onRunQuery}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default LokiQueryEditor;
|
export default LokiQueryEditor;
|
||||||
|
@ -0,0 +1,115 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Render LokiQueryEditor with legend should render 1`] = `
|
||||||
|
<div>
|
||||||
|
<Component
|
||||||
|
absoluteRange={
|
||||||
|
Object {
|
||||||
|
"from": 1577836800000,
|
||||||
|
"to": 1577923200000,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data={
|
||||||
|
Object {
|
||||||
|
"request": Object {
|
||||||
|
"range": Object {
|
||||||
|
"from": "2020-01-01T00:00:00.000Z",
|
||||||
|
"to": "2020-01-02T00:00:00.000Z",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
datasource={Object {}}
|
||||||
|
history={Array []}
|
||||||
|
onChange={[Function]}
|
||||||
|
onRunQuery={[Function]}
|
||||||
|
query={
|
||||||
|
Object {
|
||||||
|
"expr": "",
|
||||||
|
"legendFormat": "My Legend",
|
||||||
|
"refId": "A",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="gf-form-inline"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="gf-form"
|
||||||
|
>
|
||||||
|
<Component
|
||||||
|
tooltip="Controls the name of the time series, using name or pattern. For example
|
||||||
|
{{hostname}} will be replaced with label value for the label hostname. The legend only applies to metric queries."
|
||||||
|
width={7}
|
||||||
|
>
|
||||||
|
Legend
|
||||||
|
</Component>
|
||||||
|
<input
|
||||||
|
className="gf-form-input"
|
||||||
|
onBlur={[Function]}
|
||||||
|
onChange={[Function]}
|
||||||
|
placeholder="legend format"
|
||||||
|
type="text"
|
||||||
|
value="My Legend"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Render LokiQueryEditor with legend should update absolute timerange 1`] = `
|
||||||
|
<div>
|
||||||
|
<Component
|
||||||
|
absoluteRange={
|
||||||
|
Object {
|
||||||
|
"from": 1546300800000,
|
||||||
|
"to": 1577923200000,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data={
|
||||||
|
Object {
|
||||||
|
"request": Object {
|
||||||
|
"range": Object {
|
||||||
|
"from": "2019-01-01T00:00:00.000Z",
|
||||||
|
"to": "2020-01-02T00:00:00.000Z",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
datasource={Object {}}
|
||||||
|
history={Array []}
|
||||||
|
onChange={[Function]}
|
||||||
|
onRunQuery={[Function]}
|
||||||
|
query={
|
||||||
|
Object {
|
||||||
|
"expr": "",
|
||||||
|
"legendFormat": "My Legend",
|
||||||
|
"refId": "A",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="gf-form-inline"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="gf-form"
|
||||||
|
>
|
||||||
|
<Component
|
||||||
|
tooltip="Controls the name of the time series, using name or pattern. For example
|
||||||
|
{{hostname}} will be replaced with label value for the label hostname. The legend only applies to metric queries."
|
||||||
|
width={7}
|
||||||
|
>
|
||||||
|
Legend
|
||||||
|
</Component>
|
||||||
|
<input
|
||||||
|
className="gf-form-input"
|
||||||
|
onBlur={[Function]}
|
||||||
|
onChange={[Function]}
|
||||||
|
placeholder="legend format"
|
||||||
|
type="text"
|
||||||
|
value="My Legend"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
@ -143,8 +143,10 @@ function createUid(ts: string, labelsString: string, line: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function lokiMatrixToTimeSeries(matrixResult: LokiMatrixResult, options: TransformerOptions): TimeSeries {
|
function lokiMatrixToTimeSeries(matrixResult: LokiMatrixResult, options: TransformerOptions): TimeSeries {
|
||||||
|
const name = createMetricLabel(matrixResult.metric, options);
|
||||||
return {
|
return {
|
||||||
target: createMetricLabel(matrixResult.metric, options),
|
target: name,
|
||||||
|
title: name,
|
||||||
datapoints: lokiPointsToTimeseriesPoints(matrixResult.values, options),
|
datapoints: lokiPointsToTimeseriesPoints(matrixResult.values, options),
|
||||||
tags: matrixResult.metric,
|
tags: matrixResult.metric,
|
||||||
meta: options.meta,
|
meta: options.meta,
|
||||||
|
Reference in New Issue
Block a user