ruby-grafana-reporter 0.4.4 → 0.4.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7553ba3b91efd75d9e435a4c4d8148670b3337e953e2b39f13ae63ebf2f5fbb2
4
- data.tar.gz: 696ea8b584bf017553f0b1fc9849870e78495f7257e7ea40671107c10254f66d
3
+ metadata.gz: cd6bdf1a9856b3c0a663060c4b2ecfc3a094ae230635807398a611d38d74ac90
4
+ data.tar.gz: 95ac2750e962a67b0d3b71184b5409b88441840f4b08f31284b33f20b0d862a3
5
5
  SHA512:
6
- metadata.gz: d10fd4f00099b92b843dc02a418487bae5de21363253212b2a5cc52794c6c11e361e300decef92524b5513e963c19eb11a9aaf82977aff4664be5a9da61ebc51
7
- data.tar.gz: 85bb38c275136bc1f0318e9af15beacd98a1d7d3819c5e43db97b8df01abf8dc75b556618bb012eba4ead25c0b4bdef1baf80e5067cdf87307bf977331f61411
6
+ metadata.gz: 53698b98b33afef1706243cf5c322bd1a9968ec723e18d32ec5d550769117fa9c64c43c9d174752630ac7f8e8015d1394d4f2060fbe6be1b2ce73d7ad8390801
7
+ data.tar.gz: 5f39c12e056e689ff442f00c175219e6a560e9e38362a5aec9f67dc738b172ebf0b9d288958e312020f8304603e54ff1c4e21ca44d77182342a3df9fe03825ba
data/README.md CHANGED
@@ -1,337 +1,336 @@
1
- [![MIT License](https://img.shields.io/github/license/divinity666/ruby-grafana-reporter.svg?style=flat-square)](https://github.com/divinity666/ruby-grafana-reporter/blob/master/LICENSE)
2
- [![Build Status](https://travis-ci.com/divinity666/ruby-grafana-reporter.svg?branch=master)](https://travis-ci.com/github/divinity666/ruby-grafana-reporter?branch=master)
3
- [![Coverage Status](https://coveralls.io/repos/github/divinity666/ruby-grafana-reporter/badge.svg?branch=master)](https://coveralls.io/github/divinity666/ruby-grafana-reporter?branch=master)
4
- [![Gem Version](https://badge.fury.io/rb/ruby-grafana-reporter.svg)](https://badge.fury.io/rb/ruby-grafana-reporter)
5
-
6
- # Ruby Grafana Reporter
7
- Reporting Service for Grafana
8
-
9
- ## Table of Contents
10
-
11
- * [About the project](#about-the-project)
12
- * [Features](#features)
13
- * [Supported datasources](#supported-datasources)
14
- * [Quick Start](#quick-start)
15
- * [Setup](#setup)
16
- * [Grafana integration](#grafana-integration)
17
- * [Advanced information](#advanced-information)
18
- * [Webservice](#webservice)
19
- * [Using ERB templates](#using-erb-templates)
20
- * [Using webhooks](#using-webhooks)
21
- * [Developing your own plugin](#developing-your-own-plugin)
22
- * [Roadmap](#roadmap)
23
- * [Donations](#donations)
24
-
25
- ## About the project
26
-
27
- [Grafana](https://github.com/grafana/grafana) is a great tool for monitoring and
28
- visualizing data from different sources. Anyway the free version is lacking a
29
- professional reporting functionality. And this is, where the ruby grafana reporter
30
- steps in.
31
-
32
- The key functionality of the reporter is to capture data and images from grafana
33
- dashboards and to use it in your custom reports to finally create reports in PDF,
34
- HTML, or any other format.
35
-
36
- By default (an extended version of) Asciidoctor is enabled as template language.
37
-
38
- ## Features
39
-
40
- * Supports creation of reports for multiple [grafana](https://github.com/grafana/grafana)
41
- dashboards (and also multiple grafana installations!) in one resulting report
42
- * PDF (default), HTML and many other report formats are supported
43
- * Easy-to-use configuration wizard, including fully automated functionality to create a
44
- demo report for your dashboard
45
- * Include dynamic content from grafana (find here a reference for all
46
- [asciidcotor reporter calls](FUNCTION_CALLS.md)):
47
- * panels as images
48
- * tables based on grafana panel queries or custom database queries (no images!)
49
- * single values to be integrated in text, based on grafana panel queries or custom
50
- database queries
51
- * Runs as
52
- * webservice to be called directly from grafana
53
- * standalone command line tool, e.g. to be automated with `cron` or `bash` scrips
54
- * microservice from standard asciidoctor docker container without any dependencies
55
- * Supports webhook callbacks on before, on cancel and on finishing a report (see
56
- configuration file)
57
- * Solid as a rock, also in case of template errors and whatever else may happen
58
- * Full [API documentation](https://rubydoc.info/gems/ruby-grafana-reporter) available
59
-
60
- ## Supported datasources
61
-
62
- Functionalities are provided as shown here:
63
-
64
- Database | Image rendering | Raw queries | Composed queries
65
- ------------------------- | :-------------: | :-----------: | :------------:
66
- all SQL based datasources | supported | supported | supported
67
- Graphite | supported | supported | supported
68
- InfluxDB | supported | supported | not (yet) supported
69
- Prometheus | supported | supported | n/a in grafana
70
- other datasources | supported | not supported | not supported
71
-
72
- The characteristics of a raw query are, that the query is either specified manually in
73
- the panel specification or in the calling template.
74
-
75
- Composed queries are all kinds of query, where the grafana UI feature (aka visual editor
76
- mode) for query specifications are used. In this case grafana is translating the UI query
77
- specification to a raw query, which then in fact is sent to the database.
78
-
79
- ## Quick Start
80
-
81
-
82
- ### Setup
83
-
84
- You don't have a grafana setup runnning already? No worries, just configure
85
- `https://play.grafana.org` in the configuration wizard and see the magic
86
- happen!
87
-
88
- If your grafana setup requires a login, you'll have to setup an api key for
89
- the reporter. Please follow the steps
90
- [described here](https://github.com/divinity666/ruby-grafana-reporter/issues/2#issuecomment-811836757)
91
- first.
92
-
93
- **Windows:**
94
-
95
- * [Download latest Windows executable](https://github.com/divinity666/ruby-grafana-reporter/releases/latest)
96
- * `ruby-grafana-reporter -w`
97
-
98
- **Raspberry Pi:**
99
-
100
- * `sudo apt-get install ruby`
101
- * `gem install ruby-grafana-reporter`
102
- * `ruby-grafana-reporter -w`
103
-
104
- **Ruby environment:**
105
-
106
- * `gem install ruby-grafana-reporter`
107
- * `ruby-grafana-reporter -w`
108
-
109
- **Docker environment** (advanced users):
110
-
111
- * [Download latest single-rb file](https://github.com/divinity666/ruby-grafana-reporter/releases/latest)
112
- to an empty folder
113
- * create a configuration file by calling `ruby ruby-grafana-reporter -w` (if in doubt,
114
- run the command within your docker container)
115
- * create file `/<<path-to-single-rb-file-folder>>/startup.sh` with the following
116
- content:
117
-
118
- ```
119
- cd /documents
120
- ruby bin/ruby-grafana-reporter
121
- ```
122
- * add the startup script to your asciidoctor section in your docker-compose.yaml:
123
-
124
- ```
125
- asciidoctor:
126
- image: asciidoctor/docker-asciidoctor
127
- container_name: asciidoctor
128
- hostname: asciidoctor
129
- volumes:
130
- - /<<path-to-single-rb-file-folder>>:/documents
131
- command:
132
- sh /documents/startup.sh
133
- restart: unless-stopped
134
- ```
135
- * start/restart the asciidoctor docker container
136
-
137
- ### Grafana integration
138
-
139
- For using the reporter directly from grafana, you need to simply add a link to your
140
- grafana dashboard:
141
-
142
- * Open the dashboard configuration
143
- * Select `Links`
144
- * Select `Add`
145
- * Fill out as following:
146
- * Type: `link`
147
- * Url: `http://<<your-server-url>>:<<your-webservice-port>>/render?var-template=demo_report`
148
- * Title: `Demo Report`
149
- * Select `Time range`
150
- * Select `Variable values`
151
- * Select `Add`
152
-
153
- Now go back to your dashboard and click the newly generated `Demo Report`
154
- link on it. Now the renderer should start it's task and show you the expected
155
- results.
156
-
157
- Please note, that the reporter won't automatically refresh your screen to update
158
- the progress. Simply hit `F5` to refresh your browser. After the report has been
159
- successfully built, it will show the PDF after the next refresh automatically.
160
-
161
- You want to select a template in grafana, which shall then be rendered?
162
- Piece of cake: Just add a dashboard variable to your grafana dashboard named
163
- `template` and let the user select or enter a template name. To make use of it,
164
- you should change the link of the `Demo Report` link to
165
- `http://<<your-server-url>>:<<your-webservice-port>>/render?`. On
166
- hitting the new link in the dashboard, grafana will add the selected template as
167
- a variable and forward it to the reporter.
168
-
169
- ## Advanced information
170
-
171
- ### Webservice
172
-
173
- Running the reporter as a webservice provides the following URLs
174
-
175
- /overview - for all running or retained renderings
176
- /render - for rendering a template, 'var-template' is the only mandatory GET parameter, all parameters will be passed to the report templates as attributes
177
- /view_report - for viewing the status or receving the result of a specific rendering, is automatically called after a successfull /render call
178
- /cancel_report - for cancelling the rendering of a specific report, normally not called manually, but on user interaction in the /view_report or /overview URL
179
-
180
- The main endpoint to call for report generation is configured in the previous chapter [Grafana integration](#grafana-integration).
181
-
182
- However, if you would like to see, currently running report generations and previously generated reports, you may want to call the endpoint `/overview`.
183
-
184
- ### Using ERB templates
185
-
186
- By default the configuration wizard will setup the reporter with the asciidoctor
187
- template language enabled. For several reasons, you may want to take advantage of
188
- the ruby included
189
- [ERB template language](https://docs.ruby-lang.org/en/master/ERB.html).
190
-
191
- Anyway you should consider, that ERB templates can include harmful code. So make
192
- sure, that you will only use ERB templates in a safe environment.
193
-
194
- To enable the ERB template language, you need to modify your configuration file
195
- in the section `grafana-reporter`:
196
-
197
- ````
198
- grafana-reporter:
199
- report-class: GrafanaReporter::ERB::Report
200
- ````
201
-
202
- Restart the grafana reporter instance, if running as webservice. That's all.
203
-
204
- In ERB templates, you have access to the variables `report`, which is a reference
205
- to the currently executed
206
- [ERB Report object](https://rubydoc.info/gems/ruby-grafana-reporter/GrafanaReporter/ERB/Report)
207
- and `attributes`, which contains a hash
208
- of variables, which have been handed over to the report generations, e.g. from
209
- a webservice call.
210
-
211
- To test the configuration, you may want to run the configuration wizard again,
212
- which will create an ERB template for you.
213
-
214
- ### Using webhooks
215
-
216
- Webhooks provide an easy way to get automatically informed about the progress
217
- of a report. The nice thing is, that this is completely independent from
218
- running the reporter as webservice, i.e. these callbacks are also called if you
219
- run the reporter standalone.
220
-
221
- To use webhooks, you have to specify, in which progress states of a report you
222
- are interested. Therefore you have to configure it in the `grafana-reporter`
223
- section of your configuration file, e.g.
224
-
225
- ````
226
- grafana-reporter:
227
- callbacks:
228
- all:
229
- - http://<<your_callback_url>>
230
- ````
231
-
232
- Remember to restart the reporter, if it is running as a webservice.
233
-
234
- After having done so, your callback url will be called for each event with
235
- a JSON body including all necessary information of the report. For details see
236
- [callback](https://rubydoc.info/gems/ruby-grafana-reporter/GrafanaReporter/ReportWebhook#callback-instance_method).
237
-
238
- ### Developing your own plugin
239
-
240
- The reporter is designed to allow easy integration of your own plugins,
241
- without having to modify the reporter base source on github (or anywhere
242
- else). This section shows how to implement and load a custom datasource.
243
-
244
- Implementing a custom datasource is needed, if you use a custom datasource
245
- grafana plugin, which is not yet supported by the reporter. In that case you
246
- can build your own custom datasource for the reporter and load it on demand
247
- with a command line parameter, without having to build your own fork of this
248
- project.
249
-
250
- This documentation will provide a simple, but mocked implementation of an
251
- imagined grafana datasource.
252
-
253
- First of all, let's create a new text file, e.g. `my_datasource.rb` with the
254
- following content:
255
-
256
- ````
257
- class MyDatasource < ::Grafana::AbstractDatasource
258
- def self.handles?(model)
259
- tmp = new(model)
260
- tmp.type == 'my_datasource'
261
- end
262
-
263
- def request(query_description)
264
- # see https://rubydoc.info/gems/ruby-grafana-reporter/Grafana/AbstractDatasource#request-instance_method
265
- # for detailed information of given parameters and expected return format
266
-
267
- # TODO: call your datasource, e.g. via REST call
268
- # TODO: return the value in the needed format
269
- end
270
-
271
- def raw_query_from_panel_model(panel_query_target)
272
- # TODO: extract or build the query from the given grafana panel query target hash
273
- end
274
-
275
- def default_variable_format
276
- # TODO, specify the default variable format
277
- # see https://rubydoc.info/gems/ruby-grafana-reporter/Grafana/Variable#value_formatted-instance_method
278
- # for detailed information.
279
- end
280
- end
281
- ````
282
-
283
- The only thing left to do now, is to make this datasource known to the
284
- reporter. This can be done with the `-r` command line flag, e.g.
285
-
286
- ````
287
- ruby-grafana-reporter -r my_datasource.rb
288
- ````
289
-
290
- The reporter implemented some magic, to automatically register datasource
291
- implementations on load, if they inherit from `::Grafana::AbstractDatasource`.
292
- This means, that you don't have to do anything else here.
293
-
294
- Now the reporter knows about your datasource implementation and will use it,
295
- if you request information from a panel, which is linked to the type
296
- `my_datasource` as specified in the `handles?` method above. If any errors
297
- occur during execution, the reporter will catch them and show them in the error
298
- log.
299
-
300
- Registering a custom ruby file is independent from running the reporter as a
301
- webservice or as a standalone executable. In any case the reporter will apply
302
- the file.
303
-
304
- Technically, loading your own plugin will call require for your ruby file,
305
- _after_ all reporter files have been loaded and _before_ the execution of the
306
- webservice or a rendering process starts.
307
-
308
- ## Roadmap
309
-
310
- This is just a collection of things, I am heading for in future, without a schedule.
311
-
312
- * Support grafana internal datasources
313
- * Solve code TODOs
314
- * Become [rubocop](https://rubocop.org/) ready
315
-
316
- ## Contributing
317
-
318
- If you'd like to contribute, please fork the repository and use a feature
319
- branch. Pull requests are warmly welcome.
320
-
321
- ## Licensing
322
-
323
- The code in this project is licensed under MIT license.
324
-
325
- ## Acknowledgements
326
- * [asciidoctor](https://github.com/asciidoctor/asciidoctor)
327
- * [asciidoctor-pdf](https://github.com/asciidoctor/asciidoctor-pdf)
328
- * [grafana](https://github.com/grafana/grafana)
329
-
330
- Inspired by [Izak Marai's grafana reporter](https://github.com/IzakMarais/reporter)
331
-
332
- ## Donations
333
-
334
- If this project saves you as much time as I hope it does, and if you'd like to
335
- support my work, feel free donate. :)
336
-
337
- [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/donate?hosted_button_id=35LH6JNLPHPHQ)
1
+ [![MIT License](https://img.shields.io/github/license/divinity666/ruby-grafana-reporter.svg?style=flat-square)](https://github.com/divinity666/ruby-grafana-reporter/blob/master/LICENSE)
2
+ [![Build Status](https://travis-ci.com/divinity666/ruby-grafana-reporter.svg?branch=master)](https://travis-ci.com/github/divinity666/ruby-grafana-reporter?branch=master)
3
+ [![Coverage Status](https://coveralls.io/repos/github/divinity666/ruby-grafana-reporter/badge.svg?branch=master)](https://coveralls.io/github/divinity666/ruby-grafana-reporter?branch=master)
4
+ [![Gem Version](https://badge.fury.io/rb/ruby-grafana-reporter.svg)](https://badge.fury.io/rb/ruby-grafana-reporter)
5
+
6
+ # Ruby Grafana Reporter
7
+ Reporting Service for Grafana
8
+
9
+ ## Table of Contents
10
+
11
+ * [About the project](#about-the-project)
12
+ * [Features](#features)
13
+ * [Supported datasources](#supported-datasources)
14
+ * [Quick Start](#quick-start)
15
+ * [Setup](#setup)
16
+ * [Grafana integration](#grafana-integration)
17
+ * [Advanced information](#advanced-information)
18
+ * [Webservice](#webservice)
19
+ * [Using ERB templates](#using-erb-templates)
20
+ * [Using webhooks](#using-webhooks)
21
+ * [Developing your own plugin](#developing-your-own-plugin)
22
+ * [Roadmap](#roadmap)
23
+ * [Donations](#donations)
24
+
25
+ ## About the project
26
+
27
+ [Grafana](https://github.com/grafana/grafana) is a great tool for monitoring and
28
+ visualizing data from different sources. Anyway the free version is lacking a
29
+ professional reporting functionality. And this is, where the ruby grafana reporter
30
+ steps in.
31
+
32
+ The key functionality of the reporter is to capture data and images from grafana
33
+ dashboards and to use it in your custom templates to finally create reports in PDF
34
+ (default), HTML, or any other format.
35
+
36
+ By default (an extended version of) Asciidoctor is enabled as template language.
37
+
38
+ ## Features
39
+
40
+ * Supports creation of reports for multiple [grafana](https://github.com/grafana/grafana)
41
+ dashboards (and also multiple grafana installations!) in one resulting report
42
+ * PDF (default), HTML and many other report formats are supported
43
+ * Easy-to-use configuration wizard, including fully automated functionality to create a
44
+ demo report for your dashboard
45
+ * Include dynamic content from grafana (find here a reference for all
46
+ [asciidcotor reporter calls](FUNCTION_CALLS.md)):
47
+ * panels as images
48
+ * tables based on grafana panel queries or custom database queries (no images!)
49
+ * single values to be integrated in text, based on grafana panel queries or custom
50
+ database queries
51
+ * Runs as
52
+ * webservice to be called directly from grafana
53
+ * standalone command line tool, e.g. to be automated with `cron` or `bash` scrips
54
+ * microservice from standard asciidoctor docker container without any dependencies
55
+ * Supports webhook callbacks on before, on cancel and on finishing a report (see
56
+ configuration file)
57
+ * Solid as a rock, also in case of template errors and whatever else may happen
58
+ * Full [API documentation](https://rubydoc.info/gems/ruby-grafana-reporter) available
59
+
60
+ ## Supported datasources
61
+
62
+ Functionalities are provided as shown here:
63
+
64
+ Database | Image rendering | Raw queries | Composed queries
65
+ ------------------------- | :-------------: | :-----------: | :------------:
66
+ all SQL based datasources | supported | supported | supported
67
+ Graphite | supported | supported | supported
68
+ InfluxDB | supported | supported | not (yet) supported
69
+ Prometheus | supported | supported | n/a in grafana
70
+ other datasources | supported | not supported | not supported
71
+
72
+ The characteristics of a raw query are, that the query is either specified manually in
73
+ the panel specification or in the calling template.
74
+
75
+ Composed queries are all kinds of query, where the grafana UI feature (aka visual editor
76
+ mode) for query specifications are used. In this case grafana is translating the UI query
77
+ specification to a raw query, which then in fact is sent to the database.
78
+
79
+ ## Quick Start
80
+
81
+
82
+ ### Setup
83
+
84
+ You don't have a grafana setup runnning already? No worries, just configure
85
+ `https://play.grafana.org` in the configuration wizard and see the magic
86
+ happen!
87
+
88
+ If your grafana setup requires a login, you'll have to setup an api key for
89
+ the reporter. Please follow the steps
90
+ [described here](https://github.com/divinity666/ruby-grafana-reporter/issues/2#issuecomment-811836757)
91
+ first.
92
+
93
+ **Windows:**
94
+
95
+ * [Download latest Windows executable](https://github.com/divinity666/ruby-grafana-reporter/releases/latest)
96
+ * `ruby-grafana-reporter -w`
97
+
98
+ **Raspberry Pi:**
99
+
100
+ * `sudo apt-get install ruby`
101
+ * `gem install ruby-grafana-reporter`
102
+ * `ruby-grafana-reporter -w`
103
+
104
+ **Ruby environment:**
105
+
106
+ * `gem install ruby-grafana-reporter`
107
+ * `ruby-grafana-reporter -w`
108
+
109
+ **Docker environment** (advanced users):
110
+
111
+ * [Download latest single-rb file](https://github.com/divinity666/ruby-grafana-reporter/releases/latest)
112
+ to an empty folder
113
+ * create a configuration file by calling `ruby ruby-grafana-reporter -w` (if in doubt,
114
+ run the command within your docker container)
115
+ * create file `/<<path-to-single-rb-file-folder>>/startup.sh` with the following
116
+ content:
117
+
118
+ ```
119
+ cd /documents
120
+ ruby bin/ruby-grafana-reporter
121
+ ```
122
+ * add the startup script to your asciidoctor section in your docker-compose.yaml:
123
+
124
+ ```
125
+ asciidoctor:
126
+ image: asciidoctor/docker-asciidoctor
127
+ container_name: asciidoctor
128
+ hostname: asciidoctor
129
+ volumes:
130
+ - /<<path-to-single-rb-file-folder>>:/documents
131
+ command:
132
+ sh /documents/startup.sh
133
+ restart: unless-stopped
134
+ ```
135
+ * start/restart the asciidoctor docker container
136
+
137
+ ### Grafana integration
138
+
139
+ For using the reporter directly from grafana, you need to simply add a link to your
140
+ grafana dashboard:
141
+
142
+ * Open the dashboard configuration
143
+ * Select `Links`
144
+ * Select `Add`
145
+ * Fill out as following:
146
+ * Type: `link`
147
+ * Url: `http://<<your-server-url>>:<<your-webservice-port>>/render?var-template=demo_report`
148
+ * Title: `Demo Report`
149
+ * Select `Time range`
150
+ * Select `Variable values`
151
+ * Select `Add`
152
+
153
+ Now go back to your dashboard and click the newly generated `Demo Report`
154
+ link on it. Now the renderer should start it's task and show you the expected
155
+ results.
156
+
157
+ Please note, that the reporter won't automatically refresh your screen to update
158
+ the progress. Simply hit `F5` to refresh your browser. After the report has been
159
+ successfully built, it will show the PDF after the next refresh automatically.
160
+
161
+ You want to select a template in grafana, which shall then be rendered?
162
+ Piece of cake: Just add a dashboard variable to your grafana dashboard named
163
+ `template` and let the user select or enter a template name. To make use of it,
164
+ you should change the link of the `Demo Report` link to
165
+ `http://<<your-server-url>>:<<your-webservice-port>>/render?`. On
166
+ hitting the new link in the dashboard, grafana will add the selected template as
167
+ a variable and forward it to the reporter.
168
+
169
+ ## Advanced information
170
+
171
+ ### Webservice
172
+
173
+ Running the reporter as a webservice provides the following URLs
174
+
175
+ /overview - for all running or retained renderings
176
+ /render - for rendering a template, 'var-template' is the only mandatory GET parameter, all parameters will be passed to the report templates as attributes
177
+ /view_report - for viewing the status or receving the result of a specific rendering, is automatically called after a successfull /render call
178
+ /cancel_report - for cancelling the rendering of a specific report, normally not called manually, but on user interaction in the /view_report or /overview URL
179
+
180
+ The main endpoint to call for report generation is configured in the previous chapter [Grafana integration](#grafana-integration).
181
+
182
+ However, if you would like to see, currently running report generations and previously generated reports, you may want to call the endpoint `/overview`.
183
+
184
+ ### Using ERB templates
185
+
186
+ By default the configuration wizard will setup the reporter with the asciidoctor
187
+ template language enabled. For several reasons, you may want to take advantage of
188
+ the ruby included
189
+ [ERB template language](https://docs.ruby-lang.org/en/master/ERB.html).
190
+
191
+ Anyway you should consider, that ERB templates can include harmful code. So make
192
+ sure, that you will only use ERB templates in a safe environment.
193
+
194
+ To enable the ERB template language, you need to modify your configuration file
195
+ in the section `grafana-reporter`:
196
+
197
+ ````
198
+ grafana-reporter:
199
+ report-class: GrafanaReporter::ERB::Report
200
+ ````
201
+
202
+ Restart the grafana reporter instance, if running as webservice. That's all.
203
+
204
+ In ERB templates, you have access to the variables `report`, which is a reference
205
+ to the currently executed
206
+ [ERB Report object](https://rubydoc.info/gems/ruby-grafana-reporter/GrafanaReporter/ERB/Report)
207
+ and `attributes`, which contains a hash
208
+ of variables, which have been handed over to the report generations, e.g. from
209
+ a webservice call.
210
+
211
+ To test the configuration, you may want to run the configuration wizard again,
212
+ which will create an ERB template for you.
213
+
214
+ ### Using webhooks
215
+
216
+ Webhooks provide an easy way to get automatically informed about the progress
217
+ of a report. The nice thing is, that this is completely independent from
218
+ running the reporter as webservice, i.e. these callbacks are also called if you
219
+ run the reporter standalone.
220
+
221
+ To use webhooks, you have to specify, in which progress states of a report you
222
+ are interested. Therefore you have to configure it in the `grafana-reporter`
223
+ section of your configuration file, e.g.
224
+
225
+ ````
226
+ grafana-reporter:
227
+ callbacks:
228
+ all:
229
+ - http://<<your_callback_url>>
230
+ ````
231
+
232
+ Remember to restart the reporter, if it is running as a webservice.
233
+
234
+ After having done so, your callback url will be called for each event with
235
+ a JSON body including all necessary information of the report. For details see
236
+ [callback](https://rubydoc.info/gems/ruby-grafana-reporter/GrafanaReporter/ReportWebhook#callback-instance_method).
237
+
238
+ ### Developing your own plugin
239
+
240
+ The reporter is designed to allow easy integration of your own plugins,
241
+ without having to modify the reporter base source on github (or anywhere
242
+ else). This section shows how to implement and load a custom datasource.
243
+
244
+ Implementing a custom datasource is needed, if you use a custom datasource
245
+ grafana plugin, which is not yet supported by the reporter. In that case you
246
+ can build your own custom datasource for the reporter and load it on demand
247
+ with a command line parameter, without having to build your own fork of this
248
+ project.
249
+
250
+ This documentation will provide a simple, but mocked implementation of an
251
+ imagined grafana datasource.
252
+
253
+ First of all, let's create a new text file, e.g. `my_datasource.rb` with the
254
+ following content:
255
+
256
+ ````
257
+ class MyDatasource < ::Grafana::AbstractDatasource
258
+ def self.handles?(model)
259
+ tmp = new(model)
260
+ tmp.type == 'my_datasource'
261
+ end
262
+
263
+ def request(query_description)
264
+ # see https://rubydoc.info/gems/ruby-grafana-reporter/Grafana/AbstractDatasource#request-instance_method
265
+ # for detailed information of given parameters and expected return format
266
+
267
+ # TODO: call your datasource, e.g. via REST call
268
+ # TODO: return the value in the needed format
269
+ end
270
+
271
+ def raw_query_from_panel_model(panel_query_target)
272
+ # TODO: extract or build the query from the given grafana panel query target hash
273
+ end
274
+
275
+ def default_variable_format
276
+ # TODO, specify the default variable format
277
+ # see https://rubydoc.info/gems/ruby-grafana-reporter/Grafana/Variable#value_formatted-instance_method
278
+ # for detailed information.
279
+ end
280
+ end
281
+ ````
282
+
283
+ The only thing left to do now, is to make this datasource known to the
284
+ reporter. This can be done with the `-r` command line flag, e.g.
285
+
286
+ ````
287
+ ruby-grafana-reporter -r my_datasource.rb
288
+ ````
289
+
290
+ The reporter implemented some magic, to automatically register datasource
291
+ implementations on load, if they inherit from `::Grafana::AbstractDatasource`.
292
+ This means, that you don't have to do anything else here.
293
+
294
+ Now the reporter knows about your datasource implementation and will use it,
295
+ if you request information from a panel, which is linked to the type
296
+ `my_datasource` as specified in the `handles?` method above. If any errors
297
+ occur during execution, the reporter will catch them and show them in the error
298
+ log.
299
+
300
+ Registering a custom ruby file is independent from running the reporter as a
301
+ webservice or as a standalone executable. In any case the reporter will apply
302
+ the file.
303
+
304
+ Technically, loading your own plugin will call require for your ruby file,
305
+ _after_ all reporter files have been loaded and _before_ the execution of the
306
+ webservice or a rendering process starts.
307
+
308
+ ## Roadmap
309
+
310
+ This is just a collection of things, I am heading for in future, without a schedule.
311
+
312
+ * Support grafana internal datasources
313
+ * Solve code TODOs
314
+ * Become [rubocop](https://rubocop.org/) ready
315
+
316
+ ## Contributing
317
+
318
+ If you'd like to contribute, please fork the repository and use a feature
319
+ branch. Pull requests are warmly welcome.
320
+
321
+ ## Licensing
322
+
323
+ The code in this project is licensed under MIT license.
324
+
325
+ ## Acknowledgements
326
+ * [asciidoctor](https://github.com/asciidoctor/asciidoctor)
327
+ * [asciidoctor-pdf](https://github.com/asciidoctor/asciidoctor-pdf)
328
+ * [grafana](https://github.com/grafana/grafana)
329
+
330
+ Inspired by [Izak Marai's grafana reporter](https://github.com/IzakMarais/reporter)
331
+
332
+ ## Donations
333
+
334
+ If you like this project and you would like to support my work, feel free to donate. :)
335
+
336
+ [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/donate?hosted_button_id=35LH6JNLPHPHQ)
data/lib/VERSION.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Version information
4
- GRAFANA_REPORTER_VERSION = [0, 4, 4].freeze
4
+ GRAFANA_REPORTER_VERSION = [0, 4, 5].freeze
5
5
  # Release date
6
- GRAFANA_REPORTER_RELEASE_DATE = '2021-07-12'
6
+ GRAFANA_REPORTER_RELEASE_DATE = '2021-08-26'
@@ -70,12 +70,4 @@ module Grafana
70
70
  super("The datasource query provided, does not look like a grafana datasource target (received: #{query}).")
71
71
  end
72
72
  end
73
-
74
- # Raised if a datasource implementation cannot handle a query, which is composed
75
- # in the grafana visual editor.
76
- class ComposedQueryNotSupportedError < GrafanaError
77
- def initialize(class_obj)
78
- super("Composed queries are not yet supported for datasource '#{class_obj}'.")
79
- end
80
- end
81
73
  end
@@ -50,7 +50,7 @@ module Grafana
50
50
  # @return [Datasource] Datasource for the specified datasource name
51
51
  def datasource_by_name(datasource_name)
52
52
  datasource_name = 'default' if datasource_name.to_s.empty?
53
- # TODO: add support for grafana builtin datasource types
53
+ # TODO: PRIO add support for grafana builtin datasource types
54
54
  return UnsupportedDatasource.new(nil) if datasource_name.to_s =~ /-- (?:Mixed|Dashboard|Grafana) --/
55
55
  raise DatasourceDoesNotExistError.new('name', datasource_name) unless @datasources[datasource_name]
56
56
 
@@ -15,7 +15,21 @@ module Grafana
15
15
  def request(query_description)
16
16
  raise MissingSqlQueryError if query_description[:raw_query].nil?
17
17
 
18
- url = "/api/datasources/proxy/#{id}/query?db=#{@model['database']}&q=#{ERB::Util.url_encode(query_description[:raw_query])}&epoch=ms"
18
+ # replace variables
19
+ query = replace_variables(query_description[:raw_query], query_description[:variables])
20
+
21
+ # Unfortunately the grafana internal variables are not replaced in the grafana backend, but in the
22
+ # frontend, i.e. we have to replace them here manually
23
+ # replace $timeFilter variable
24
+ query = query.gsub(/\$timeFilter(?=\W|$)/, "time >= #{query_description[:from]}ms and time <= #{query_description[:to]}ms")
25
+
26
+ # replace grafana variables $__interval and $__interval_ms in query
27
+ # TODO: influx datasource currently uses a fixed values of 1000 width for interval variables specified in a query - it should be possible to calculate this according to grafana
28
+ # TODO: check where calculation and replacement of interval variable should take place
29
+ query = query.gsub(/\$(?:__)?interval(?=\W|$)/, "#{((query_description[:to].to_i - query_description[:from].to_i) / 1000 / 1000).to_i}s")
30
+ query = query.gsub(/\$(?:__)?interval_ms(?=\W|$)/, "#{((query_description[:to].to_i - query_description[:from].to_i) / 1000).to_i}")
31
+
32
+ url = "/api/datasources/proxy/#{id}/query?db=#{@model['database']}&q=#{ERB::Util.url_encode(query)}&epoch=ms"
19
33
 
20
34
  webrequest = query_description[:prepared_request]
21
35
  webrequest.relative_url = url
@@ -29,8 +43,8 @@ module Grafana
29
43
  def raw_query_from_panel_model(panel_query_target)
30
44
  return panel_query_target['query'] if panel_query_target['rawQuery']
31
45
 
32
- # TODO: support composed queries
33
- raise ComposedQueryNotSupportedError, self
46
+ # build composed queries
47
+ build_select(panel_query_target['select']) + build_from(panel_query_target) + build_where(panel_query_target['tags']) + build_group_by(panel_query_target['groupBy'])
34
48
  end
35
49
 
36
50
  # @see AbstractDatasource#default_variable_format
@@ -40,10 +54,82 @@ module Grafana
40
54
 
41
55
  private
42
56
 
57
+ def build_group_by(stmt)
58
+ groups = []
59
+ fill = ""
60
+
61
+ stmt.each do |group|
62
+ case group['type']
63
+ when 'tag'
64
+ groups << "\"#{group['params'].first}\""
65
+
66
+ when 'fill'
67
+ fill = " fill(#{group['params'].first})"
68
+
69
+ else
70
+ groups << "#{group['type']}(#{group['params'].join(', ')})"
71
+
72
+ end
73
+ end
74
+
75
+ " GROUP BY #{groups.join(', ')}#{fill}"
76
+ end
77
+
78
+ def build_where(stmt)
79
+ custom_where = []
80
+
81
+ stmt.each do |where|
82
+ value = where['operator'] =~ /^[=!]~$/ ? where['value'] : "'#{where['value']}'"
83
+ custom_where << "\"#{where['key']}\" #{where['operator']} #{value}"
84
+ end
85
+
86
+ " WHERE #{"(#{custom_where.join(' AND ')}) AND " unless custom_where.empty?}$timeFilter"
87
+ end
88
+
89
+ def build_from(stmt)
90
+ " FROM \"#{"stmt['policy']." unless stmt['policy'] == 'default'}#{stmt['measurement']}\""
91
+ end
92
+
93
+ def build_select(stmt)
94
+ res = "SELECT"
95
+ parts = []
96
+
97
+ stmt.each do |value|
98
+ part = ""
99
+
100
+ value.each do |item|
101
+ case item['type']
102
+ when 'field'
103
+ # frame field parameter as string
104
+ part = "\"#{item['params'].first}\""
105
+
106
+ when 'alias'
107
+ # append AS with parameter as string
108
+ part = "#{part} AS \"#{item['params'].first}\""
109
+
110
+
111
+ when 'math'
112
+ # append parameter as raw value for calculation
113
+ part = "#{part} #{item['params'].first}"
114
+
115
+
116
+ else
117
+ # frame current part by brackets and call by item function including parameters
118
+ part = "#{item['type']}(#{part}#{", #{item['params'].join(', ')}" unless item['params'].empty?})"
119
+ end
120
+ end
121
+
122
+ parts << part
123
+ end
124
+
125
+ "#{res} #{parts.join(', ')}"
126
+ end
127
+
43
128
  # @see AbstractDatasource#preformat_response
44
129
  def preformat_response(response_body)
45
130
  # TODO: how to handle multiple query results?
46
131
  json = JSON.parse(response_body)['results'].first['series']
132
+ return {} if json.nil?
47
133
 
48
134
  header = ['time']
49
135
  content = {}
@@ -14,7 +14,11 @@ module Grafana
14
14
  def request(query_description)
15
15
  raise MissingSqlQueryError if query_description[:raw_query].nil?
16
16
 
17
- url = "/api/datasources/proxy/#{id}/api/v1/query_range?"\
17
+ # TODO: properly allow endpoint to be set - also check raw_query method
18
+ end_point = @endpoint ? @endpoint : "query_range"
19
+
20
+ # TODO: set query option 'step' on request
21
+ url = "/api/datasources/proxy/#{id}/api/v1/#{end_point}?"\
18
22
  "start=#{query_description[:from]}&end=#{query_description[:to]}"\
19
23
  "&query=#{replace_variables(query_description[:raw_query], query_description[:variables])}"
20
24
 
@@ -28,6 +32,7 @@ module Grafana
28
32
 
29
33
  # @see AbstractDatasource#raw_query_from_panel_model
30
34
  def raw_query_from_panel_model(panel_query_target)
35
+ @endpoint = panel_query_target['format'] == 'time_series' && (panel_query_target['instant'] == false || !panel_query_target['instant']) ? 'query_range' : 'query'
31
36
  panel_query_target['expr']
32
37
  end
33
38
 
@@ -47,7 +52,11 @@ module Grafana
47
52
 
48
53
  # keep sorting, if json has only one target item, otherwise merge results and return
49
54
  # as a time sorted array
50
- return { header: headers << json.first['metric']['mode'], content: json.first['values'] } if json.length == 1
55
+ # TODO properly set headlines
56
+ if json.length == 1
57
+ return { header: headers << json.first['metric'].to_s, content: [[json.first['value'][1], json.first['value'][0]]] } if json.first.has_key?('value') # this happens for the special case of calls to '/query' endpoint
58
+ return { header: headers << json.first['metric']['mode'], content: json.first['values'] }
59
+ end
51
60
 
52
61
  # TODO: show warning if results may be sorted different
53
62
  json.each_index do |i|
@@ -33,6 +33,7 @@ module Grafana
33
33
  if config_or_value.is_a? Hash
34
34
  @config = config_or_value
35
35
  @name = @config['name']
36
+ # TODO: if a variable uses type 'query' which is never updated, the selected values are stored in 'options'
36
37
  unless @config['current'].nil?
37
38
  @raw_value = @config['current']['value']
38
39
  @text = @config['current']['text']
@@ -62,7 +63,8 @@ module Grafana
62
63
  def value_formatted(format = '')
63
64
  value = @raw_value
64
65
 
65
- # handle value 'All' properly
66
+ # if 'All' is selected for this template variable, capture all values properly
67
+ # (from grafana config or query) and format the results afterwards accordingly
66
68
  if value == '$__all'
67
69
  if !@config['options'].empty?
68
70
  # this query contains predefined values, so capture them and format the values accordingly
@@ -147,7 +149,7 @@ module Grafana
147
149
  value.gsub(%r{[" |=/\\]}, '\\\\\0')
148
150
 
149
151
  when /^date(?::(?<format>.*))?$/
150
- # TODO: grafana does not seem to allow multivariables with date format - so properly handle here as well
152
+ # TODO: grafana does not seem to allow multiselection of variables with date format - raise an error if this happens anyway
151
153
  get_date_formatted(value, Regexp.last_match(1))
152
154
 
153
155
  when ''
@@ -13,7 +13,7 @@ module GrafanaReporter
13
13
  attr_reader :variables, :result, :panel, :dashboard
14
14
 
15
15
  def timeout
16
- # TODO: check where value priorities should be evaluated
16
+ # TODO: PRIO check where value priorities should be evaluated
17
17
  return @variables['timeout'].raw_value if @variables['timeout']
18
18
  return @variables['grafana_default_timeout'].raw_value if @variables['grafana_default_timeout']
19
19
 
@@ -307,13 +307,13 @@ module GrafanaReporter
307
307
 
308
308
  if opts[:table_formatter].raw_value == 'adoc_deprecated'
309
309
  @grafana.logger.warn("You are using deprecated 'table_formatter' named 'adoc_deprecated', which will be "\
310
- "removed in a future version. Start using 'adoc' or register your own implementation "\
311
- "of AbstractTableFormatStrategy.")
310
+ "removed in a future version. Start using 'adoc_plain' or register your own "\
311
+ "implementation of AbstractTableFormatStrategy.")
312
312
  return result[:content].map do |row|
313
313
  opts[:row_divider].raw_value + row.map do |item|
314
314
  item.to_s.gsub('|', '\\|')
315
315
  end.join(opts[:column_divider].raw_value)
316
- end
316
+ end.join("\n")
317
317
  end
318
318
 
319
319
  AbstractTableFormatStrategy.get(opts[:table_formatter].raw_value).format(result, opts[:include_headline].raw_value.downcase == 'true')
@@ -349,7 +349,7 @@ module GrafanaReporter
349
349
  raise TimeRangeUnknownError, orig_date unless date_spec
350
350
 
351
351
  date = DateTime.parse(report_time.raw_value)
352
- # TODO: allow from_translated or similar in ADOC template
352
+ # TODO: PRIO allow from_translated or similar in ADOC template
353
353
  date = date.new_offset(timezone.raw_value) if timezone
354
354
 
355
355
  until date_spec.empty?
@@ -71,7 +71,7 @@ module GrafanaReporter
71
71
  query.raw_query = defaults.merge(selected_attrs.each_with_object({}) { |(k, v), h| h[k] = v })
72
72
 
73
73
  begin
74
- reader.unshift_lines query.execute
74
+ reader.unshift_lines query.execute.split("\n")
75
75
  rescue GrafanaReporterError => e
76
76
  @report.logger.error(e.message)
77
77
  reader.unshift_line "|#{e.message}"
@@ -70,7 +70,7 @@ module GrafanaReporter
70
70
  query.raw_query = defaults.merge(selected_attrs.each_with_object({}) { |(k, v), h| h[k] = v })
71
71
 
72
72
  begin
73
- reader.unshift_lines query.execute
73
+ reader.unshift_lines query.execute.split("\n")
74
74
  rescue GrafanaReporterError => e
75
75
  @report.logger.error(e.message)
76
76
  reader.unshift_line "|#{e.message}"
@@ -51,7 +51,7 @@ module GrafanaReporter
51
51
 
52
52
  image = query.execute
53
53
  image_path = @report.save_image_file(image)
54
- rescue GrafanaError => e
54
+ rescue Grafana::GrafanaError => e
55
55
  @report.logger.error(e.message)
56
56
  return create_paragraph(parent, e.message, attrs)
57
57
  rescue GrafanaReporterError => e
@@ -53,7 +53,7 @@ module GrafanaReporter
53
53
 
54
54
  image = query.execute
55
55
  image_path = @report.save_image_file(image)
56
- rescue GrafanaError => e
56
+ rescue Grafana::GrafanaError => e
57
57
  @report.logger.error(e.message)
58
58
  return create_inline(parent, :quoted, e.message)
59
59
  rescue GrafanaReporterError => e
@@ -43,7 +43,7 @@ module GrafanaReporter
43
43
  query.raw_query = { property_name: attrs[:field] }
44
44
 
45
45
  description = query.execute
46
- rescue GrafanaError => e
46
+ rescue Grafana::GrafanaError => e
47
47
  @report.logger.error(e.message)
48
48
  return create_inline(parent, :quoted, e.message)
49
49
  rescue GrafanaReporterError => e
@@ -62,8 +62,8 @@ module GrafanaReporter
62
62
  vars = { 'table_formatter' => 'adoc_plain' }.merge(build_attribute_hash(doc.attributes, attrs))
63
63
  query = QueryValueQuery.new(panel, variables: vars)
64
64
 
65
- reader.unshift_lines query.execute
66
- rescue GrafanaError => e
65
+ reader.unshift_lines query.execute.split("\n")
66
+ rescue Grafana::GrafanaError => e
67
67
  @report.logger.error(e.message)
68
68
  reader.unshift_line "|#{e.message}"
69
69
  rescue GrafanaReporterError => e
@@ -59,7 +59,7 @@ module GrafanaReporter
59
59
  query = QueryValueQuery.new(panel, variables: build_attribute_hash(parent.document.attributes, attrs))
60
60
 
61
61
  create_inline(parent, :quoted, query.execute)
62
- rescue GrafanaError => e
62
+ rescue Grafana::GrafanaError => e
63
63
  @report.logger.error(e.message)
64
64
  create_inline(parent, :quoted, e.message)
65
65
  rescue GrafanaReporterError => e
@@ -56,8 +56,8 @@ module GrafanaReporter
56
56
  query.datasource = @report.grafana(instance).datasource_by_id(target.split(':')[1].to_i)
57
57
  query.raw_query = attrs['sql']
58
58
 
59
- reader.unshift_lines query.execute
60
- rescue GrafanaError => e
59
+ reader.unshift_lines query.execute.split("\n")
60
+ rescue Grafana::GrafanaError => e
61
61
  @report.logger.error(e.message)
62
62
  reader.unshift_line "|#{e.message}"
63
63
  rescue GrafanaReporterError => e
@@ -55,7 +55,7 @@ module GrafanaReporter
55
55
  query.raw_query = attrs['sql']
56
56
 
57
57
  create_inline(parent, :quoted, query.execute)
58
- rescue GrafanaError => e
58
+ rescue Grafana::GrafanaError => e
59
59
  @report.logger.error(e.message)
60
60
  create_inline(parent, :quoted, e.message)
61
61
  rescue GrafanaReporterError => e
@@ -5,6 +5,7 @@ module GrafanaReporter
5
5
  class PanelImageQuery < AbstractQuery
6
6
  # Sets the proper render variables.
7
7
  def pre_process
8
+ # TODO: properly show error, if a (maybe a repeated template) panel can not be rendered
8
9
  # TODO: ensure that in case of timezones are specified, that they are also forwarded to the image renderer
9
10
  # rename "render-" variables
10
11
  @variables = @variables.each_with_object({}) { |(k, v), h| h[k.gsub(/^render-/, '')] = v }
@@ -18,6 +18,17 @@ require 'asciidoctor-pdf'
18
18
  require 'zip'
19
19
  require_relative 'VERSION'
20
20
 
21
+ # TODO: add test to see if datasource default formats are applied
22
+ # TODO: add test for render-height and render-width, as they are not forwarded
23
+ # TODO: show convert-backend, template language and all variables in webservice overview
24
+ # TODO: add automated test against grafana playground before building a new release
25
+ # TODO: allow registration of files to be defined in config file
26
+ # TODO: PRIO: check in cloud - variables fetched from queries are replaced with SQL query instead of the resolved values, which can lead to issues, e.g. when using that in a function as SUBSTRING
27
+ # TODO: PRIO: allow multiple report classes in configuration file including possibility to decide for the individual class for each rendering call
28
+ # TODO: add configuration example to README
29
+ # TODO: find a way, how to automatically update test grafana responses with _real_ grafana responses
30
+ # TODO: append necessary variables on demo report creation for plain SQL queries, as they are lacking the grafana reference
31
+
21
32
  folders = [
22
33
  %w[grafana],
23
34
  %w[grafana_reporter logger],
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-grafana-reporter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.4
4
+ version: 0.4.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christian Kohlmeyer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-12 00:00:00.000000000 Z
11
+ date: 2021-08-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: asciidoctor