Skip to content

Commit af63676

Browse files
authored
Add details about stream in the query editor (#12)
1 parent 45e81cb commit af63676

File tree

12 files changed

+138
-23
lines changed

12 files changed

+138
-23
lines changed

dist/img/logo.svg

Lines changed: 1 addition & 1 deletion
Loading

dist/module.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/module.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/plugin.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"id": "parseable-datasource",
66
"logs": true,
77
"info": {
8-
"description": "Parseable is a log observability platform that helps you collect, parse, and analyze your logs. This plugin allows you to query your logs in Grafana.",
8+
"description": "Parseable is a logging platform that helps you collect, parse, and analyze your logs. This plugin allows you to visualize your Parseable logs in Grafana.",
99
"author": {
1010
"name": "Parseable Team <[email protected]>"
1111
},
@@ -24,7 +24,7 @@
2424
{"name": "Log Dashboard", "path": "img/log-visualisation.png"}
2525
],
2626
"version": "1.0.0",
27-
"updated": "2023-01-10"
27+
"updated": "2023-01-11"
2828
},
2929
"routes": [
3030
{

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "parseable",
33
"version": "1.0.0",
4-
"description": "Y",
4+
"description": "Parseable is a logging platform that helps you collect, parse, and analyze your logs. This plugin allows you to visualize your Parseable logs in Grafana.",
55
"scripts": {
66
"build": "webpack -c ./.config/webpack/webpack.config.ts --env production",
77
"dev": "webpack -w -c ./.config/webpack/webpack.config.ts --env development",
@@ -18,7 +18,7 @@
1818
"build-storybook": "build-storybook"
1919
},
2020
"author": "Parseable",
21-
"license": "Affero General Public License v3.0",
21+
"license": "AGPL-3.0-only",
2222
"devDependencies": {
2323
"@babel/core": "^7.16.7",
2424
"@grafana/e2e": "9.2.5",
299 Bytes
Binary file not shown.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
a106522fa8af91dc1277c63ee19a0a0b8bd3b3bf
1+
dd08ae43a151ee69e30f74592a34e9726373fc4d

src/components/QueryEditor.tsx

Lines changed: 78 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { ComponentType, ChangeEvent, useState } from 'react';
22
import { LegacyForms, AsyncSelect, Label, InlineField, InlineFieldRow } from '@grafana/ui';
33
import { QueryEditorProps, SelectableValue } from '@grafana/data';
44
import { DataSource } from '../datasource';
5-
import { Fields, MyDataSourceOptions, MyQuery } from '../types';
5+
import { SchemaFields, MyDataSourceOptions, MyQuery } from '../types';
66

77
const { FormField } = LegacyForms;
88

@@ -30,14 +30,18 @@ export const QueryEditor: ComponentType<Props> = ({ datasource, onChange, onRunQ
3030

3131
const [value, setValue] = useState<SelectableValue<string>>();
3232
const [schema = '', setSchema] = React.useState<string | number>();
33+
const [count = '', setEventCount] = React.useState<string | number>();
34+
const [jsonsize = '', setJsonSize] = React.useState<string | number>();
35+
const [parquetsize = '', setParquetSize] = React.useState<string | number>();
36+
const [streamname = '', setStreamName] = React.useState<string | number>();
3337
//const [fielder, setFielder] = React.useState<string | number>();
3438

35-
const loadSchemaOptions = React.useCallback((value) => {
39+
const loadStreamSchema = React.useCallback((value) => {
3640
if (value) {
37-
return datasource.listSchema(value).then(
41+
return datasource.streamSchema(value).then(
3842
(result) => {
3943
if (result.fields) {
40-
const schema = result.fields.map((data: Fields) => (data.name));
44+
const schema = result.fields.map((data: SchemaFields) => (data.name));
4145
const schemaToText = schema.join(", ")
4246
setSchema(schemaToText);
4347
return schema;
@@ -52,6 +56,31 @@ export const QueryEditor: ComponentType<Props> = ({ datasource, onChange, onRunQ
5256
return '';
5357
}, [datasource, schema]);
5458

59+
const loadStreamStats = React.useCallback((value) => {
60+
if (value) {
61+
return datasource.streamStats(value).then(
62+
(result) => {
63+
if (result.ingestion) {
64+
const count = result.ingestion.count;
65+
const jsonsize = result.ingestion.size;
66+
const parquetsize = result.storage?.size;
67+
const streamname = result.stream;
68+
setJsonSize(jsonsize);
69+
setParquetSize(parquetsize);
70+
setStreamName(streamname);
71+
setEventCount(count);
72+
return count;
73+
}
74+
return count;
75+
},
76+
(response) => {
77+
throw new Error(response.statusText);
78+
}
79+
);
80+
}
81+
return '';
82+
}, [datasource, count]);
83+
5584
const onQueryTextChange = (event: ChangeEvent<HTMLInputElement>) => {
5685
onChange({ ...query, queryText: event.target.value });
5786
};
@@ -65,15 +94,21 @@ export const QueryEditor: ComponentType<Props> = ({ datasource, onChange, onRunQ
6594
}, [onRunQuery, queryText])
6695

6796
React.useEffect(() => {
68-
loadSchemaOptions(value)
69-
}, [loadSchemaOptions, value]);
97+
loadStreamSchema(value)
98+
}, [loadStreamSchema, value]);
99+
100+
React.useEffect(() => {
101+
loadStreamStats(value)
102+
}, [loadStreamStats, value]);
70103

71104
return (
72105
<>
73106
<div className="gf-form">
74107
<InlineField>
75108
<Label >
76-
StreamName:
109+
<div style={{ width: 'fit-content', color: 'blue' }}>
110+
Select a log stream:
111+
</div>
77112
<div style={{ width: 200 + 'px', marginRight: '20px', marginLeft: '20px' }}>
78113
<AsyncSelect
79114
loadOptions={loadAsyncOptions}
@@ -89,8 +124,42 @@ export const QueryEditor: ComponentType<Props> = ({ datasource, onChange, onRunQ
89124

90125
<InlineFieldRow>
91126
<div>
127+
<Label>
128+
<div style={{ width: 'fit-content', textAlign: 'center', color: 'blue'}}>
129+
Stream {streamname} details
130+
</div>
131+
</Label>
132+
<Label>
133+
<div style={{ width: 'fit-content', color: 'blue' }}>
134+
Columns:
135+
</div>
136+
<div style={{ width: 'fit-content' }}>
137+
{ schema}
138+
</div>
139+
</Label>
140+
<Label>
141+
<div style={{ width: 'fit-content', color: 'blue' }}>
142+
Total events ingested:
143+
</div>
144+
<div style={{ width: 'fit-content' }}>
145+
{ count}
146+
</div>
147+
</Label>
148+
<Label>
149+
<div style={{ width: 'fit-content', color: 'blue' }}>
150+
Total ingested data size:
151+
</div>
152+
<div style={{ width: 'fit-content' }}>
153+
{ jsonsize}
154+
</div>
155+
</Label>
92156
<Label>
93-
Schema: {schema}
157+
<div style={{ width: 'fit-content', color: 'blue' }}>
158+
Total compressed data stored:
159+
</div>
160+
<div style={{ width: 'fit-content' }}>
161+
{ parquetsize}
162+
</div>
94163
</Label>
95164
</div>
96165
</InlineFieldRow>
@@ -102,7 +171,7 @@ export const QueryEditor: ComponentType<Props> = ({ datasource, onChange, onRunQ
102171
value={queryText || ''}
103172
onChange={onQueryTextChange}
104173
label="SQL Query"
105-
tooltip="Enter the search SQL query here."
174+
tooltip="Enter the search SQL query here (use column names as displayed above)"
106175
/>
107176
</>
108177
);

src/datasource.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ import {
2020
StreamName,
2121
StreamList,
2222
//Fields,
23-
ListSchemaResponse
23+
StreamSchemaResponse,
24+
StreamStatsResponse
2425
} from './types';
2526
export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
2627
url: string;
@@ -149,7 +150,32 @@ export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
149150
);
150151
}
151152

152-
async listSchema(streamname: StreamName): Promise<ListSchemaResponse> {
153+
async streamStats(streamname: StreamName): Promise<StreamStatsResponse> {
154+
if (streamname) {
155+
return lastValueFrom(
156+
this.doFetch({
157+
url: this.url + '/api/v1/logstream/' + streamname.value + '/stats',
158+
method: 'GET',
159+
}).pipe(
160+
map((response) =>
161+
(typeof response.data === 'object' && !isNull(response.data))
162+
? response.data
163+
: {}
164+
),
165+
catchError((err) => {
166+
console.error(err);
167+
return of({
168+
status: 'error',
169+
message: err.statusText
170+
})
171+
172+
}))
173+
)
174+
}
175+
return {}
176+
}
177+
178+
async streamSchema(streamname: StreamName): Promise<StreamSchemaResponse> {
153179
if (streamname) {
154180
return lastValueFrom(
155181
this.doFetch({

src/img/logo.svg

Lines changed: 1 addition & 1 deletion
Loading

0 commit comments

Comments
 (0)