sentry_top_errors 0.1.0 → 0.1.2

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: 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