mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
DirectInput: new alpha datasource that lets you enter data via CSV
Initial alpha datasource that saves data directly in a panel or in the shared datasource configs.
This commit is contained in:
parent
d27de81a91
commit
08a22c806f
@ -12,14 +12,14 @@ TableInputStories.addDecorator(withCenteredStory);
|
||||
|
||||
TableInputStories.add('default', () => {
|
||||
return (
|
||||
<div style={{ width: '90%', height: '90vh' }}>
|
||||
<TableInputCSV
|
||||
text={'a,b,c\n1,2,3'}
|
||||
onSeriesParsed={(data: SeriesData[], text: string) => {
|
||||
console.log('Data', data, text);
|
||||
action('Data')(data, text);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<TableInputCSV
|
||||
width={400}
|
||||
height={'90vh'}
|
||||
text={'a,b,c\n1,2,3'}
|
||||
onSeriesParsed={(data: SeriesData[], text: string) => {
|
||||
console.log('Data', data, text);
|
||||
action('Data')(data, text);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
@ -9,6 +9,8 @@ describe('TableInputCSV', () => {
|
||||
const tree = renderer
|
||||
.create(
|
||||
<TableInputCSV
|
||||
width={'100%'}
|
||||
height={200}
|
||||
text={'a,b,c\n1,2,3'}
|
||||
onSeriesParsed={(data: SeriesData[], text: string) => {
|
||||
// console.log('Table:', table, 'from:', text);
|
||||
|
@ -1,12 +1,13 @@
|
||||
import React from 'react';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { SeriesData } from '../../types/data';
|
||||
import { AutoSizer } from 'react-virtualized';
|
||||
import { CSVConfig, readCSV } from '../../utils/csv';
|
||||
|
||||
interface Props {
|
||||
config?: CSVConfig;
|
||||
text: string;
|
||||
width: string | number;
|
||||
height: string | number;
|
||||
onSeriesParsed: (data: SeriesData[], text: string) => void;
|
||||
}
|
||||
|
||||
@ -18,7 +19,7 @@ interface State {
|
||||
/**
|
||||
* Expects the container div to have size set and will fill it 100%
|
||||
*/
|
||||
class TableInputCSV extends React.PureComponent<Props, State> {
|
||||
export class TableInputCSV extends React.PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
@ -58,28 +59,30 @@ class TableInputCSV extends React.PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { width, height } = this.props;
|
||||
const { data } = this.state;
|
||||
|
||||
return (
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<div className="gf-table-input-csv" style={{ width, height }}>
|
||||
<textarea placeholder="Enter CSV here..." value={this.state.text} onChange={this.onTextChange} />
|
||||
{data && (
|
||||
<footer>
|
||||
{data.map((series, index) => {
|
||||
return (
|
||||
<span key={index}>
|
||||
Rows:{series.rows.length}, Columns:{series.fields.length}
|
||||
<i className="fa fa-check-circle" />
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</footer>
|
||||
)}
|
||||
</div>
|
||||
<div className="gf-table-input-csv">
|
||||
<textarea
|
||||
style={{ width, height }}
|
||||
placeholder="Enter CSV here..."
|
||||
value={this.state.text}
|
||||
onChange={this.onTextChange}
|
||||
className="gf-form-input"
|
||||
/>
|
||||
{data && (
|
||||
<footer>
|
||||
{data.map((series, index) => {
|
||||
return (
|
||||
<span key={index}>
|
||||
Rows:{series.rows.length}, Columns:{series.fields.length}
|
||||
<i className="fa fa-check-circle" />
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</footer>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@
|
||||
.gf-table-input-csv textarea {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.gf-table-input-csv footer {
|
||||
@ -13,8 +12,7 @@
|
||||
bottom: 15px;
|
||||
right: 15px;
|
||||
border: 1px solid #222;
|
||||
background: #ccc;
|
||||
background: $online;
|
||||
padding: 1px 4px;
|
||||
font-size: 80%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -32,6 +32,9 @@ export { UnitPicker } from './UnitPicker/UnitPicker';
|
||||
export { StatsPicker } from './StatsPicker/StatsPicker';
|
||||
export { Input, InputStatus } from './Input/Input';
|
||||
|
||||
export { Table } from './Table/Table';
|
||||
export { TableInputCSV } from './Table/TableInputCSV';
|
||||
|
||||
// Visualizations
|
||||
export { BigValue } from './BigValue/BigValue';
|
||||
export { Gauge } from './Gauge/Gauge';
|
||||
|
@ -116,6 +116,11 @@ export interface DataSourceApi<TQuery extends DataQuery = DataQuery> {
|
||||
*/
|
||||
name?: string;
|
||||
|
||||
/**
|
||||
* Set after constructor is called by Grafana
|
||||
*/
|
||||
id?: number;
|
||||
|
||||
/**
|
||||
* Set after constructor call, as the data source instance is the most common thing to pass around
|
||||
* we attach the components to this instance for easy access
|
||||
@ -275,6 +280,7 @@ export interface DataSourceSettings {
|
||||
* as this data model is available to every user who has access to a data source (Viewers+).
|
||||
*/
|
||||
export interface DataSourceInstanceSettings {
|
||||
id: number;
|
||||
type: string;
|
||||
name: string;
|
||||
meta: PluginMeta;
|
||||
|
@ -316,6 +316,10 @@ function getHeaderLine(key: string, fields: Field[], config: CSVConfig): string
|
||||
}
|
||||
|
||||
export function toCSV(data: SeriesData[], config?: CSVConfig): string {
|
||||
if (!data) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let csv = '';
|
||||
config = defaults(config, {
|
||||
delimiter: ',',
|
||||
|
@ -11,6 +11,7 @@ import * as postgresPlugin from 'app/plugins/datasource/postgres/module';
|
||||
import * as prometheusPlugin from 'app/plugins/datasource/prometheus/module';
|
||||
import * as mssqlPlugin from 'app/plugins/datasource/mssql/module';
|
||||
import * as testDataDSPlugin from 'app/plugins/datasource/testdata/module';
|
||||
import * as inputDatasourcePlugin from 'app/plugins/datasource/input/module';
|
||||
import * as stackdriverPlugin from 'app/plugins/datasource/stackdriver/module';
|
||||
import * as azureMonitorPlugin from 'app/plugins/datasource/grafana-azure-monitor-datasource/module';
|
||||
|
||||
@ -45,6 +46,7 @@ const builtInPlugins = {
|
||||
'app/plugins/datasource/mssql/module': mssqlPlugin,
|
||||
'app/plugins/datasource/prometheus/module': prometheusPlugin,
|
||||
'app/plugins/datasource/testdata/module': testDataDSPlugin,
|
||||
'app/plugins/datasource/input/module': inputDatasourcePlugin,
|
||||
'app/plugins/datasource/stackdriver/module': stackdriverPlugin,
|
||||
'app/plugins/datasource/grafana-azure-monitor-datasource/module': azureMonitorPlugin,
|
||||
|
||||
|
@ -65,6 +65,7 @@ export class DatasourceSrv {
|
||||
instanceSettings: dsConfig,
|
||||
});
|
||||
|
||||
instance.id = dsConfig.id;
|
||||
instance.name = name;
|
||||
instance.components = dsPlugin.components;
|
||||
instance.meta = dsConfig.meta;
|
||||
|
@ -23,18 +23,21 @@ describe('datasource_srv', () => {
|
||||
beforeEach(() => {
|
||||
config.datasources = {
|
||||
buildInDs: {
|
||||
id: 1,
|
||||
type: 'b',
|
||||
name: 'buildIn',
|
||||
meta: { builtIn: true } as PluginMeta,
|
||||
jsonData: {},
|
||||
},
|
||||
nonBuildIn: {
|
||||
id: 2,
|
||||
type: 'e',
|
||||
name: 'external1',
|
||||
meta: { builtIn: false } as PluginMeta,
|
||||
jsonData: {},
|
||||
},
|
||||
nonExplore: {
|
||||
id: 3,
|
||||
type: 'e2',
|
||||
name: 'external2',
|
||||
meta: {} as PluginMeta,
|
||||
|
98
public/app/plugins/datasource/input/InputQueryEditor.tsx
Normal file
98
public/app/plugins/datasource/input/InputQueryEditor.tsx
Normal file
@ -0,0 +1,98 @@
|
||||
// Libraries
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Types
|
||||
import { InputDatasource } from './datasource';
|
||||
import { InputQuery } from './types';
|
||||
|
||||
import { FormLabel, Select, QueryEditorProps, SelectOptionItem, SeriesData, TableInputCSV, toCSV } from '@grafana/ui';
|
||||
|
||||
type Props = QueryEditorProps<InputDatasource, InputQuery>;
|
||||
|
||||
const options = [
|
||||
{ value: 'panel', label: 'Panel', description: 'Save data in the panel configuration.' },
|
||||
{ value: 'shared', label: 'Shared', description: 'Save data in the shared datasource object.' },
|
||||
];
|
||||
|
||||
interface State {
|
||||
text: string;
|
||||
}
|
||||
|
||||
export class InputQueryEditor extends PureComponent<Props, State> {
|
||||
state = {
|
||||
text: '',
|
||||
};
|
||||
|
||||
onComponentDidMount() {
|
||||
const { query } = this.props;
|
||||
const text = query.data ? toCSV(query.data) : '';
|
||||
this.setState({ text });
|
||||
}
|
||||
|
||||
onSourceChange = (item: SelectOptionItem) => {
|
||||
const { datasource, query, onChange, onRunQuery } = this.props;
|
||||
let data: SeriesData[] | undefined = undefined;
|
||||
if (item.value === 'panel') {
|
||||
if (query.data) {
|
||||
return;
|
||||
}
|
||||
data = [...datasource.data];
|
||||
if (!data) {
|
||||
data = [
|
||||
{
|
||||
fields: [],
|
||||
rows: [],
|
||||
},
|
||||
];
|
||||
}
|
||||
this.setState({ text: toCSV(data) });
|
||||
}
|
||||
onChange({ ...query, data });
|
||||
onRunQuery();
|
||||
};
|
||||
|
||||
onSeriesParsed = (data: SeriesData[], text: string) => {
|
||||
const { query, onChange, onRunQuery } = this.props;
|
||||
this.setState({ text });
|
||||
if (!data) {
|
||||
data = [
|
||||
{
|
||||
fields: [],
|
||||
rows: [],
|
||||
},
|
||||
];
|
||||
}
|
||||
onChange({ ...query, data });
|
||||
onRunQuery();
|
||||
};
|
||||
|
||||
render() {
|
||||
const { datasource, query } = this.props;
|
||||
const { id, name } = datasource;
|
||||
const { text } = this.state;
|
||||
|
||||
const selected = query.data ? options[0] : options[1];
|
||||
return (
|
||||
<div>
|
||||
<div className="gf-form">
|
||||
<FormLabel width={4}>Data</FormLabel>
|
||||
<Select width={6} options={options} value={selected} onChange={this.onSourceChange} />
|
||||
|
||||
<div className="btn btn-link">
|
||||
{query.data ? (
|
||||
datasource.getDescription(query.data)
|
||||
) : (
|
||||
<a href={`datasources/edit/${id}/`}>
|
||||
{name}: {datasource.getDescription(datasource.data)}
|
||||
<i className="fa fa-pencil-square-o" />
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{query.data && <TableInputCSV text={text} onSeriesParsed={this.onSeriesParsed} width={'100%'} height={200} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default InputQueryEditor;
|
3
public/app/plugins/datasource/input/README.md
Normal file
3
public/app/plugins/datasource/input/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Table Datasource - Native Plugin
|
||||
|
||||
This datasource lets you define results directly in CSV. The values are stored either in a shared datasource, or directly in panels.
|
34
public/app/plugins/datasource/input/datasource.test.ts
Normal file
34
public/app/plugins/datasource/input/datasource.test.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import InputDatasource from './datasource';
|
||||
import { InputQuery } from './types';
|
||||
import { readCSV, DataSourceInstanceSettings, PluginMeta } from '@grafana/ui';
|
||||
import { getQueryOptions } from 'test/helpers/getQueryOptions';
|
||||
|
||||
describe('InputDatasource', () => {
|
||||
const data = readCSV('a,b,c\n1,2,3\n4,5,6');
|
||||
const instanceSettings: DataSourceInstanceSettings = {
|
||||
id: 1,
|
||||
type: 'x',
|
||||
name: 'xxx',
|
||||
meta: {} as PluginMeta,
|
||||
jsonData: {
|
||||
data,
|
||||
},
|
||||
};
|
||||
|
||||
describe('when querying', () => {
|
||||
test('should return the saved data with a query', () => {
|
||||
const ds = new InputDatasource(instanceSettings);
|
||||
const options = getQueryOptions<InputQuery>({
|
||||
targets: [{ refId: 'Z' }],
|
||||
});
|
||||
|
||||
return ds.query(options).then(rsp => {
|
||||
expect(rsp.data.length).toBe(1);
|
||||
|
||||
const series = rsp.data[0];
|
||||
expect(series.refId).toBe('Z');
|
||||
expect(series.rows).toEqual(data[0].rows);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
110
public/app/plugins/datasource/input/datasource.ts
Normal file
110
public/app/plugins/datasource/input/datasource.ts
Normal file
@ -0,0 +1,110 @@
|
||||
// Types
|
||||
import {
|
||||
DataQueryOptions,
|
||||
SeriesData,
|
||||
DataQueryResponse,
|
||||
DataSourceApi,
|
||||
DataSourceInstanceSettings,
|
||||
} from '@grafana/ui/src/types';
|
||||
import { InputQuery } from './types';
|
||||
|
||||
export class InputDatasource implements DataSourceApi<InputQuery> {
|
||||
data: SeriesData[];
|
||||
|
||||
// Filled in by grafana plugin system
|
||||
name?: string;
|
||||
|
||||
// Filled in by grafana plugin system
|
||||
id?: number;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(instanceSettings: DataSourceInstanceSettings) {
|
||||
if (instanceSettings.jsonData) {
|
||||
this.data = instanceSettings.jsonData.data;
|
||||
}
|
||||
|
||||
if (!this.data) {
|
||||
this.data = [];
|
||||
}
|
||||
}
|
||||
|
||||
getDescription(data: SeriesData[]): string {
|
||||
if (!data) {
|
||||
return '';
|
||||
}
|
||||
if (data.length > 1) {
|
||||
const count = data.reduce((acc, series) => {
|
||||
return acc + series.rows.length;
|
||||
}, 0);
|
||||
return `${data.length} Series, ${count} Rows`;
|
||||
}
|
||||
const series = data[0];
|
||||
return `${series.fields.length} Fields, ${series.rows.length} Rows`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a query to a simple text string
|
||||
*/
|
||||
getQueryDisplayText(query: InputQuery): string {
|
||||
if (query.data) {
|
||||
return 'Panel Data: ' + this.getDescription(query.data);
|
||||
}
|
||||
return `Shared Data From: ${this.name} (${this.getDescription(this.data)})`;
|
||||
}
|
||||
|
||||
metricFindQuery(query: string, options?: any) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const names = [];
|
||||
for (const series of this.data) {
|
||||
for (const field of series.fields) {
|
||||
// TODO, match query/options?
|
||||
names.push({
|
||||
text: field.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
resolve(names);
|
||||
});
|
||||
}
|
||||
|
||||
query(options: DataQueryOptions<InputQuery>): Promise<DataQueryResponse> {
|
||||
const results: SeriesData[] = [];
|
||||
for (const query of options.targets) {
|
||||
if (query.hide) {
|
||||
continue;
|
||||
}
|
||||
const data = query.data ? query.data : this.data;
|
||||
for (const series of data) {
|
||||
results.push({
|
||||
refId: query.refId,
|
||||
...series,
|
||||
});
|
||||
}
|
||||
}
|
||||
return Promise.resolve({ data: results });
|
||||
}
|
||||
|
||||
testDatasource() {
|
||||
return new Promise((resolve, reject) => {
|
||||
let rowCount = 0;
|
||||
let info = `${this.data.length} Series:`;
|
||||
for (const series of this.data) {
|
||||
info += ` [${series.fields.length} Fields, ${series.rows.length} Rows]`;
|
||||
rowCount += series.rows.length;
|
||||
}
|
||||
|
||||
if (rowCount > 0) {
|
||||
resolve({
|
||||
status: 'success',
|
||||
message: info,
|
||||
});
|
||||
}
|
||||
reject({
|
||||
status: 'error',
|
||||
message: 'No Data Entered',
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default InputDatasource;
|
14
public/app/plugins/datasource/input/img/input.svg
Normal file
14
public/app/plugins/datasource/input/img/input.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Svg Vector Icons : http://www.onlinewebfonts.com/icon -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve">
|
||||
<defs>
|
||||
<style>.cls-1{fill:url(#linear-gradient);}</style>
|
||||
<linearGradient id="linear-gradient" x1="50" y1="101.02" x2="50" y2="4.05" gradientTransform="matrix(1, 0, 0, -1, 0, 102)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#70b0df"/>
|
||||
<stop offset="0.5" stop-color="#1b81c5"/>
|
||||
<stop offset="1" stop-color="#4a98ce"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g><path class="cls-1" d="M889.5,814.1h-201v50.2H701c20.8,0,37.7,16.9,37.7,37.7c0,20.8-16.9,37.7-37.7,37.7H600.5c-20.8,0-37.7-16.9-37.7-37.7c0-20.8,16.9-37.7,37.7-37.7h12.6v-50.2H110.5C55,814.1,10,769.1,10,713.6V286.5c0-55.5,45-100.5,100.5-100.5h502.6v-50.3h-12.6c-20.8,0-37.7-16.9-37.7-37.7c0-20.8,16.9-37.7,37.7-37.7H701c20.8,0,37.7,16.9,37.7,37.7c0,20.8-16.9,37.7-37.7,37.7h-12.6v50.3h201c55.5,0,100.5,45,100.5,100.5v427.2C990,769.1,945,814.1,889.5,814.1z M562.8,738.8h50.3V261.3h-50.3 M688.5,261.3v477.5h50.3V261.3H688.5z"/></g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
@ -0,0 +1,45 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import coreModule from 'app/core/core_module';
|
||||
|
||||
import { TableInputCSV, SeriesData, toCSV } from '@grafana/ui';
|
||||
|
||||
interface Props {
|
||||
data: SeriesData[];
|
||||
onParsed: (data: SeriesData[]) => void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
data: SeriesData[];
|
||||
text: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Angular wrapper around TableInputCSV
|
||||
*/
|
||||
class Wraper extends Component<Props, State> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
text: toCSV(props.data),
|
||||
data: props.data,
|
||||
};
|
||||
}
|
||||
|
||||
onSeriesParsed = (data: SeriesData[], text: string) => {
|
||||
this.setState({ data, text });
|
||||
this.props.onParsed(data);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { text } = this.state;
|
||||
return <TableInputCSV text={text} onSeriesParsed={this.onSeriesParsed} width={'100%'} height={300} />;
|
||||
}
|
||||
}
|
||||
|
||||
coreModule.directive('csvInput', [
|
||||
'reactDirective',
|
||||
reactDirective => {
|
||||
return reactDirective(Wraper, ['data', 'onParsed']);
|
||||
},
|
||||
]);
|
@ -0,0 +1,21 @@
|
||||
import { SeriesData } from '@grafana/ui';
|
||||
|
||||
// Loads the angular wrapping directive
|
||||
import './CSVInputWrapper';
|
||||
|
||||
export class TableConfigCtrl {
|
||||
static templateUrl = 'legacy/config.html';
|
||||
|
||||
current: any; // the Current Configuration (set by the plugin infra)
|
||||
|
||||
/** @ngInject */
|
||||
constructor($scope: any, $injector: any) {
|
||||
console.log('TableConfigCtrl Init', this);
|
||||
}
|
||||
|
||||
onParsed = (data: SeriesData[]) => {
|
||||
this.current.jsonData.data = data;
|
||||
};
|
||||
}
|
||||
|
||||
export default TableConfigCtrl;
|
16
public/app/plugins/datasource/input/legacy/config.html
Normal file
16
public/app/plugins/datasource/input/legacy/config.html
Normal file
@ -0,0 +1,16 @@
|
||||
<div class="gf-form-group">
|
||||
<h4>Shared Data:</h4>
|
||||
<span>Enter CSV</span>
|
||||
<csv-input
|
||||
data="ctrl.current.jsonData.data"
|
||||
onParsed="ctrl.onParsed"
|
||||
></csv-input>
|
||||
</div>
|
||||
|
||||
<div class="grafana-info-box">
|
||||
This data is stored in the datasource json and is returned to every user
|
||||
in the initial request for any datasource. This is an appropriate place
|
||||
to enter a few values. Large datasets will perform better in other datasources.
|
||||
<br/><br/>
|
||||
<b>NOTE:</b> Changes to this data will only be reflected after a browser refresh.
|
||||
</div>
|
6
public/app/plugins/datasource/input/module.ts
Normal file
6
public/app/plugins/datasource/input/module.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import Datasource from './datasource';
|
||||
|
||||
import InputQueryEditor from './InputQueryEditor';
|
||||
import InputConfigCtrl from './legacy/InputConfigCtrl';
|
||||
|
||||
export { Datasource, InputQueryEditor as QueryEditor, InputConfigCtrl as ConfigCtrl };
|
24
public/app/plugins/datasource/input/plugin.json
Normal file
24
public/app/plugins/datasource/input/plugin.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"type": "datasource",
|
||||
"name": "Direct Input",
|
||||
"id": "input",
|
||||
"state": "alpha",
|
||||
|
||||
"metrics": true,
|
||||
"alerting": false,
|
||||
"annotations": false,
|
||||
"logs": false,
|
||||
"explore": false,
|
||||
|
||||
"info": {
|
||||
"description": "User Input Data Source for Grafana",
|
||||
"author": {
|
||||
"name": "Grafana Project",
|
||||
"url": "https://grafana.com"
|
||||
},
|
||||
"logos": {
|
||||
"small": "img/input.svg",
|
||||
"large": "img/input.svg"
|
||||
}
|
||||
}
|
||||
}
|
6
public/app/plugins/datasource/input/types.ts
Normal file
6
public/app/plugins/datasource/input/types.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { DataQuery, SeriesData } from '@grafana/ui/src/types';
|
||||
|
||||
export interface InputQuery extends DataQuery {
|
||||
// Data saved in the panel
|
||||
data?: SeriesData[];
|
||||
}
|
@ -1,19 +1,21 @@
|
||||
import angular from 'angular';
|
||||
import _ from 'lodash';
|
||||
|
||||
class MixedDatasource {
|
||||
/** @ngInject */
|
||||
constructor(private $q, private datasourceSrv) {}
|
||||
import { DataSourceApi, DataQuery, DataQueryOptions } from '@grafana/ui';
|
||||
import DatasourceSrv from 'app/features/plugins/datasource_srv';
|
||||
|
||||
query(options) {
|
||||
class MixedDatasource implements DataSourceApi<DataQuery> {
|
||||
/** @ngInject */
|
||||
constructor(private datasourceSrv: DatasourceSrv) {}
|
||||
|
||||
query(options: DataQueryOptions<DataQuery>) {
|
||||
const sets = _.groupBy(options.targets, 'datasource');
|
||||
const promises = _.map(sets, targets => {
|
||||
const promises = _.map(sets, (targets: DataQuery[]) => {
|
||||
const dsName = targets[0].datasource;
|
||||
if (dsName === '-- Mixed --') {
|
||||
return this.$q([]);
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
const filtered = _.filter(targets, t => {
|
||||
const filtered = _.filter(targets, (t: DataQuery) => {
|
||||
return !t.hide;
|
||||
});
|
||||
|
||||
@ -22,16 +24,20 @@ class MixedDatasource {
|
||||
}
|
||||
|
||||
return this.datasourceSrv.get(dsName).then(ds => {
|
||||
const opt = angular.copy(options);
|
||||
const opt = _.cloneDeep(options);
|
||||
opt.targets = filtered;
|
||||
return ds.query(opt);
|
||||
});
|
||||
});
|
||||
|
||||
return this.$q.all(promises).then(results => {
|
||||
return Promise.all(promises).then(results => {
|
||||
return { data: _.flatten(_.map(results, 'data')) };
|
||||
});
|
||||
}
|
||||
|
||||
testDatasource() {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
}
|
||||
|
||||
export { MixedDatasource, MixedDatasource as Datasource };
|
||||
|
@ -3,8 +3,8 @@
|
||||
echo -e "Collecting code stats (typescript errors & more)"
|
||||
|
||||
ERROR_COUNT_LIMIT=6816
|
||||
DIRECTIVES_LIMIT=173
|
||||
CONTROLLERS_LIMIT=137
|
||||
DIRECTIVES_LIMIT=175
|
||||
CONTROLLERS_LIMIT=138
|
||||
|
||||
ERROR_COUNT="$(./node_modules/.bin/tsc --project tsconfig.json --noEmit --noImplicitAny true | grep -oP 'Found \K(\d+)')"
|
||||
DIRECTIVES="$(grep -r -o directive public/app/**/* | wc -l)"
|
||||
|
Loading…
Reference in New Issue
Block a user