Skip to content

Commit 4e02aaa

Browse files
committed
feat: add full text sql query editor
1 parent c25f683 commit 4e02aaa

File tree

7 files changed

+183
-73
lines changed

7 files changed

+183
-73
lines changed

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ Grafana Databricks integration allowing direct connection to Databricks to query
77
The plugin is still work in progress and currently only offers limited functionality.
88

99
#### TODO:
10+
- [x] Full-text query editor
1011
- [ ] Add GROUP BY to query form
1112
- [ ] Allow multiple metrics query
12-
- [ ] Full-text query editor
1313
- [ ] Autocomplete
1414
- [ ] ...
1515

@@ -28,7 +28,11 @@ allow_loading_unsigned_plugins = mullerpeter-databricks-datasource
2828

2929
![img.png](img/querry_editor.png)
3030

31-
At the moment only simple queries for one value over time are implemented. The Time-range and TimeBucket parameters from Grafana are automatically inserted into the query.
31+
At the moment only simple queries for one value over time are implemented. The Time-range and TimeBucket parameters from Grafana are automatically inserted into the query.
32+
33+
![img.png](img/full_text_sql_editor.png)
34+
35+
When using the raw SQL editor, template variables are replaced by the Time-Range and Bucket (see default query in editor for example).
3236

3337
## Plugin Configuration
3438

img/full_text_sql_editor.png

304 KB
Loading

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "climeworks-databricks",
3-
"version": "0.0.3",
3+
"version": "0.0.4",
44
"description": "Databricks SQL Connector",
55
"scripts": {
66
"build": "grafana-toolkit plugin:build",
@@ -14,8 +14,8 @@
1414
"license": "Apache-2.0",
1515
"devDependencies": {
1616
"@grafana/data": "latest",
17-
"@grafana/toolkit": "latest",
1817
"@grafana/runtime": "latest",
18+
"@grafana/toolkit": "latest",
1919
"@grafana/ui": "latest",
2020
"@types/lodash": "latest"
2121
},
@@ -24,5 +24,8 @@
2424
},
2525
"engines": {
2626
"node": ">=14"
27+
},
28+
"dependencies": {
29+
"@monaco-editor/react": "^4.4.6"
2730
}
2831
}

pkg/plugin/plugin.go

Lines changed: 72 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ import (
66
"encoding/json"
77
"fmt"
88
_ "github.com/databricks/databricks-sql-go"
9-
"time"
10-
119
"github.com/grafana/grafana-plugin-sdk-go/backend"
1210
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
1311
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
1412
"github.com/grafana/grafana-plugin-sdk-go/data"
1513
"github.com/grafana/grafana-plugin-sdk-go/live"
14+
"regexp"
15+
"time"
1616
)
1717

1818
// Make sure SampleDatasource implements required interfaces. This is important to do
@@ -89,6 +89,8 @@ type queryModel struct {
8989
ValueColumnName string `json:"valueColumnName"`
9090
WhereQuery string `json:"whereQuery"`
9191
TableName string `json:"tableName"`
92+
RawSqlQuery string `json:"rawSqlQuery"`
93+
RawSqlSelected bool `json:"rawSqlSelected"`
9294
}
9395

9496
func (d *SampleDatasource) query(_ context.Context, pCtx backend.PluginContext, query backend.DataQuery) backend.DataResponse {
@@ -109,20 +111,76 @@ func (d *SampleDatasource) query(_ context.Context, pCtx backend.PluginContext,
109111
if seconds_interval <= 0 {
110112
seconds_interval = 1
111113
}
114+
queryString := ""
115+
if qm.RawSqlSelected {
116+
queryString = qm.RawSqlQuery
117+
log.DefaultLogger.Info("Raw SQL Query selected", "query", queryString)
118+
119+
var rgx = regexp.MustCompile(`\$__timeWindow\(([a-zA-Z0-9_-]+)\)`)
120+
if rgx.MatchString(queryString) {
121+
log.DefaultLogger.Info("__timeWindow placeholder found")
122+
rs := rgx.FindStringSubmatch(queryString)
123+
timeColumnName := rs[1]
124+
queryString = rgx.ReplaceAllString(queryString, fmt.Sprintf("window(%s, '%d SECONDS')", timeColumnName, seconds_interval))
125+
126+
rgx = regexp.MustCompile(`\$__time\(([a-zA-Z0-9_-]+)\)`)
127+
if rgx.MatchString(queryString) {
128+
log.DefaultLogger.Info("__time placeholder found")
129+
queryString = rgx.ReplaceAllString(queryString, "window.start")
130+
}
131+
132+
rgx = regexp.MustCompile(`\$__value\(([a-zA-Z0-9_-]+)\)`)
133+
if rgx.MatchString(queryString) {
134+
log.DefaultLogger.Info("__value placeholder found")
135+
rs = rgx.FindStringSubmatch(queryString)
136+
valueColumnName := rs[1]
137+
queryString = rgx.ReplaceAllString(queryString, fmt.Sprintf("avg(%s) AS value", valueColumnName))
138+
}
139+
} else {
140+
rgx = regexp.MustCompile(`\$__time\(([a-zA-Z0-9_-]+)\)`)
141+
if rgx.MatchString(queryString) {
142+
log.DefaultLogger.Info("__time placeholder found")
143+
rs := rgx.FindStringSubmatch(queryString)
144+
timeColumnName := rs[1]
145+
queryString = rgx.ReplaceAllString(queryString, fmt.Sprintf("%s AS time", timeColumnName))
146+
}
147+
148+
rgx = regexp.MustCompile(`\$__value\(([a-zA-Z0-9_-]+)\)`)
149+
if rgx.MatchString(queryString) {
150+
log.DefaultLogger.Info("__value placeholder found")
151+
rs := rgx.FindStringSubmatch(queryString)
152+
valueColumnName := rs[1]
153+
queryString = rgx.ReplaceAllString(queryString, fmt.Sprintf("%s AS value", valueColumnName))
154+
}
155+
}
112156

113-
whereQuery := ""
114-
if qm.WhereQuery != "" {
115-
whereQuery = fmt.Sprintf(" %s AND", qm.WhereQuery)
157+
rgx = regexp.MustCompile(`\$__timeFilter\(([a-zA-Z0-9_-]+)\)`)
158+
if rgx.MatchString(queryString) {
159+
rs := rgx.FindStringSubmatch(queryString)
160+
timeColumnName := rs[1]
161+
timeRangeFilter := fmt.Sprintf("%s BETWEEN '%s' AND '%s'",
162+
timeColumnName,
163+
query.TimeRange.From.UTC().Format("2006-01-02 15:04:05"),
164+
query.TimeRange.To.UTC().Format("2006-01-02 15:04:05"),
165+
)
166+
queryString = rgx.ReplaceAllString(queryString, timeRangeFilter)
167+
}
168+
169+
} else {
170+
whereQuery := ""
171+
if qm.WhereQuery != "" {
172+
whereQuery = fmt.Sprintf(" %s AND", qm.WhereQuery)
173+
}
174+
queryString = fmt.Sprintf("SELECT window.start, avg(%s) AS value FROM %s WHERE%s %s BETWEEN '%s' AND '%s' GROUP BY window(%s, '%d SECONDS')",
175+
qm.ValueColumnName,
176+
qm.TableName,
177+
whereQuery,
178+
qm.TimeColumnName,
179+
query.TimeRange.From.UTC().Format("2006-01-02 15:04:05"),
180+
query.TimeRange.To.UTC().Format("2006-01-02 15:04:05"),
181+
qm.TimeColumnName,
182+
seconds_interval)
116183
}
117-
queryString := fmt.Sprintf("SELECT window.start, avg(%s) AS value FROM %s WHERE%s %s BETWEEN '%s' AND '%s' GROUP BY window(%s, '%d SECONDS')",
118-
qm.ValueColumnName,
119-
qm.TableName,
120-
whereQuery,
121-
qm.TimeColumnName,
122-
query.TimeRange.From.UTC().Format("2006-01-02 15:04:05"),
123-
query.TimeRange.To.UTC().Format("2006-01-02 15:04:05"),
124-
qm.TimeColumnName,
125-
seconds_interval)
126184
log.DefaultLogger.Info("Query", "query", queryString)
127185

128186
rows, err := databricksDB.Query(queryString)

src/QueryEditor.tsx

Lines changed: 87 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,125 @@
11
import { defaults } from 'lodash';
22

3-
import React, { PureComponent, FormEvent } from 'react';
4-
import { AutoSizeInput, InlineFieldRow, InlineField } from '@grafana/ui';
3+
import React, {FormEvent, useState} from 'react';
4+
import { AutoSizeInput, InlineFieldRow, InlineField, useTheme, InlineSwitch } from '@grafana/ui';
55
import { QueryEditorProps } from '@grafana/data';
66
import { DataSource } from './datasource';
7+
import Editor from "@monaco-editor/react";
78
import { defaultQuery, MyDataSourceOptions, MyQuery } from './types';
9+
import * as monaco from "monaco-editor";
810

911
type Props = QueryEditorProps<DataSource, MyQuery, MyDataSourceOptions>;
1012

11-
export class QueryEditor extends PureComponent<Props> {
13+
export function QueryEditor(props: Props) {
1214

13-
ontableNameChange = (event: FormEvent<HTMLInputElement>) => {
15+
const [, setQueryText] = useState("")
16+
const [sqlEditorSelected, setSqlEditorSelected] = useState(false)
17+
const onQuerryChange = (value: string | undefined, ev: monaco.editor.IModelContentChangedEvent) => {
18+
setQueryText(value || "")
19+
const { onChange, query } = props;
20+
onChange({ ...query, rawSqlQuery: value });
21+
};
22+
23+
const onSQLSwitchChange = (event: any) => {
24+
const { onChange, query } = props;
25+
onChange({ ...query, rawSqlSelected: !sqlEditorSelected });
26+
setSqlEditorSelected(!sqlEditorSelected);
27+
};
28+
const ontableNameChange = (event: FormEvent<HTMLInputElement>) => {
1429
console.log(event);
15-
const { onChange, query } = this.props;
30+
const { onChange, query } = props;
1631
onChange({ ...query, tableName: event.currentTarget.value });
1732
};
1833

1934

20-
ontimeColumnNameTextChange = (event: FormEvent<HTMLInputElement>) => {
35+
const ontimeColumnNameTextChange = (event: FormEvent<HTMLInputElement>) => {
2136
console.log(event);
22-
const { onChange, query } = this.props;
37+
const { onChange, query } = props;
2338
onChange({ ...query, timeColumnName: event.currentTarget.value });
2439
};
2540

2641

27-
onvalueColumnNameChange = (event: FormEvent<HTMLInputElement>) => {
42+
const onvalueColumnNameChange = (event: FormEvent<HTMLInputElement>) => {
2843
console.log(event);
29-
const { onChange, query } = this.props;
44+
const { onChange, query } = props;
3045
onChange({ ...query, valueColumnName: event.currentTarget.value });
3146
};
3247

3348

34-
onwhereQueryChange = (event: FormEvent<HTMLInputElement>) => {
49+
const onwhereQueryChange = (event: FormEvent<HTMLInputElement>) => {
3550
console.log(event);
36-
const { onChange, query } = this.props;
51+
const { onChange, query } = props;
3752
onChange({ ...query, whereQuery: event.currentTarget.value });
3853
};
3954

40-
render() {
41-
const query = defaults(this.props.query, defaultQuery);
55+
const query = defaults(props.query, defaultQuery);
4256
const { timeColumnName, valueColumnName, whereQuery, tableName } = query;
43-
57+
const theme = useTheme()
4458
return (
4559
<div className="gf-form" style={{ flexDirection: "column", rowGap: "8px"}}>
60+
{sqlEditorSelected ? (
61+
<Editor
62+
height="200px"
63+
theme={theme.isDark ? "vs-dark" : "vs-light"}
64+
defaultLanguage="sql"
65+
defaultValue="SELECT $__time(time_column), $__value(value_column) FROM catalog.default.table_name WHERE $__timeFilter(time_column) GROUP BY $__timeWindow(time_column)"
66+
onChange={onQuerryChange}
67+
/>
68+
) : (
69+
<>
70+
<InlineFieldRow>
71+
<InlineField label="Time Column" labelWidth={16} tooltip="The column name of the time column">
72+
<AutoSizeInput
73+
value={timeColumnName || ''}
74+
onCommitChange={ontimeColumnNameTextChange}
75+
minWidth={32}
76+
defaultValue="timestamp"
77+
required
78+
/>
79+
</InlineField>
80+
</InlineFieldRow>
81+
<InlineFieldRow>
82+
<InlineField label="Value Column" labelWidth={16} tooltip="The column name of the value to query">
83+
<AutoSizeInput
84+
value={valueColumnName || ''}
85+
onCommitChange={onvalueColumnNameChange}
86+
minWidth={32}
87+
defaultValue="value"
88+
required
89+
/>
90+
</InlineField>
91+
</InlineFieldRow>
92+
<InlineFieldRow>
93+
<InlineField label="Table Name" labelWidth={16} tooltip="(Catalog), Schema and Table Name in the format (<catalog>).<schema>.<table_name>">
94+
<AutoSizeInput
95+
value={tableName || ''}
96+
onCommitChange={ontableNameChange}
97+
minWidth={32}
98+
placeholder="catalog.default.table_name"
99+
required
100+
/>
101+
</InlineField>
102+
</InlineFieldRow>
103+
<InlineFieldRow>
104+
<InlineField label="Where SQL" labelWidth={16} tooltip="Additional WHERE conditions to filter the queried data (Databricks SQL)">
105+
<AutoSizeInput
106+
value={whereQuery || ''}
107+
onCommitChange={onwhereQueryChange}
108+
minWidth={32}
109+
placeholder="id = 1 AND name = 'Peter'"
110+
/>
111+
</InlineField>
112+
</InlineFieldRow>
113+
</>
114+
)}
46115
<InlineFieldRow>
47-
<InlineField label="Time Column" labelWidth={16} tooltip="The column name of the time column">
48-
<AutoSizeInput
49-
value={timeColumnName || ''}
50-
onCommitChange={this.ontimeColumnNameTextChange}
51-
minWidth={32}
52-
defaultValue="timestamp"
53-
required
54-
/>
55-
</InlineField>
56-
</InlineFieldRow>
57-
<InlineFieldRow>
58-
<InlineField label="Value Column" labelWidth={16} tooltip="The column name of the value to query">
59-
<AutoSizeInput
60-
value={valueColumnName || ''}
61-
onCommitChange={this.onvalueColumnNameChange}
62-
minWidth={32}
63-
defaultValue="value"
64-
required
65-
/>
66-
</InlineField>
67-
</InlineFieldRow>
68-
<InlineFieldRow>
69-
<InlineField label="Table Name" labelWidth={16} tooltip="Schema and Table Name in the format <schema>.<table_name>">
70-
<AutoSizeInput
71-
value={tableName || ''}
72-
onCommitChange={this.ontableNameChange}
73-
minWidth={32}
74-
placeholder="default.table_name"
75-
required
76-
/>
77-
</InlineField>
78-
</InlineFieldRow>
79-
<InlineFieldRow>
80-
<InlineField label="Where SQL" labelWidth={16} tooltip="Additional WHERE conditions to filter the queried data (Databricks SQL)">
81-
<AutoSizeInput
82-
value={whereQuery || ''}
83-
onCommitChange={this.onwhereQueryChange}
84-
minWidth={32}
85-
placeholder="id = 1 AND name = 'Peter'"
116+
<InlineField label="SQL Editor" labelWidth={16}>
117+
<InlineSwitch
118+
value={sqlEditorSelected}
119+
onChange={onSQLSwitchChange}
86120
/>
87121
</InlineField>
88122
</InlineFieldRow>
89123
</div>
90124
);
91-
}
92125
}

src/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@ export interface MyQuery extends DataQuery {
66
whereQuery?: string;
77
tableName: string;
88
withStreaming: boolean;
9+
rawSqlQuery?: string;
10+
rawSqlSelected: boolean;
911
}
1012

1113
export const defaultQuery: Partial<MyQuery> = {
1214
timeColumnName: "timestamp",
1315
valueColumnName: "value",
1416
withStreaming: false,
17+
rawSqlSelected: false,
18+
rawSqlQuery: "SELECT $__time(time_column), $__value(value_column) FROM catalog.default.table_name WHERE $__timeFilter(time_column) GROUP BY $__timeWindow(time_column)"
1519
};
1620

1721
/**

yarn.lock

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1873,7 +1873,7 @@
18731873
resolved "https://registry.yarnpkg.com/@mapbox/unitbezier/-/unitbezier-0.0.0.tgz#15651bd553a67b8581fb398810c98ad86a34524e"
18741874
integrity sha512-HPnRdYO0WjFjRTSwO3frz1wKaU649OBFPX3Zo/2WZvuRi6zMiRGui8SnPQiQABgqCf8YikDe5t3HViTVw1WUzA==
18751875

1876-
"@monaco-editor/loader@^1.2.0":
1876+
"@monaco-editor/loader@^1.2.0", "@monaco-editor/loader@^1.3.2":
18771877
version "1.3.2"
18781878
resolved "https://registry.yarnpkg.com/@monaco-editor/loader/-/loader-1.3.2.tgz#04effbb87052d19cd7d3c9d81c0635490f9bb6d8"
18791879
integrity sha512-BTDbpHl3e47r3AAtpfVFTlAi7WXv4UQ/xZmz8atKl4q7epQV5e7+JbigFDViWF71VBi4IIBdcWP57Hj+OWuc9g==
@@ -1888,6 +1888,14 @@
18881888
"@monaco-editor/loader" "^1.2.0"
18891889
prop-types "^15.7.2"
18901890

1891+
"@monaco-editor/react@^4.4.6":
1892+
version "4.4.6"
1893+
resolved "https://registry.yarnpkg.com/@monaco-editor/react/-/react-4.4.6.tgz#8ae500b0edf85276d860ed702e7056c316548218"
1894+
integrity sha512-Gr3uz3LYf33wlFE3eRnta4RxP5FSNxiIV9ENn2D2/rN8KgGAD8ecvcITRtsbbyuOuNkwbuHYxfeaz2Vr+CtyFA==
1895+
dependencies:
1896+
"@monaco-editor/loader" "^1.3.2"
1897+
prop-types "^15.7.2"
1898+
18911899
"@nodelib/[email protected]":
18921900
version "2.1.5"
18931901
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"

0 commit comments

Comments
 (0)