mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Grafana: include a built-in backend datasource (#38571)
This commit is contained in:
@@ -1,9 +1,16 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { InlineField, Select, Alert, Input } from '@grafana/ui';
|
||||
import { QueryEditorProps, SelectableValue, dataFrameFromJSON, rangeUtil } from '@grafana/data';
|
||||
import { InlineField, Select, Alert, Input, InlineFieldRow } from '@grafana/ui';
|
||||
import {
|
||||
QueryEditorProps,
|
||||
SelectableValue,
|
||||
dataFrameFromJSON,
|
||||
rangeUtil,
|
||||
DataQueryRequest,
|
||||
DataFrame,
|
||||
} from '@grafana/data';
|
||||
import { GrafanaDatasource } from '../datasource';
|
||||
import { defaultQuery, GrafanaQuery, GrafanaQueryType } from '../types';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { getBackendSrv, getDataSourceSrv } from '@grafana/runtime';
|
||||
|
||||
type Props = QueryEditorProps<GrafanaDatasource, GrafanaQuery>;
|
||||
|
||||
@@ -12,6 +19,7 @@ const labelWidth = 12;
|
||||
interface State {
|
||||
channels: Array<SelectableValue<string>>;
|
||||
channelFields: Record<string, Array<SelectableValue<string>>>;
|
||||
folders?: Array<SelectableValue<string>>;
|
||||
}
|
||||
|
||||
export class QueryEditor extends PureComponent<Props, State> {
|
||||
@@ -28,6 +36,11 @@ export class QueryEditor extends PureComponent<Props, State> {
|
||||
value: GrafanaQueryType.LiveMeasurements,
|
||||
description: 'Stream real-time measurements from Grafana',
|
||||
},
|
||||
{
|
||||
label: 'List public files',
|
||||
value: GrafanaQueryType.List,
|
||||
description: 'Show directory listings for public resources',
|
||||
},
|
||||
];
|
||||
|
||||
loadChannelInfo() {
|
||||
@@ -62,6 +75,30 @@ export class QueryEditor extends PureComponent<Props, State> {
|
||||
});
|
||||
}
|
||||
|
||||
loadFolderInfo() {
|
||||
const query: DataQueryRequest<GrafanaQuery> = {
|
||||
targets: [{ queryType: GrafanaQueryType.List, refId: 'A' }],
|
||||
} as any;
|
||||
|
||||
getDataSourceSrv()
|
||||
.get('-- Grafana --')
|
||||
.then((ds) => {
|
||||
const gds = ds as GrafanaDatasource;
|
||||
gds.query(query).subscribe({
|
||||
next: (rsp) => {
|
||||
if (rsp.data.length) {
|
||||
const names = (rsp.data[0] as DataFrame).fields[0];
|
||||
const folders = names.values.toArray().map((v) => ({
|
||||
value: v,
|
||||
label: v,
|
||||
}));
|
||||
this.setState({ folders });
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadChannelInfo();
|
||||
}
|
||||
@@ -242,6 +279,49 @@ export class QueryEditor extends PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
onFolderChanged = (sel: SelectableValue<string>) => {
|
||||
const { onChange, query, onRunQuery } = this.props;
|
||||
onChange({ ...query, path: sel?.value });
|
||||
onRunQuery();
|
||||
};
|
||||
|
||||
renderListPublicFiles() {
|
||||
let { path } = this.props.query;
|
||||
let { folders } = this.state;
|
||||
if (!folders) {
|
||||
folders = [];
|
||||
this.loadFolderInfo();
|
||||
}
|
||||
const currentFolder = folders.find((f) => f.value === path);
|
||||
if (path && !currentFolder) {
|
||||
folders = [
|
||||
...folders,
|
||||
{
|
||||
value: path,
|
||||
label: path,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return (
|
||||
<InlineFieldRow>
|
||||
<InlineField label="Path" grow={true} labelWidth={labelWidth}>
|
||||
<Select
|
||||
menuShouldPortal
|
||||
options={folders}
|
||||
value={currentFolder || ''}
|
||||
onChange={this.onFolderChanged}
|
||||
allowCustomValue={true}
|
||||
backspaceRemovesValue={true}
|
||||
placeholder="Select folder"
|
||||
isClearable={true}
|
||||
formatCreateLabel={(input: string) => `Folder: ${input}`}
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const query = {
|
||||
...defaultQuery,
|
||||
@@ -250,7 +330,7 @@ export class QueryEditor extends PureComponent<Props, State> {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="gf-form">
|
||||
<InlineFieldRow>
|
||||
<InlineField label="Query type" grow={true} labelWidth={labelWidth}>
|
||||
<Select
|
||||
menuShouldPortal
|
||||
@@ -259,8 +339,9 @@ export class QueryEditor extends PureComponent<Props, State> {
|
||||
onChange={this.onQueryTypeChange}
|
||||
/>
|
||||
</InlineField>
|
||||
</div>
|
||||
</InlineFieldRow>
|
||||
{query.queryType === GrafanaQueryType.LiveMeasurements && this.renderMeasurementsQuery()}
|
||||
{query.queryType === GrafanaQueryType.List && this.renderListPublicFiles()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { from, merge, Observable, of } from 'rxjs';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
import { getBackendSrv, getGrafanaLiveSrv, getTemplateSrv, toDataQueryResponse } from '@grafana/runtime';
|
||||
import { DataSourceWithBackend, getBackendSrv, getGrafanaLiveSrv, getTemplateSrv } from '@grafana/runtime';
|
||||
import {
|
||||
AnnotationQuery,
|
||||
AnnotationQueryRequest,
|
||||
DataQueryRequest,
|
||||
DataQueryResponse,
|
||||
DataSourceApi,
|
||||
DataSourceInstanceSettings,
|
||||
DatasourceRef,
|
||||
isValidLiveChannelAddress,
|
||||
@@ -22,7 +20,7 @@ import { isString } from 'lodash';
|
||||
|
||||
let counter = 100;
|
||||
|
||||
export class GrafanaDatasource extends DataSourceApi<GrafanaQuery> {
|
||||
export class GrafanaDatasource extends DataSourceWithBackend<GrafanaQuery> {
|
||||
constructor(instanceSettings: DataSourceInstanceSettings) {
|
||||
super(instanceSettings);
|
||||
this.annotations = {
|
||||
@@ -49,7 +47,8 @@ export class GrafanaDatasource extends DataSourceApi<GrafanaQuery> {
|
||||
}
|
||||
|
||||
query(request: DataQueryRequest<GrafanaQuery>): Observable<DataQueryResponse> {
|
||||
const queries: Array<Observable<DataQueryResponse>> = [];
|
||||
const results: Array<Observable<DataQueryResponse>> = [];
|
||||
const targets: GrafanaQuery[] = [];
|
||||
const templateSrv = getTemplateSrv();
|
||||
for (const target of request.targets) {
|
||||
if (target.queryType === GrafanaQueryType.Annotations) {
|
||||
@@ -90,7 +89,7 @@ export class GrafanaDatasource extends DataSourceApi<GrafanaQuery> {
|
||||
buffer.maxDelta = request.range.to.valueOf() - request.range.from.valueOf();
|
||||
}
|
||||
|
||||
queries.push(
|
||||
results.push(
|
||||
getGrafanaLiveSrv().getDataStream({
|
||||
key: `${request.requestId}.${counter++}`,
|
||||
addr: addr!,
|
||||
@@ -99,15 +98,28 @@ export class GrafanaDatasource extends DataSourceApi<GrafanaQuery> {
|
||||
})
|
||||
);
|
||||
} else {
|
||||
queries.push(getRandomWalk(request));
|
||||
if (!target.queryType) {
|
||||
target.queryType = GrafanaQueryType.RandomWalk;
|
||||
}
|
||||
targets.push(target);
|
||||
}
|
||||
}
|
||||
// With a single query just return the results
|
||||
if (queries.length === 1) {
|
||||
return queries[0];
|
||||
|
||||
if (targets.length) {
|
||||
results.push(
|
||||
super.query({
|
||||
...request,
|
||||
targets,
|
||||
})
|
||||
);
|
||||
}
|
||||
if (queries.length > 1) {
|
||||
return merge(...queries);
|
||||
|
||||
if (results.length) {
|
||||
// With a single query just return the results
|
||||
if (results.length === 1) {
|
||||
return results[0];
|
||||
}
|
||||
return merge(...results);
|
||||
}
|
||||
return of(); // nothing
|
||||
}
|
||||
@@ -171,32 +183,3 @@ export class GrafanaDatasource extends DataSourceApi<GrafanaQuery> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
// Note that the query does not actually matter
|
||||
function getRandomWalk(request: DataQueryRequest): Observable<DataQueryResponse> {
|
||||
const { intervalMs, maxDataPoints, range, requestId } = request;
|
||||
|
||||
// Yes, this implementation ignores multiple targets! But that matches existing behavior
|
||||
const params: Record<string, any> = {
|
||||
intervalMs,
|
||||
maxDataPoints,
|
||||
from: range.from.valueOf(),
|
||||
to: range.to.valueOf(),
|
||||
};
|
||||
|
||||
return getBackendSrv()
|
||||
.fetch({
|
||||
url: '/api/tsdb/testdata/random-walk',
|
||||
method: 'GET',
|
||||
params,
|
||||
requestId,
|
||||
})
|
||||
.pipe(
|
||||
map((rsp: any) => {
|
||||
return toDataQueryResponse(rsp);
|
||||
}),
|
||||
catchError((err) => {
|
||||
return of(toDataQueryResponse(err));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,9 +6,13 @@ import { LiveDataFilter } from '@grafana/runtime';
|
||||
//----------------------------------------------
|
||||
|
||||
export enum GrafanaQueryType {
|
||||
RandomWalk = 'randomWalk',
|
||||
LiveMeasurements = 'measurements',
|
||||
Annotations = 'annotations',
|
||||
|
||||
// backend
|
||||
RandomWalk = 'randomWalk',
|
||||
List = 'list',
|
||||
Read = 'read',
|
||||
}
|
||||
|
||||
export interface GrafanaQuery extends DataQuery {
|
||||
@@ -16,6 +20,7 @@ export interface GrafanaQuery extends DataQuery {
|
||||
channel?: string;
|
||||
filter?: LiveDataFilter;
|
||||
buffer?: number;
|
||||
path?: string; // for list and read
|
||||
}
|
||||
|
||||
export const defaultQuery: GrafanaQuery = {
|
||||
|
||||
Reference in New Issue
Block a user