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
pull/25996/head^2
Sebastian Widmer 5 years ago committed by GitHub
parent 73e82af4df
commit 5789f80e14
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 66
      public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx
  2. 114
      public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx
  3. 115
      public/app/plugins/datasource/loki/components/__snapshots__/LokiQueryEditor.test.tsx.snap
  4. 4
      public/app/plugins/datasource/loki/result_transformer.ts

@ -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;
// Query target properties that are fully controlled inputs
this.state = {
// Fully controlled text inputs
legendFormat: query.legendFormat,
}; };
} else { }
absolute = {
calcAbsoluteRange = (data: PanelData): AbsoluteTimeRange => {
if (data && data.request) {
const { range } = data.request;
return {
from: range.from.valueOf(),
to: range.to.valueOf(),
};
}
return {
from: Date.now() - 10000, from: Date.now() - 10000,
to: Date.now(), to: Date.now(),
}; };
} };
onFieldChange = (query: LokiQuery, override?: any) => {
this.query.expr = query.expr;
};
return ( onLegendChange = (e: React.SyntheticEvent<HTMLInputElement>) => {
<div> const legendFormat = e.currentTarget.value;
<LokiQueryField this.query.legendFormat = legendFormat;
datasource={datasource} this.setState({ legendFormat });
query={query} };
onChange={onChange}
onRunQuery={onRunQuery} onRunQuery = () => {
history={[]} const { query } = this;
data={data} this.props.onChange(query);
absoluteRange={absolute} this.props.onRunQuery();
/> };
</div>
); 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,

Loading…
Cancel
Save