Skip to content

Commit 016423c

Browse files
Loori-Rdmitryk-dk
andauthored
add support for alerting and toggle between request types (#130)
* add support alerting * update query type usage * cleanup * cleanup * docs: update changelog for #98 and #111 * feat: add options for legend template, max lines, and step * fix indentation in code --------- Co-authored-by: dmitryk-dk <[email protected]>
1 parent 558d333 commit 016423c

14 files changed

+427
-53
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## tip
44

5+
* FEATURE: add alerting support. See [this issue](https://github.com/VictoriaMetrics/victorialogs-datasource/issues/98).
6+
* FEATURE: implement a toggle to switch between instant and range requests. See [this issue](https://github.com/VictoriaMetrics/victorialogs-datasource/issues/111).
7+
* FEATURE: add options to configure the legend template, limit for number of log lines, and step. See [this](https://github.com/VictoriaMetrics/victorialogs-datasource/issues/114) and [this](https://github.com/VictoriaMetrics/victorialogs-datasource/issues/124) issues.
8+
59
## v0.9.0
610

711
* FEATURE: Add support for the `$__range` variable in queries. It will be transformed to the `[time_from, time_to]` in the Unix format. See [this issue](https://github.com/VictoriaMetrics/victorialogs-datasource/issues/112).

pkg/plugin/datasource.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ func (d *Datasource) query(ctx context.Context, _ backend.PluginContext, q *Quer
242242
}
243243
}()
244244

245-
switch QueryType(q.QueryType) {
245+
switch q.QueryType {
246246
case QueryTypeStats:
247247
return parseStatsResponse(r, q)
248248
case QueryTypeStatsRange:

pkg/plugin/query.go

+15-9
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,14 @@ const (
4343
type Query struct {
4444
backend.DataQuery `json:"inline"`
4545

46-
Expr string `json:"expr"`
47-
LegendFormat string `json:"legendFormat"`
48-
TimeInterval string `json:"timeInterval"`
49-
Interval string `json:"interval"`
50-
IntervalMs int64 `json:"intervalMs"`
51-
MaxLines int `json:"maxLines"`
46+
Expr string `json:"expr"`
47+
LegendFormat string `json:"legendFormat"`
48+
TimeInterval string `json:"timeInterval"`
49+
Interval string `json:"interval"`
50+
IntervalMs int64 `json:"intervalMs"`
51+
MaxLines int `json:"maxLines"`
52+
Step string `json:"step"`
53+
QueryType QueryType `json:"queryType"`
5254
url *url.URL
5355
}
5456

@@ -69,7 +71,7 @@ func (q *Query) getQueryURL(rawURL string, queryParams string) (string, error) {
6971

7072
q.url = u
7173

72-
switch QueryType(q.QueryType) {
74+
switch q.QueryType {
7375
case QueryTypeStats:
7476
return q.statsQueryURL(params), nil
7577
case QueryTypeStatsRange:
@@ -196,12 +198,16 @@ func (q *Query) statsQueryRangeURL(queryParams url.Values, minInterval time.Dura
196198
}
197199

198200
q.Expr = utils.ReplaceTemplateVariable(q.Expr, q.IntervalMs, q.TimeRange)
199-
step := utils.CalculateStep(minInterval, q.TimeRange, q.MaxDataPoints)
201+
202+
step := q.Step
203+
if step == "" {
204+
step = utils.CalculateStep(minInterval, q.TimeRange, q.MaxDataPoints).String()
205+
}
200206

201207
values.Set("query", q.Expr)
202208
values.Set("start", strconv.FormatInt(q.TimeRange.From.Unix(), 10))
203209
values.Set("end", strconv.FormatInt(q.TimeRange.To.Unix(), 10))
204-
values.Set("step", step.String())
210+
values.Set("step", step)
205211

206212
q.url.RawQuery = values.Encode()
207213
return q.url.String()

pkg/plugin/query_test.go

+1-3
Original file line numberDiff line numberDiff line change
@@ -251,14 +251,12 @@ func TestQuery_getQueryURL(t *testing.T) {
251251
q := &Query{
252252
DataQuery: backend.DataQuery{
253253
RefID: tt.fields.RefID,
254-
QueryType: string(tt.fields.QueryType),
255254
TimeRange: tt.fields.TimeRange,
256255
},
257-
// RefID: tt.fields.RefID,
258256
Expr: tt.fields.Expr,
259257
MaxLines: tt.fields.MaxLines,
260258

261-
// QueryType: tt.fields.QueryType,
259+
QueryType: tt.fields.QueryType,
262260
}
263261
got, err := q.getQueryURL(tt.args.rawURL, tt.args.queryParams)
264262
if (err != nil) != tt.wantErr {
+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { css } from '@emotion/css';
2+
import React, { ComponentProps, FC } from 'react';
3+
4+
import { GrafanaTheme2 } from '@grafana/data';
5+
import { useTheme2, ReactUtils, Field, Icon, PopoverContent, Tooltip } from '@grafana/ui';
6+
7+
interface EditorFieldProps extends ComponentProps<typeof Field> {
8+
label: string;
9+
children: React.ReactElement;
10+
width?: number | string;
11+
optional?: boolean;
12+
tooltip?: PopoverContent;
13+
}
14+
15+
const EditorField: FC<EditorFieldProps> = (props) => {
16+
const { label, optional, tooltip, children, width, ...fieldProps } = props;
17+
18+
const theme = useTheme2();
19+
const styles = getStyles(theme, width);
20+
21+
// Null check for backward compatibility
22+
const childInputId = fieldProps?.htmlFor || ReactUtils?.getChildId(children);
23+
24+
const labelEl = (
25+
<>
26+
<label className={styles.label} htmlFor={childInputId}>
27+
{label}
28+
{optional && <span className={styles.optional}> - optional</span>}
29+
{tooltip && (
30+
<Tooltip placement="top" content={tooltip} theme="info">
31+
<Icon name="info-circle" size="sm" className={styles.icon} />
32+
</Tooltip>
33+
)}
34+
</label>
35+
</>
36+
);
37+
38+
return (
39+
<div className={styles.root}>
40+
<Field className={styles.field} label={labelEl} {...fieldProps}>
41+
{children}
42+
</Field>
43+
</div>
44+
);
45+
};
46+
47+
export default EditorField;
48+
49+
const getStyles = (theme: GrafanaTheme2, width?: number | string) => {
50+
return {
51+
root: css({
52+
paddingLeft: theme.spacing(1),
53+
minWidth: theme.spacing(width ?? 0),
54+
}),
55+
label: css({
56+
fontSize: 12,
57+
fontWeight: theme.typography.fontWeightMedium,
58+
}),
59+
optional: css({
60+
fontStyle: 'italic',
61+
color: theme.colors.text.secondary,
62+
}),
63+
field: css({
64+
marginBottom: 0, // GrafanaUI/Field has a bottom margin which we must remove
65+
}),
66+
icon: css({
67+
color: theme.colors.text.secondary,
68+
marginLeft: theme.spacing(1),
69+
':hover': {
70+
color: theme.colors.text.primary,
71+
},
72+
}),
73+
};
74+
};
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { css } from '@emotion/css';
2+
import React from 'react';
3+
4+
import { GrafanaTheme2 } from '@grafana/data';
5+
import { useStyles2 } from '@grafana/ui';
6+
7+
interface EditorRowProps {
8+
children: React.ReactNode;
9+
}
10+
11+
export const EditorRow: React.FC<EditorRowProps> = ({ children }) => {
12+
const styles = useStyles2(getStyles);
13+
14+
return (
15+
<div className={styles.root}>
16+
{children}
17+
</div>
18+
);
19+
};
20+
21+
const getStyles = (theme: GrafanaTheme2) => {
22+
return {
23+
root: css({
24+
marginTop: theme.spacing(0.5),
25+
padding: theme.spacing(1),
26+
backgroundColor: theme.colors.background.secondary,
27+
borderRadius: theme.shape.radius.default,
28+
})
29+
};
30+
};

src/components/QueryEditor/QueryEditor.tsx

+10-2
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,17 @@ import { QueryBuilderContainer } from "./QueryBuilder/QueryBuilderContainer";
1212
import { QueryEditorModeToggle } from "./QueryBuilder/QueryEditorModeToggle";
1313
import { buildVisualQueryFromString } from "./QueryBuilder/utils/parseFromString";
1414
import QueryCodeEditor from "./QueryCodeEditor";
15+
import { QueryEditorOptions } from "./QueryEditorOptions";
1516
import { changeEditorMode, getQueryWithDefaults } from "./state";
1617

1718
const QueryEditor = React.memo<VictoriaLogsQueryEditorProps>((props) => {
1819
const styles = useStyles2(getStyles);
1920

20-
const { onChange, onRunQuery, data, app, queries, range: timeRange } = props;
21+
const { onChange, onRunQuery, data, app, queries, datasource, range: timeRange } = props;
2122
const [dataIsStale, setDataIsStale] = useState(false);
2223
const [parseModalOpen, setParseModalOpen] = useState(false);
2324

24-
const query = getQueryWithDefaults(props.query);
25+
const query = getQueryWithDefaults(props.query, app, data?.request?.panelPluginId);
2526
const editorMode = query.editorMode!;
2627

2728
const onEditorModeChange = useCallback((newEditorMode: QueryEditorMode) => {
@@ -88,6 +89,13 @@ const QueryEditor = React.memo<VictoriaLogsQueryEditorProps>((props) => {
8889
) : (
8990
<QueryCodeEditor {...props} query={query} onChange={onChangeInternal} showExplain={true}/>
9091
)}
92+
<QueryEditorOptions
93+
query={query}
94+
onChange={onChange}
95+
onRunQuery={onRunQuery}
96+
app={app}
97+
maxLines={datasource.maxLines}
98+
/>
9199
</div>
92100
</div>
93101
</>

src/components/QueryEditor/QueryEditorForAlerting.tsx

+16-1
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,26 @@ import React from 'react';
22

33
import { VictoriaLogsQueryEditorProps } from "../../types";
44

5+
import QueryField from "./QueryField";
6+
57
const QueryEditorForAlerting = (props: VictoriaLogsQueryEditorProps) => {
8+
const { query, data, datasource, onChange, onRunQuery, history } = props;
69

710
return (
8-
<div>QueryEditorForAlerting</div>
11+
<QueryField
12+
datasource={datasource}
13+
query={query}
14+
onChange={onChange}
15+
onRunQuery={onRunQuery}
16+
history={history}
17+
data={data}
18+
data-testid={testIds.editor}
19+
/>
920
);
1021
}
1122

1223
export default QueryEditorForAlerting
24+
25+
export const testIds = {
26+
editor: 'victorialogs-editor-cloud-alerting',
27+
};

0 commit comments

Comments
 (0)