ruby-grafana-reporter 0.4.4 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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