import React, { PureComponent } from 'react'; 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, getDataSourceSrv } from '@grafana/runtime'; type Props = QueryEditorProps; const labelWidth = 12; interface State { channels: Array>; channelFields: Record>>; folders?: Array>; } export class QueryEditor extends PureComponent { state: State = { channels: [], channelFields: {} }; queryTypes: Array> = [ { label: 'Random Walk', value: GrafanaQueryType.RandomWalk, description: 'Random signal within the selected time range', }, { label: 'Live Measurements', 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() { getBackendSrv() .fetch({ url: 'api/live/list' }) .subscribe({ next: (v: any) => { const channelInfo = v.data?.channels as any[]; if (channelInfo?.length) { const channelFields: Record>> = {}; const channels: Array> = channelInfo.map((c) => { if (c.data) { const distinctFields = new Set(); const frame = dataFrameFromJSON(c.data); for (const f of frame.fields) { distinctFields.add(f.name); } channelFields[c.channel] = Array.from(distinctFields).map((n) => ({ value: n, label: n, })); } return { value: c.channel, label: c.channel + ' [' + c.minute_rate + ' msg/min]', }; }); this.setState({ channelFields, channels }); } }, }); } loadFolderInfo() { const query: DataQueryRequest = { 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(); } onQueryTypeChange = (sel: SelectableValue) => { const { onChange, query, onRunQuery } = this.props; onChange({ ...query, queryType: sel.value! }); onRunQuery(); // Reload the channel list this.loadChannelInfo(); }; onChannelChange = (sel: SelectableValue) => { const { onChange, query, onRunQuery } = this.props; onChange({ ...query, channel: sel?.value }); onRunQuery(); }; onFieldNamesChange = (item: SelectableValue) => { const { onChange, query, onRunQuery } = this.props; let fields: string[] = []; if (Array.isArray(item)) { fields = item.map((v) => v.value); } else if (item.value) { fields = [item.value]; } // When adding the first field, also add time (if it exists) if (fields.length === 1 && !query.filter?.fields?.length && query.channel) { const names = this.state.channelFields[query.channel] ?? []; const tf = names.find((f) => f.value === 'time' || f.value === 'Time'); if (tf && tf.value && tf.value !== fields[0]) { fields = [tf.value, ...fields]; } } onChange({ ...query, filter: { ...query.filter, fields, }, }); onRunQuery(); }; checkAndUpdateBuffer = (txt: string) => { const { onChange, query, onRunQuery } = this.props; let buffer: number | undefined; if (txt) { try { buffer = rangeUtil.intervalToSeconds(txt) * 1000; } catch (err) { console.warn('ERROR', err); } } onChange({ ...query, buffer, }); onRunQuery(); }; handleEnterKey = (e: React.KeyboardEvent) => { if (e.key !== 'Enter') { return; } this.checkAndUpdateBuffer((e.target as any).value); }; handleBlur = (e: React.FocusEvent) => { this.checkAndUpdateBuffer(e.target.value); }; renderMeasurementsQuery() { let { channel, filter, buffer } = this.props.query; let { channels, channelFields } = this.state; let currentChannel = channels.find((c) => c.value === channel); if (channel && !currentChannel) { currentChannel = { value: channel, label: channel, description: `Connected to ${channel}`, }; channels = [currentChannel, ...channels]; } const distinctFields = new Set(); const fields: Array> = channel ? channelFields[channel] ?? [] : []; // if (data && data.series?.length) { // for (const frame of data.series) { // for (const field of frame.fields) { // if (distinctFields.has(field.name) || !field.name) { // continue; // } // fields.push({ // value: field.name, // label: field.name, // description: `(${getFrameDisplayName(frame)} / ${field.type})`, // }); // distinctFields.add(field.name); // } // } // } if (filter?.fields) { for (const f of filter.fields) { if (!distinctFields.has(f)) { fields.push({ value: f, label: `${f} (not loaded)`, description: `Configured, but not found in the query results`, }); distinctFields.add(f); } } } let formattedTime = ''; if (buffer) { formattedTime = rangeUtil.secondsToHms(buffer / 1000); } return ( <>
`Field: ${input}`} isSearchable={true} isMulti={true} />
)} This supports real-time event streams in Grafana core. This feature is under heavy development. Expect the interfaces and structures to change as this becomes more production ready. ); } onFolderChanged = (sel: SelectableValue) => { 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 ( v.value === query.queryType) || this.queryTypes[0]} onChange={this.onQueryTypeChange} /> {query.queryType === GrafanaQueryType.LiveMeasurements && this.renderMeasurementsQuery()} {query.queryType === GrafanaQueryType.List && this.renderListPublicFiles()} ); } }