Skip to content

Commit 0eb35a1

Browse files
author
wtao
committed
prepare query upgrade
1 parent 549d2e3 commit 0eb35a1

File tree

7 files changed

+121
-35
lines changed

7 files changed

+121
-35
lines changed

backend/Dockerfile

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Use the official Node.js image as base
2+
FROM node:20
3+
4+
# Set the working directory in the container
5+
WORKDIR /usr/src/app
6+
7+
# Copy package.json and package-lock.json to the working directory
8+
COPY package*.json ./
9+
10+
# Install dependencies
11+
RUN npm install
12+
13+
# Copy the rest of the application code
14+
COPY . .
15+
16+
# Expose port 4000 to the outside world
17+
EXPOSE 4000
18+
19+
# Command to run the application
20+
CMD ["node", "index.mjs"]

backend/express.mjs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,18 @@ export const dbQuery = async (req, res, next) => {
4141
next(err);
4242
}
4343
};
44+
45+
// grafana: getall collection names
46+
export const dbCollections = async (req, res) => {
47+
const { url, db } = req.body;
48+
let client = null;
49+
try {
50+
client = new MongoClient(url);
51+
await client.connect();
52+
const cursor = await client.db(db).listCollections().toArray();
53+
const collections = cursor.map((collection) => collection.name);
54+
res.json(collections);
55+
} finally {
56+
client?.close();
57+
}
58+
};

backend/index-sls.mjs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import express from "express";
22
import serverless from "serverless-http";
3-
import { dbConfig, dbQuery } from "./express.mjs";
3+
import { dbConfig, dbQuery, dbCollections } from "./express.mjs";
44

55
const app = express();
66
app.use(express.json());
@@ -18,6 +18,9 @@ app.all(`${awsApiGatewayRoute}/`, dbConfig);
1818
// grafana: qeury
1919
app.all(`${awsApiGatewayRoute}/query`, dbQuery);
2020

21+
// grafana: getall collection names
22+
app.all(`${awsApiGatewayRoute}/collections`, dbCollections);
23+
2124
//default error handler
2225
app.use((err, req, res, next) => {
2326
console.error(err.stack);

backend/index.mjs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import express from "express";
22
import cors from "cors";
3-
import { dbConfig, dbQuery } from "./express.mjs";
3+
import { dbConfig, dbQuery, dbCollections } from "./express.mjs";
44

55
const app = express();
66
app.use(express.json());
@@ -12,6 +12,9 @@ app.all("/", dbConfig);
1212
// grafana: qeury
1313
app.all("/query", dbQuery);
1414

15+
// grafana: getall collection names
16+
app.all("/collections", dbCollections);
17+
1518
//default error handler
1619
app.use((err, req, res, next) => {
1720
console.error(err.stack);

frontend/src/components/QueryEditor.tsx

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
11
import React from 'react';
2-
import { InlineField, TextArea, Stack, Select } from '@grafana/ui';
2+
import {
3+
InlineField,
4+
TextArea,
5+
Stack,
6+
Select,
7+
Modal,
8+
Button,
9+
Spinner,
10+
} from '@grafana/ui';
311
import { QueryEditorProps } from '@grafana/data';
412
import { DataSource } from '../datasource';
513
import { MyDataSourceOptions, MyQuery, QueryType } from '../types';
14+
import { getBackendSrv } from '@grafana/runtime';
15+
import { lastValueFrom } from 'rxjs';
616

717
type Props = QueryEditorProps<DataSource, MyQuery, MyDataSourceOptions>;
818

@@ -22,10 +32,41 @@ const queryHint = `{
2232
],
2333
}`;
2434

25-
export function QueryEditor({ query, onChange, onRunQuery }: Props) {
26-
const { queryText, queryType } = query;
35+
export function QueryEditor(props: Props) {
36+
const {
37+
query,
38+
datasource: { baseUrl, db },
39+
onChange,
40+
onRunQuery,
41+
} = props;
42+
const { collection, queryText, queryType } = query;
43+
const [collections, setCollections] = React.useState<string[]>([]);
44+
const [spinner, setSpinner] = React.useState(true);
45+
const [errorMsg, setErrorMsg] = React.useState('');
2746

28-
return (
47+
React.useEffect(() => {
48+
if (!!db && !!baseUrl) {
49+
(async () => {
50+
try {
51+
const response = await getBackendSrv().fetch<string[]>({
52+
url: `${baseUrl}/collections`,
53+
method: 'POST',
54+
headers: {
55+
'Content-Type': 'application/json',
56+
},
57+
data: JSON.stringify(db),
58+
});
59+
const collections = await lastValueFrom(response);
60+
setCollections(collections.data);
61+
setSpinner(false);
62+
} catch (err: any) {
63+
setErrorMsg(err?.message ?? err);
64+
}
65+
})();
66+
}
67+
}, [db, baseUrl]);
68+
69+
const queryUI = (
2970
<Stack gap={0} direction="column">
3071
<InlineField label="Type" labelWidth={16} tooltip="Not used yet">
3172
<Select
@@ -38,9 +79,20 @@ export function QueryEditor({ query, onChange, onRunQuery }: Props) {
3879
onChange={(sv) => onChange({ ...query, queryType: sv.value!! })}
3980
/>
4081
</InlineField>
82+
<InlineField label="Type" labelWidth={16} tooltip="Not used yet">
83+
<Select
84+
width={50}
85+
options={collections.map((collection) => ({
86+
label: collection,
87+
value: collection,
88+
}))}
89+
value={collection}
90+
onChange={(sv) => onChange({ ...query, collection: sv.value!! })}
91+
/>
92+
</InlineField>
4193
<InlineField label="Query Text" labelWidth={16} tooltip="Not used yet">
4294
<TextArea
43-
style={{ width: 500, minHeight:200 }}
95+
style={{ width: 500, minHeight: 200 }}
4496
value={queryText ?? ''}
4597
placeholder={queryHint}
4698
onChange={(event) => {
@@ -54,4 +106,18 @@ export function QueryEditor({ query, onChange, onRunQuery }: Props) {
54106
</InlineField>
55107
</Stack>
56108
);
109+
110+
const loadingUI = (
111+
<Stack gap={0} direction="column">
112+
<Spinner />
113+
<Modal title="error" isOpen={!!errorMsg}>
114+
{errorMsg}
115+
<Modal.ButtonRow>
116+
<Button onClick={() => setErrorMsg('')}>Ok</Button>
117+
</Modal.ButtonRow>
118+
</Modal>
119+
</Stack>
120+
);
121+
122+
return spinner ? loadingUI : queryUI;
57123
}

frontend/src/datasource.ts

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FetchResponse, getBackendSrv, isFetchError } from '@grafana/runtime';
1+
import { FetchResponse, getBackendSrv } from '@grafana/runtime';
22
import {
33
CoreApp,
44
DataQueryRequest,
@@ -15,7 +15,6 @@ import {
1515
QueryType,
1616
} from './types';
1717
import { lastValueFrom } from 'rxjs';
18-
import _ from 'lodash';
1918

2019
interface PluginConfigs {
2120
url: string;
@@ -25,7 +24,6 @@ interface PluginConfigs {
2524
export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
2625
baseUrl: string;
2726
db: PluginConfigs;
28-
defaultErrorMessage = 'unknown error';
2927

3028
constructor(
3129
instanceSettings: DataSourceInstanceSettings<MyDataSourceOptions>,
@@ -61,18 +59,8 @@ export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
6159
TimeSeriesResponse[] | TableResponse[]
6260
>;
6361
return { data: response.data };
64-
} catch (err) {
65-
let message = '';
66-
if (_.isString(err)) {
67-
message = err;
68-
} else if (isFetchError(err)) {
69-
message =
70-
'Fetch error: ' + (err.statusText ?? this.defaultErrorMessage);
71-
if (err.data && err.data.error && err.data.error.code) {
72-
message += ': ' + err.data.error.code + '. ' + err.data.error.message;
73-
}
74-
}
75-
return { data: [], error: { message } };
62+
} catch (err: any) {
63+
return { data: [], error: { message: err?.message ?? err } };
7664
}
7765
}
7866

@@ -111,23 +99,13 @@ export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
11199
} else {
112100
return {
113101
status: 'error',
114-
message: response.data.message ?? this.defaultErrorMessage,
102+
message: response.data.message ?? 'unknown',
115103
};
116104
}
117-
} catch (err) {
118-
let message = '';
119-
if (_.isString(err)) {
120-
message = err;
121-
} else if (isFetchError(err)) {
122-
message =
123-
'Fetch error: ' + (err.statusText ?? this.defaultErrorMessage);
124-
if (err.data && err.data.error && err.data.error.code) {
125-
message += ': ' + err.data.error.code + '. ' + err.data.error.message;
126-
}
127-
}
105+
} catch (err: any) {
128106
return {
129107
status: 'error',
130-
message,
108+
message: err?.message ?? err,
131109
};
132110
}
133111
}

frontend/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export enum QueryType {
77
}
88

99
export interface MyQuery extends DataQuery {
10+
collection: string;
1011
queryText: string;
1112
queryType: QueryType;
1213
}

0 commit comments

Comments
 (0)