Skip to content

Commit 1b4e641

Browse files
authored
Merge pull request #3 from exaco/unlimit_labels
Unlimit labels
2 parents 72ab470 + ac90974 commit 1b4e641

File tree

3 files changed

+226
-33
lines changed

3 files changed

+226
-33
lines changed

README.md

Lines changed: 119 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,83 @@ for example, if you are using Grafana with containers, add:
3636

3737
You REST API application should return data in the following format:
3838

39-
### Fetch Graph
39+
> Note: You API application should handle CORS policy. Otherwise you will face CORS-Policy error in Grafana.
4040
41-
This route returns the graph which is intended to visualize.
41+
### Fetch Graph Fields
4242

43-
endpoint: `/api/fetchgraph`
43+
This route returns the nodes and edges fields defined in the [parameter tables](https://grafana.com/docs/grafana/latest/visualizations/node-graph/#data-api).
44+
This would help the plugin to create desired parameters for the graph.
45+
For nodes, `id` and for edges, `id`, `source` and `target` fields are required and the other fields are optional.
46+
47+
endpoint: `/api/graph/fields`
48+
49+
method: `GET`
50+
51+
content type: `application/json`
52+
53+
content format example:
54+
55+
```json
56+
{
57+
"edges_fields": [
58+
{
59+
"field_name": "id",
60+
"type": "string"
61+
},
62+
{
63+
"field_name": "source",
64+
"type": "string"
65+
},
66+
{
67+
"field_name": "target",
68+
"type": "string"
69+
},
70+
{
71+
"field_name": "mainStat",
72+
"type": "number"
73+
}
74+
],
75+
"nodes_fields": [
76+
{
77+
"field_name": "id",
78+
"type": "string"
79+
},
80+
{
81+
"field_name": "title",
82+
"type": "string"
83+
},
84+
{
85+
"field_name": "mainStat",
86+
"type": "string"
87+
},
88+
{
89+
"field_name": "secondaryStat",
90+
"type": "number"
91+
},
92+
{
93+
"color": "red",
94+
"field_name": "arc__failed",
95+
"type": "number"
96+
},
97+
{
98+
"color": "green",
99+
"field_name": "arc__passed",
100+
"type": "number"
101+
},
102+
{
103+
"displayName": "Role",
104+
"field_name": "detail__role",
105+
"type": "string"
106+
}
107+
]
108+
}
109+
```
110+
111+
### Fetch Graph Data
112+
113+
This route returns the graph data which is intended to visualize.
114+
115+
endpoint: `/api/graph/data`
44116

45117
method: `GET`
46118

@@ -49,7 +121,34 @@ content type: `application/json`
49121
Data Format example:
50122

51123
```json
52-
{"edges":[{"id":"1","mainStat":"53/s","source":"1","target":"2"}],"nodes":[{"arc__failed":0.7,"arc__passed":0.3,"detail__zone":"load","id":"1","subTitle":"instance:#2","title":"Service1"},{"arc__failed":0.5,"arc__passed":0.5,"detail__zone":"transform","id":"2","subTitle":"instance:#3","title":"Service2"}]}
124+
{
125+
"edges": [
126+
{
127+
"id": "1",
128+
"mainStat": "53/s",
129+
"source": "1",
130+
"target": "2"
131+
}
132+
],
133+
"nodes": [
134+
{
135+
"arc__failed": 0.7,
136+
"arc__passed": 0.3,
137+
"detail__zone": "load",
138+
"id": "1",
139+
"subTitle": "instance:#2",
140+
"title": "Service1"
141+
},
142+
{
143+
"arc__failed": 0.5,
144+
"arc__passed": 0.5,
145+
"detail__zone": "transform",
146+
"id": "2",
147+
"subTitle": "instance:#3",
148+
"title": "Service2"
149+
}
150+
]
151+
}
53152
```
54153

55154
For more detail of the variables please visit [here](https://grafana.com/docs/grafana/latest/visualizations/node-graph/#data-api).
@@ -65,6 +164,22 @@ method: `GET`
65164

66165
success status code: `200`
67166

167+
## API Example
168+
169+
In `example` folder you can find a simple API application in Python Flask.
170+
171+
### Requirements:
172+
173+
- flask
174+
- flask-cors
175+
176+
### Run
177+
178+
```bash
179+
python run.py
180+
```
181+
The application will be started on `http://localhost:5000`
182+
68183
## Compiling the data source by yourself
69184

70185
1. Install dependencies

example/api/python/run.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from flask import Flask, jsonify
2+
from flask_cors import CORS
3+
app = Flask(__name__)
4+
CORS(app)
5+
6+
7+
@app.route('/api/graph/fields')
8+
def fetch_graph_fields():
9+
nodes_fields = [{"field_name": "id", "type": "string"},
10+
{"field_name": "title", "type": "string",
11+
},
12+
{"field_name": "subTitle", "type": "string"},
13+
{"field_name": "mainStat", "type": "string"},
14+
{"field_name": "secondaryStat", "type": "number"},
15+
{"field_name": "arc__failed",
16+
"type": "number", "color": "red"},
17+
{"field_name": "arc__passed",
18+
"type": "number", "color": "green"},
19+
{"field_name": "detail__role",
20+
"type": "string", "displayName": "Role"}]
21+
edges_fields = [
22+
{"field_name": "id", "type": "string"},
23+
{"field_name": "source", "type": "string"},
24+
{"field_name": "target", "type": "string"},
25+
{"field_name": "mainStat", "type": "number"},
26+
]
27+
result = {"nodes_fields": nodes_fields,
28+
"edges_fields": edges_fields}
29+
return jsonify(result)
30+
31+
32+
@app.route('/api/graph/data')
33+
def fetch_graph_data():
34+
35+
nodes = [{"id": "1", "title": "Service1", "subTitle": "instance:#2", "detail__role": "load",
36+
"arc__failed": 0.7, "arc__passed": 0.3, "mainStat": "qaz"},
37+
{"id": "2", "title": "Service2", "subTitle": "instance:#2", "detail__role": "transform",
38+
"arc__failed": 0.5, "arc__passed": 0.5, "mainStat": "qaz"},
39+
{"id": "3", "title": "Service3", "subTitle": "instance:#3", "detail__role": "extract",
40+
"arc__failed": 0.3, "arc__passed": 0.7, "mainStat": "qaz"},
41+
{"id": "4", "title": "Service3", "subTitle": "instance:#1", "detail__role": "transform",
42+
"arc__failed": 0.5, "arc__passed": 0.5, "mainStat": "qaz"},
43+
{"id": "5", "title": "Service4", "subTitle": "instance:#5", "detail__role": "transform",
44+
"arc__failed": 0.5, "arc__passed": 0.5, "mainStat": "qaz"}]
45+
edges = [{"id": "1", "source": "1", "target": "2", "mainStat": 53},
46+
{"id": "2", "source": "2", "target": "3", "mainStat": 53},
47+
{"id": "2", "source": "1", "target": "4", "mainStat": 5},
48+
{"id": "3", "source": "3", "target": "5", "mainStat": 70},
49+
{"id": "4", "source": "2", "target": "5", "mainStat": 100}]
50+
result = {"nodes": nodes, "edges": edges}
51+
return jsonify(result)
52+
53+
54+
@app.route('/api/health')
55+
def check_health():
56+
return "API is working well!"
57+
58+
59+
app.run(host='0.0.0.0', port=5000)

src/datasource.ts

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { getBackendSrv } from '@grafana/runtime';
1515
import { MyQuery, MyDataSourceOptions, defaultQuery } from './types';
1616

1717
export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
18-
baseUrl: string;
18+
baseUrl: string; // base url of the api
1919
constructor(instanceSettings: DataSourceInstanceSettings<MyDataSourceOptions>) {
2020
super(instanceSettings);
2121

@@ -25,44 +25,63 @@ export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
2525
async query(options: DataQueryRequest<MyQuery>): Promise<DataQueryResponse> {
2626
const promises = options.targets.map(async target => {
2727
const query = defaults(target, defaultQuery);
28-
const response = await this.doRequest('/api/fetchgraph', `query=${query.queryText}`);
28+
// fetch graph fields from api
29+
const responseGraphFields = await this.doRequest('/api/graph/fields', `query=${query.queryText}`);
30+
// fetch graph data from api
31+
const responseGraphData = await this.doRequest('/api/graph/data', `query=${query.queryText}`);
32+
// extract fields of the nodes and edges in the graph fields object
33+
const nodeFieldsResponse = responseGraphFields.data.nodes_fields;
34+
const edgeFieldsResponse = responseGraphFields.data.edges_fields;
35+
// Define an interface for types of the FrameField
36+
interface FrameFieldType {
37+
name: string;
38+
type: any;
39+
config?: any;
40+
}
41+
// This function gets the fields of the api and transforms them to what grafana dataframe prefers
42+
function fieldAssignator(FieldsResponse: any): FrameFieldType[] {
43+
var outputFields: FrameFieldType[] = [];
44+
FieldsResponse.forEach((field: any) => {
45+
// fieldType can be either number of string
46+
var fieldType = field['type'] === 'number' ? FieldType.number : FieldType.string;
47+
// add 'name' and 'type' items to the output object
48+
var outputField: FrameFieldType = { name: field['field_name'], type: fieldType };
49+
// add color for 'arc__*' items(only apperas for the nodes)
50+
if ('color' in field) {
51+
outputField.config = { color: { fixedColor: field['color'], mode: FieldColorModeId.Fixed } };
52+
}
53+
// add disPlayName for 'detail__*' items
54+
if ('displayName' in field) {
55+
outputField.config = { displayName: field['displayName'] };
56+
}
57+
outputFields.push(outputField);
58+
});
59+
return outputFields;
60+
}
61+
// Extract node fields
62+
const nodeFields: FrameFieldType[] = fieldAssignator(nodeFieldsResponse);
63+
// Create nodes dataframe
2964
const nodeFrame = new MutableDataFrame({
3065
name: 'Nodes',
3166
refId: query.refId,
32-
fields: [
33-
{ name: 'id', type: FieldType.string },
34-
{ name: 'title', type: FieldType.string },
35-
{ name: 'subTitle', type: FieldType.string },
36-
{ name: 'detail__role', type: FieldType.string },
37-
{
38-
name: 'arc__failed',
39-
type: FieldType.number,
40-
config: { color: { fixedColor: 'red', mode: FieldColorModeId.Fixed } },
41-
},
42-
{
43-
name: 'arc__passed',
44-
type: FieldType.number,
45-
config: { color: { fixedColor: 'green', mode: FieldColorModeId.Fixed } },
46-
},
47-
],
67+
fields: nodeFields,
4868
});
49-
69+
// Extract edge fields
70+
const edgeFields: FrameFieldType[] = fieldAssignator(edgeFieldsResponse);
71+
// Create Edges dataframe
5072
const edgeFrame = new MutableDataFrame({
5173
name: 'Edges',
5274
refId: query.refId,
53-
fields: [
54-
{ name: 'id', type: FieldType.string },
55-
{ name: 'source', type: FieldType.string },
56-
{ name: 'target', type: FieldType.string },
57-
{ name: 'mainStat', type: FieldType.string },
58-
// { name: 'secondaryStat', type: FieldType.number },
59-
],
75+
fields: edgeFields,
6076
});
61-
const nodes = response.data.nodes;
62-
const edges = response.data.edges;
77+
// Extract graph data of the related api response
78+
const nodes = responseGraphData.data.nodes;
79+
const edges = responseGraphData.data.edges;
80+
// add nodes to the node dataframe
6381
nodes.forEach((node: any) => {
6482
nodeFrame.add(node);
6583
});
84+
// add edges to the edges dataframe
6685
edges.forEach((edge: any) => {
6786
edgeFrame.add(edge);
6887
});
@@ -86,7 +105,7 @@ export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
86105
async testDatasource() {
87106
const defaultErrorMessage = 'Cannot connect to API';
88107
try {
89-
const response = await this.doRequest('/health');
108+
const response = await this.doRequest('/api/health');
90109
if (response.status === 200) {
91110
return {
92111
status: 'success',

0 commit comments

Comments
 (0)