Skip to content

Commit 15c756d

Browse files
authored
Add query variable support (#17)
* Add query variable support * add git hash info * Revert "add git hash info" This reverts commit 630f29c. * update version * preview values in text * test update plugin.json script
1 parent e1a159e commit 15c756d

File tree

9 files changed

+216
-24
lines changed

9 files changed

+216
-24
lines changed

.github/workflows/ci.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,17 @@ jobs:
3535
- 27018:27017
3636
steps:
3737
- uses: actions/checkout@v4
38+
- name: Set up Python ${{ matrix.python-version }}
39+
uses: actions/setup-python@v5
40+
with:
41+
python-version: '3.11'
42+
- name: Update plugin.json
43+
run: python3 scripts/update_plugin_metadata.py
44+
env:
45+
GITHUB_REPOSITORY: ${{ github.repository }}
46+
GITHUB_SHA: ${{ github.sha }}
47+
GITHUB_RUN_ID: ${{ github.run_id }}
48+
GITHUB_REF_NAME: ${{ github.ref_name }}
3849
- name: Setup Node.js environment
3950
uses: actions/setup-node@v4
4051
with:

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "mongodb-datasource",
3-
"version": "0.1.0",
3+
"version": "0.1.1",
44
"scripts": {
55
"build": "webpack -c ./.config/webpack/webpack.config.ts --env production",
66
"dev": "webpack -w -c ./.config/webpack/webpack.config.ts --env development",
@@ -72,4 +72,4 @@
7272
"tslib": "2.5.3"
7373
},
7474
"packageManager": "[email protected]"
75-
}
75+
}

scripts/update_plugin_metadata.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import json
2+
import os
3+
import subprocess
4+
import pprint
5+
from datetime import datetime
6+
7+
timestamp = int(
8+
subprocess.check_output(["git", "show", "-s", "--format=%ct"]).decode().strip()
9+
)
10+
11+
update_time = datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d")
12+
13+
14+
def get_required_env_var(key_name: str, default=None):
15+
val = os.getenv(key_name, default)
16+
if val is None:
17+
raise ValueError(f"Environment variable {key_name} is not set")
18+
return val
19+
20+
21+
# ${{ github.repository }}
22+
repo = get_required_env_var("GITHUB_REPOSITORY")
23+
# ${{ github.sha }}
24+
sha = get_required_env_var("GITHUB_SHA")
25+
# ${{ github.run_id }}
26+
run_id = get_required_env_var("GITHUB_RUN_ID")
27+
# ${{ github.ref_name }}
28+
branch = get_required_env_var("GITHUB_REF_NAME")
29+
30+
with open(os.path.join("src", "plugin.json")) as f:
31+
metadata = json.load(f)
32+
33+
with open("package.json") as f:
34+
version = json.load(f)["version"]
35+
36+
37+
links = [
38+
{"name": "Source", "url": f"https://github.com/{repo}"},
39+
{
40+
"name": "Commit",
41+
"url": f"https://github.com/{repo}/commit/{sha}",
42+
},
43+
{
44+
"name": "Build",
45+
"url": f"https://github.com/{repo}/actions/runs/{run_id}",
46+
},
47+
]
48+
49+
metadata["info"]["links"] = links
50+
if branch == "master":
51+
metadata["info"]["version"] = version
52+
else:
53+
metadata["info"]["version"] = version + "-" + sha[:7]
54+
55+
metadata["info"]["updated"] = update_time
56+
57+
pprint.pprint(metadata)
58+
with open(os.path.join("src", "plugin.json"), "w") as f:
59+
json.dump(metadata, f)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React, { useState } from "react";
2+
import { DEFAULT_QUERY, VariableQuery } from "../types";
3+
import { CodeEditor, Field, InlineField, Input } from "@grafana/ui";
4+
5+
interface VariableQueryProps {
6+
query: VariableQuery;
7+
onChange: (query: VariableQuery, definition: string) => void;
8+
}
9+
10+
export const VariableQueryEditor = ({ onChange, query }: VariableQueryProps) => {
11+
const [state, setState] = useState(query);
12+
13+
const saveQuery = () => {
14+
onChange(state, `${state.collection} (${state.queryText})`);
15+
};
16+
17+
const handleCollectionChange = (event: React.FormEvent<HTMLInputElement>) =>
18+
setState({
19+
...state,
20+
collection: event.currentTarget.value,
21+
});
22+
23+
const handleQueryTextChange = (text: string) =>
24+
setState({
25+
...state,
26+
queryText: text,
27+
});
28+
29+
return (
30+
<>
31+
<InlineField label="Collection" tooltip="Enter the MongoDB collection"
32+
error="Please enter the collection" invalid={!query.collection}>
33+
<Input
34+
name="collection"
35+
onBlur={saveQuery}
36+
onChange={handleCollectionChange}
37+
value={state.collection}>
38+
</Input>
39+
</InlineField>
40+
<Field label="Query Text" description="MongoDB aggregate (JSON)">
41+
<CodeEditor width="100%" height={300} language="json" onBlur={saveQuery}
42+
value={query.queryText || DEFAULT_QUERY.queryText!} showMiniMap={false} showLineNumbers={true}
43+
onChange={handleQueryTextChange}
44+
/>
45+
</Field>
46+
</>
47+
);
48+
};

src/datasource.ts

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { DataSourceInstanceSettings, CoreApp, ScopedVars, DataQueryRequest, DataQueryResponse } from "@grafana/data";
1+
import { DataSourceInstanceSettings, CoreApp, ScopedVars, DataQueryRequest, DataQueryResponse, LegacyMetricFindQueryOptions, MetricFindValue, dateTime } from "@grafana/data";
22
import { DataSourceWithBackend, getTemplateSrv } from "@grafana/runtime";
3-
import {parseJsQuery, datetimeToJson, getBucketCount, parseJsQueryLegacy} from "./utils";
4-
import { MongoQuery, MongoDataSourceOptions, DEFAULT_QUERY, QueryLanguage } from "./types";
5-
import { Observable } from "rxjs";
3+
import { parseJsQuery, datetimeToJson, getBucketCount, parseJsQueryLegacy, randomId, getMetricValues } from "./utils";
4+
import { MongoQuery, MongoDataSourceOptions, DEFAULT_QUERY, QueryLanguage, VariableQuery } from "./types";
5+
import { Observable, firstValueFrom } from "rxjs";
66

77

88
export class DataSource extends DataSourceWithBackend<MongoQuery, MongoDataSourceOptions> {
@@ -21,6 +21,40 @@ export class DataSource extends DataSourceWithBackend<MongoQuery, MongoDataSourc
2121
};
2222
}
2323

24+
async metricFindQuery(query: VariableQuery, options?: LegacyMetricFindQueryOptions): Promise<MetricFindValue[]> {
25+
const request: DataQueryRequest<MongoQuery> = {
26+
requestId: "variable-query-" + randomId(3),
27+
targets: [{
28+
refId: "A",
29+
queryLanguage: QueryLanguage.JSON,
30+
collection: query.collection,
31+
queryText: getTemplateSrv().replace(query.queryText),
32+
queryType: "table"
33+
}],
34+
scopedVars: options?.scopedVars || {},
35+
interval: "5s",
36+
timezone: "browser",
37+
intervalMs: 5000,
38+
range: options?.range || {
39+
from: dateTime(),
40+
to: dateTime(),
41+
raw: {
42+
from: "now",
43+
to: "now"
44+
}
45+
},
46+
app: "variable-query",
47+
startTime: (options?.range?.from || dateTime()).toDate().getUTCMilliseconds()
48+
};
49+
50+
const resp = await firstValueFrom(this.query(request));
51+
if (resp.errors?.length && resp.errors.length > 0) {
52+
throw new Error(resp.errors[0].message || "Unknown error");
53+
}
54+
55+
return getMetricValues(resp);
56+
}
57+
2458
filterQuery(query: MongoQuery): boolean {
2559
return !!query.queryText && !!query.collection;
2660
}

src/module.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import { DataSource } from "./datasource";
33
import { ConfigEditor } from "./components/ConfigEditor";
44
import { QueryEditor } from "./components/QueryEditor";
55
import { QueryHelper } from "./components/QueryHelper";
6+
import { VariableQueryEditor } from "./components/VariableQueryEditor";
67
import { MongoQuery, MongoDataSourceOptions } from "./types";
78

89
export const plugin = new DataSourcePlugin<DataSource, MongoQuery, MongoDataSourceOptions>(DataSource)
910
.setConfigEditor(ConfigEditor)
1011
.setQueryEditor(QueryEditor)
11-
.setQueryEditorHelp(QueryHelper);
12+
.setQueryEditorHelp(QueryHelper)
13+
.setVariableQueryEditor(VariableQueryEditor);

src/plugin.json

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,7 @@
1919
"small": "img/logo.svg",
2020
"large": "img/logo.svg"
2121
},
22-
"links": [
23-
{
24-
"name": "Source Code",
25-
"url": "https://github.com/haohanyang/mongodb-datasource"
26-
}
27-
],
22+
"links": [],
2823
"screenshots": [],
2924
"version": "%VERSION%",
3025
"updated": "%TODAY%"

src/types.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,22 @@ export const QueryLanguage = {
2121

2222

2323
export const DEFAULT_QUERY: Partial<MongoQuery> = {
24-
queryText: "[]",
24+
queryText: `[
25+
{
26+
"$limit": 10
27+
}
28+
]`,
2529
queryType: QueryType.TIMESERIES,
2630
queryLanguage: QueryLanguage.JSON
2731
};
2832

29-
export interface DataPoint {
30-
Time: number;
31-
Value: number;
32-
}
3333

3434
export interface JsQueryResult {
3535
jsonQuery?: string;
3636
collection?: string;
3737
error: string | null;
3838
}
3939

40-
export interface DataSourceResponse {
41-
datapoints: DataPoint[];
42-
}
4340

4441
export const MongoDBAuthMethod = {
4542
NONE: "auth-none",
@@ -51,6 +48,10 @@ export const ConnectionStringScheme = {
5148
DNS_SEED_LIST: "dns_seed_list"
5249
};
5350

51+
export interface VariableQuery {
52+
collection?: string;
53+
queryText?: string;
54+
}
5455

5556
/**
5657
* These are options configured for each DataSource instance
@@ -70,3 +71,4 @@ export interface MongoDataSourceOptions extends DataSourceJsonData {
7071
export interface MySecureJsonData {
7172
password?: string;
7273
}
74+

src/utils.ts

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { DateTime } from "@grafana/data";
1+
import { DataFrameSchema, DataQueryResponse, DateTime, FieldType, MetricFindValue } from "@grafana/data";
22
import { JsQueryResult } from "types";
33
import shadow from "shadowrealm-api";
4-
import {getTemplateSrv} from "@grafana/runtime";
4+
import { getTemplateSrv } from "@grafana/runtime";
55

66
export function validateJsonQueryText(queryText?: string): string | null {
77
if (!queryText) {
@@ -57,7 +57,7 @@ export function parseJsQuery(queryText: string): JsQueryResult {
5757
jsonQuery: result,
5858
error: null
5959
};
60-
}catch (e: Error | any) {
60+
} catch (e: Error | any) {
6161
// if there is an error, return the error message
6262
return {
6363
error: e?.message
@@ -86,3 +86,44 @@ export function getBucketCount(from: DateTime, to: DateTime, intervalMs: number)
8686

8787
return count;
8888
}
89+
90+
export function randomId(length: number) {
91+
let result = "";
92+
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
93+
const charactersLength = characters.length;
94+
let counter = 0;
95+
while (counter < length) {
96+
result += characters.charAt(Math.floor(Math.random() * charactersLength));
97+
counter += 1;
98+
}
99+
return result;
100+
}
101+
102+
103+
export function getMetricValues(response: DataQueryResponse): MetricFindValue[] {
104+
const dataframe = response.data[0] as DataFrameSchema;
105+
const fields = dataframe.
106+
fields.filter(f => f.type === FieldType.string || f.type === FieldType.number)
107+
// @ts-ignore
108+
.filter(f => f.values && f.values.length > 0);
109+
110+
// @ts-ignore
111+
return fields.map(function (field) {
112+
// @ts-ignore
113+
const values: Array<string | number> = field.values;
114+
let text: string;
115+
116+
if (values.length === 1) {
117+
text = `${field.name}:${values[0]}`;
118+
} else {
119+
text = `${field.name}:[${values[0]}, ...]`;
120+
}
121+
122+
return {
123+
text: text,
124+
// @ts-ignore
125+
value: values.length === 1 ? values[0] : values,
126+
expandable: true
127+
};
128+
});
129+
}

0 commit comments

Comments
 (0)