sentry_top_errors 0.1.0 → 0.1.2

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: 451faf0b74e71cc7d9ab082b35427881ce43d99d1908246fb4d2a111a16fe7bc
4
- data.tar.gz: 9684d29db554a2fbd8a660124d1cef85b2500afc6037d1bc8a4234b46a894831
3
+ metadata.gz: 036da0a8225db87b61c37b236428dc72bc20a426fdc3cb83920a74ad96788bbd
4
+ data.tar.gz: b98afd0bb566f7958344171264e2363d66886f86dec1a8f24bf3c10d4c00d2a3
5
5
  SHA512:
6
- metadata.gz: b3dc5a3f0d23d95570d8f6627cdbec58da0a4213ca5c14085a775be7ba2c55154e6d1581ebf8dc67fb18df141c07b582051d2ff08e8c2952df4a39751f9078f8
7
- data.tar.gz: 8f9eb1cf3e1170c894ac8360a30cc0a636865bc5627e51e6e97d79ce4f7ef1ec131203a727b623785130ceea36f9a92528cacd992729bb60297f4f5c5236c039
6
+ metadata.gz: 351fe1c8cba7d350cd864cc5cd036b39fb91851c425dfdf407268c3031e267edb20a3e5d73adb9d94c88da9b9417d5351b7dbb6933cde09c50617dab7cb63030
7
+ data.tar.gz: ba898a868a3a3882708a13a44a1e2c35e59adfe4e4d8b791f8684f77b578df1888c260123a49f7df6243556867576bbf36f97b503a336fe4f952a6e2749a0dce
data/.gitignore CHANGED
@@ -1,4 +1,5 @@
1
1
  sentry_cache
2
- index.html
2
+ /index.html
3
3
  Gemfile.lock
4
4
  try_sentry_checker.rb
5
+ sentry_top_errors-*.gem
@@ -24,4 +24,4 @@ end
24
24
 
25
25
  reporter = SentryTopErrors::Reporter.new(client: client, report_type: :html)
26
26
  reporter.fetch_data
27
- puts r.generate_report
27
+ puts reporter.generate_report
@@ -1,5 +1,3 @@
1
- require 'progress_bar'
2
-
3
1
  class SentryTopErrors::Reporter
4
2
  def initialize(client:, prod_regex: /^p-/, threshold_24h: 30, threshold_new: 10, new_days_num: 1, report_type: :html)
5
3
  @client = client
@@ -139,8 +137,12 @@ class SentryTopErrors::Reporter
139
137
  elsif @report_type == :html
140
138
  tpl_dir = File.join(File.expand_path(File.dirname(__FILE__) + "/../.."), "templates")
141
139
  html_content = File.read(tpl_dir + "/index.html")
140
+ project_orgs = @projects.map {|pr| pr['organization']['name'] }.uniq.join(", ")
141
+
142
142
  html_content.sub!(%{['ALL_ISSUE_COUNTS_PLACEHOLDER']}, JSON.pretty_generate(issue_counts))
143
143
  html_content.sub!(%{['NEW_ISSUE_COUNTS_PLACEHOLDER']}, JSON.pretty_generate(new_issues))
144
+ html_content.sub!("REPORT_GENERATE_DATE", Time.now.strftime("%F %T %Z"))
145
+ html_content.sub!("REPORT_SENTRY_ORG", project_orgs)
144
146
 
145
147
  puts "Saved index.html"
146
148
  File.write("./index.html", html_content)
@@ -2,6 +2,7 @@ require 'excon'
2
2
  require 'json'
3
3
  require 'yaml'
4
4
  require 'time'
5
+ require 'progress_bar'
5
6
 
6
7
  module SentryTopErrors
7
8
  end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "sentry_top_errors"
3
- s.version = "0.1.0"
3
+ s.version = "0.1.2"
4
4
  s.summary = "Generate top errors report for sentry"
5
5
  s.description = ""
6
6
  s.author = "Pavel Evstigneev"
@@ -0,0 +1,360 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Sentry Errors</title>
7
+
8
+ <style>
9
+ body {
10
+ background: linear-gradient(0deg, rgba(252,217,182,1) 0%, rgba(160,206,219,1) 100%);
11
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
12
+ font-size: 14px;
13
+ }
14
+
15
+ .page-content {
16
+ height: 100%;
17
+ min-height: 400px;
18
+ width: 92%;
19
+ max-width: 1200px;
20
+ margin: 30px auto;
21
+ background: #fff;
22
+ padding: 25px;
23
+ border-radius: 12px;
24
+ opacity: 0.95;
25
+ }
26
+
27
+ h1 {
28
+ font-size: 1.5em;
29
+ background: #9d4f4f;
30
+ margin: -25px -25px 35px -25px;
31
+ padding: 12px 25px;
32
+ border-radius: 12px 12px 0 0;
33
+ color: white;
34
+ }
35
+
36
+ h1 a {
37
+ color: white;
38
+ text-decoration: none;
39
+ }
40
+
41
+ table.rows-table {
42
+ margin: 20px 0;
43
+ border-spacing: 0px;
44
+ border-collapse: collapse;
45
+ }
46
+
47
+ table.rows-table td {
48
+ vertical-align: top;
49
+ }
50
+
51
+ table.rows-table td a {
52
+ color: #000;
53
+ }
54
+
55
+ table.rows-table td, table.rows-table th {
56
+ padding: 3px 7px;
57
+ border: 1px solid #cfcfcf;
58
+ }
59
+
60
+ tr.is-non-prod-issue, tr.is-non-prod-issue td a {
61
+ color: #36487d;
62
+ background: #f5f5f5;
63
+ }
64
+
65
+ td.issue-count {
66
+ font-weight: bold;
67
+ }
68
+
69
+ td.issue-name {
70
+ /* word-break: break-all; */
71
+ }
72
+
73
+ td.issue-cause {
74
+ word-break: break-all;
75
+ max-width: 240px;
76
+ font-size: 80%;
77
+ }
78
+
79
+ td.project-name {
80
+ word-break: break-all;
81
+ max-width: 260px;
82
+ }
83
+
84
+ .full-width {
85
+ width: 100%;
86
+ max-width: 100%;
87
+ }
88
+ </style>
89
+ </head>
90
+ <body>
91
+ <div class='page-content'>
92
+ <h1>
93
+ <a href=''>Sentry Errors</a>
94
+ </h1>
95
+
96
+ <p>Generated at: <b>REPORT_GENERATE_DATE</b>, for sentry organization <b>REPORT_SENTRY_ORG</b></p>
97
+
98
+ <h2>Repeating Issues</h2>
99
+
100
+ <table class='rows-table full-width' id="issues-table">
101
+ <thead>
102
+ <tr>
103
+ <th>Count</th>
104
+ <th>Project</th>
105
+ <th>Issue</th>
106
+ <th>Cause</th>
107
+ </tr>
108
+ </thead>
109
+ <tbody></tbody>
110
+ </table>
111
+
112
+ <h2>New Issues</h2>
113
+
114
+ <table class='rows-table full-width' id="new-issues-table">
115
+ <thead>
116
+ <tr>
117
+ <th>Count</th>
118
+ <th>Project</th>
119
+ <th>Issue</th>
120
+ <th>Cause</th>
121
+ </tr>
122
+ </thead>
123
+ <tbody></tbody>
124
+ </table>
125
+ </div>
126
+
127
+ <script>
128
+ var ALL_ISSUE_COUNTS = ['ALL_ISSUE_COUNTS_PLACEHOLDER'];
129
+ var NEW_ISSUE_COUNTS = ['NEW_ISSUE_COUNTS_PLACEHOLDER'];
130
+ </script>
131
+ <script>
132
+
133
+ // Some sort of replacement for jquery
134
+ ///
135
+ // Usage:
136
+ // $(selector, paretn) - find an element
137
+ // $$(selector, paretn) - find elements, return as array
138
+ // $.element(html, {class: '', appendTo: '', html: '', text: '', hide: '', classes: ['']}) - create element
139
+ // $.ajax({url: '', method: 'GET', data: '', contentType: '', accepts: '', headers: '', before: fn, process: fn, uploadProgress: fn, complete: fn, error: fn, })
140
+ // $.ready - $(document).ready
141
+ // $.matches - polifill
142
+ // $.closest - same as jquery
143
+
144
+ var $ = function (selector, element) {
145
+ if (selector[0] == '<') {
146
+ return $.element(selector, element);
147
+ }
148
+ return (element || document).querySelector(selector);
149
+ };
150
+
151
+ var $$ = function (selector, element) {
152
+ return Array.from((element || document).querySelectorAll(selector));
153
+ };
154
+
155
+ $.element = function (html, attributes) {
156
+ if (!attributes) attributes = {};
157
+ var element;
158
+
159
+ if (html[0] == '<') {
160
+ var div = document.createElement('div');
161
+ div.innerHTML = html;
162
+ element = div.childNodes[0];
163
+ } else {
164
+ var element = document.createElement(html);
165
+ }
166
+
167
+ Object.keys(attributes).forEach(function (key) {
168
+ var value = attributes[key];
169
+
170
+ if (key == 'class') {
171
+ element.className = value;
172
+ } else if (key == 'classes') {
173
+ value.forEach(function (className) {
174
+ el.classList.add(className);
175
+ });
176
+ } else if (key == 'appendTo') {
177
+ value.appendChild(element);
178
+ } else if (key == 'html') {
179
+ element.innerHTML = value;
180
+ } else if (key == 'text') {
181
+ element.innerText = value;
182
+ } else if (key == 'hide') {
183
+ element.style.display = 'none';
184
+ } else {
185
+ element.setAttribute(key, value);
186
+ }
187
+ });
188
+
189
+ return element;
190
+ };
191
+
192
+ $.ajax = function (options) {
193
+ var request = new XMLHttpRequest();
194
+
195
+ if (options.query) {
196
+ var query = Object.keys(options.query).map(function (k) {
197
+ return encodeURIComponent(k) + '=' + encodeURIComponent(options.query[k]);
198
+ }).join('&');
199
+ options.url += (options.url.includes("?") ? "&" : "?") + query;
200
+ }
201
+
202
+ request.open(options.method || 'GET', options.url);
203
+
204
+ options.before && options.before(request);
205
+
206
+ if (options.contentType == 'json') {
207
+ request.setRequestHeader("Content-type", "application/json");
208
+ } else if (options.contentType) {
209
+ request.setRequestHeader("Content-type", options.contentType);
210
+ } else if (options.data && !(options.data instanceof FormData)) {
211
+ request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
212
+ }
213
+
214
+ request.setRequestHeader("X-Requested-With", "XMLHttpRequest");
215
+ if (options.accepts) {
216
+ request.setRequestHeader('Accept', '*/*;q=0.5, ' + options.accepts);
217
+ }
218
+
219
+ if (options.headers) {
220
+ Object.keys(options.headers).forEach(function (key) {
221
+ request.setRequestHeader(key, options.headers[key]);
222
+ });
223
+ }
224
+
225
+ if (options.process) {
226
+ request.addEventListener("progress", function (event) {
227
+ options.process(request, event);
228
+ });
229
+ }
230
+ if (options.uploadProgress) {
231
+ request.upload.addEventListener("progress", function (event) {
232
+ options.uploadProgress(request, event);
233
+ });
234
+ }
235
+
236
+ request.addEventListener("load", function (event) {
237
+ //console.log('Request Status', request.status, request, event);
238
+ try {
239
+ var data;
240
+ var contentType = request.getResponseHeader('Content-type') || "";
241
+ if (contentType == "application/json" || contentType.match(/^application\/json/)) {
242
+ try {
243
+ data = JSON.parse(request.responseText);
244
+ request.responseJSON = data;
245
+ } catch (e) {
246
+ console.error(e);
247
+ }
248
+ }
249
+ if (request.status < 400) {
250
+ options.success && options.success(request, data, event);
251
+ } else {
252
+ options.error && options.error(request, event);
253
+ }
254
+ } catch (e) {
255
+ console.error(e);
256
+ }
257
+ options.complete && options.complete(request, event);
258
+ });
259
+
260
+ ['error', 'abort'].map(function (event) {
261
+ request.addEventListener(event, function (event) {
262
+ try {
263
+ options.error && options.error(request, event);
264
+ } catch (e) {
265
+ console.error(e);
266
+ }
267
+ options.complete && options.complete(request, event);
268
+ });
269
+ });
270
+
271
+ request.send(options.data);
272
+ };
273
+
274
+ $.ready = function (fn) {
275
+ if (document.readyState != 'loading') {
276
+ fn();
277
+ } else {
278
+ document.addEventListener('DOMContentLoaded', fn);
279
+ }
280
+ };
281
+
282
+ $.matches = function (el, selector) {
283
+ var p = Element.prototype;
284
+ var f = p.matches || p.webkitMatchesSelector || p.mozMatchesSelector || p.msMatchesSelector || function(s) {
285
+ return [].indexOf.call(document.querySelectorAll(s), this) !== -1;
286
+ };
287
+ return f.call(el, selector);
288
+ };
289
+
290
+ $.closest = function (element, selector) {
291
+ var parent = element;
292
+ while (parent && !$.matches(parent, selector)) {
293
+ parent = parent.parentNode;
294
+ if (parent instanceof HTMLDocument) {
295
+ return null;
296
+ }
297
+ }
298
+
299
+ return parent;
300
+ };
301
+
302
+ $.onClickOutside = function (element, optoins, callback) {
303
+ if (!optoins) optoins = {};
304
+
305
+ document.addEventListener('click', function cb(event) {
306
+ var parent = event.target;
307
+ while (!(parent instanceof HTMLDocument)) {
308
+ if (parent == element) {
309
+ //console.log('click inside');
310
+ return;
311
+ }
312
+ parent = parent.parentNode;
313
+ }
314
+
315
+ //console.log('click outside');
316
+ callback(event);
317
+
318
+ if (optoins.once) {
319
+ // for browsers that don't support "once: true"
320
+ event.currentTarget.removeEventListener(event.type, cb);
321
+ }
322
+ }, {once: optoins.once});
323
+ };
324
+ </script>
325
+
326
+ <script>
327
+ var issuesTable = $('#issues-table tbody');
328
+
329
+ ALL_ISSUE_COUNTS.forEach(issue => {
330
+ var tr = $.element('tr', {
331
+ appendTo: issuesTable,
332
+ class: issue.is_production ? 'is-prod-issue' : 'is-non-prod-issue',
333
+ });
334
+
335
+ $.element('td', {text: issue.issue_count, appendTo: tr, class: 'issue-count'});
336
+ var projectCell = $.element('td', {appendTo: tr, class: 'project-name'});
337
+ $.element('a', {href: issue.project_url, text: issue.project_name, target: '_blank', appendTo: projectCell})
338
+ var issueCell = $.element('td', {appendTo: tr, class: 'issue-name'});
339
+ $.element('a', {href: issue.issue_link, text: issue.issue, target: '_blank', appendTo: issueCell})
340
+ $.element('td', {text: issue.culprit, appendTo: tr, class: 'issue-cause'});
341
+ });
342
+
343
+ var issuesTable = $('#new-issues-table tbody');
344
+
345
+ NEW_ISSUE_COUNTS.forEach(issue => {
346
+ var tr = $.element('tr', {
347
+ appendTo: issuesTable,
348
+ class: issue.is_production ? 'is-prod-issue' : 'is-non-prod-issue',
349
+ });
350
+
351
+ $.element('td', {text: issue.issue_count, appendTo: tr, class: 'issue-count'});
352
+ var projectCell = $.element('td', {appendTo: tr, class: 'project-name'});
353
+ $.element('a', {href: issue.project_url, text: issue.project_name, target: '_blank', appendTo: projectCell})
354
+ var issueCell = $.element('td', {appendTo: tr, class: 'issue-name'});
355
+ $.element('a', {href: issue.issue_link, text: issue.issue, target: '_blank', appendTo: issueCell})
356
+ $.element('td', {text: issue.culprit, appendTo: tr, class: 'issue-cause'});
357
+ });
358
+ </script>
359
+ </body>
360
+ </html>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sentry_top_errors
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pavel Evstigneev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-07-17 00:00:00.000000000 Z
11
+ date: 2023-07-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: excon
@@ -60,6 +60,7 @@ files:
60
60
  - lib/sentry_top_errors/reporter.rb
61
61
  - lib/sentry_top_errors/sentry_client.rb
62
62
  - senrty_top_errors.gemspec
63
+ - templates/index.html
63
64
  homepage: http://github.com/Paxa/sentry_top_errors
64
65
  licenses:
65
66
  - MIT