solid_errors 0.2.16 → 0.3.0

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: 7bd31da97ea56095d305c2bcfc472f271aeaaa151c597f4f0726f15f65ec9abc
4
- data.tar.gz: 0d05dd15d6ed3d74fe37a09380fc0078747b82fe27399e1f5c7fea2b3ec3d1df
3
+ metadata.gz: dabfc29afbf8683a9f79db87fc193679fa3074415e3a68b2d0d0088bfd0d99d5
4
+ data.tar.gz: 6124021da4ef6ea9823138cc264d2bcdd07d2496c2638ea3ff7ceee852975b2a
5
5
  SHA512:
6
- metadata.gz: a1f3d14559970f33ae16e9ee2b2de47da522265472819c79c617a98494b170ca56dd63db527afaa75faf9d1fc20f88c0f7e4627abba7286b3db7788e99c3b642
7
- data.tar.gz: e7da921a3718adbb33e9c11b11fab2bdac3aea3d6a069e9fccad74f161301688b6175729773a183f32f8420f10e2944c94619a4f5840502396d66a7db6312449
6
+ metadata.gz: 7859b1b2ced0cb2e44f2351d9645bb6ffa3cc88451d5bccd6d6ec74eee10c698dfd39eeccc1e5efeed5505b173a85417d3008977dad0c12afccb5a8ba3039e04
7
+ data.tar.gz: 87100e0396f8b6630a41889516e32da08f7cbd94ee7da6b45e17c67a0726bd49aa4f77a59df05fbbbcc5c916105cb4911f99c2400d34c4e89f5c9e76a7db2f93
data/README.md CHANGED
@@ -1,28 +1,125 @@
1
- # SolidErrors
1
+ # Solid Errors
2
+
3
+ <p>
4
+ <a href="https://rubygems.org/gems/solid_errors">
5
+ <img alt="GEM Version" src="https://img.shields.io/gem/v/solid_errors?color=168AFE&include_prereleases&logo=ruby&logoColor=FE1616">
6
+ </a>
7
+ <a href="https://rubygems.org/gems/solid_errors">
8
+ <img alt="GEM Downloads" src="https://img.shields.io/gem/dt/solid_errors?color=168AFE&logo=ruby&logoColor=FE1616">
9
+ </a>
10
+ <a href="https://github.com/testdouble/standard">
11
+ <img alt="Ruby Style" src="https://img.shields.io/badge/style-standard-168AFE?logo=ruby&logoColor=FE1616" />
12
+ </a>
13
+ <a href="https://github.com/fractaledmind/solid_errors/actions/workflows/main.yml">
14
+ <img alt="Tests" src="https://github.com/fractaledmind/solid_errors/actions/workflows/main.yml/badge.svg" />
15
+ </a>
16
+ <a href="https://github.com/sponsors/fractaledmind">
17
+ <img alt="Sponsors" src="https://img.shields.io/github/sponsors/fractaledmind?color=eb4aaa&logo=GitHub%20Sponsors" />
18
+ </a>
19
+ <a href="https://ruby.social/@fractaledmind">
20
+ <img alt="Ruby.Social Follow" src="https://img.shields.io/mastodon/follow/109291299520066427?domain=https%3A%2F%2Fruby.social&label=%40fractaledmind&style=social">
21
+ </a>
22
+ <a href="https://twitter.com/fractaledmind">
23
+ <img alt="Twitter Follow" src="https://img.shields.io/twitter/url?label=%40fractaledmind&style=social&url=https%3A%2F%2Ftwitter.com%2Ffractaledmind">
24
+ </a>
25
+ </p>
26
+
27
+
28
+ Solid Errors is a DB-based, app-internal exception tracker for Rails applications, designed with simplicity and performance in mind. It uses the new [Rails error reporting API](https://guides.rubyonrails.org/error_reporting.html) to store uncaught exceptions in the database, and provides a simple UI for viewing and managing exceptions.
2
29
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/solid_errors`. To experiment with that code, run `bin/console` for an interactive prompt.
30
+ ## Installation
4
31
 
5
- TODO: Delete this and the text above, and describe your gem
32
+ Install the gem and add to the application's Gemfile by executing:
6
33
 
7
- ## Installation
34
+ $ bundle add solid_errors
35
+
36
+ If bundler is not being used to manage dependencies, install the gem by executing:
37
+
38
+ $ gem install solid_errors
39
+
40
+ After installing the gem, run the installer:
41
+
42
+ $ rails generate solid_errors:install
43
+
44
+ This will copy the required migration over to your app.
45
+
46
+ Then mount the engine in your config/routes.rb file
47
+
48
+ mount SolidErrors::Engine, at: "/solid_errors"
49
+
50
+ > [!NOTE]
51
+ > Be sure to [secure the dashboard](#authentication) in production.
52
+
53
+ ## Usage
54
+
55
+ All exceptions are recorded automatically. No additional code required.
56
+
57
+ Please consult the [official guides](https://guides.rubyonrails.org/error_reporting.html) for an introduction to the error reporting API.
58
+
59
+ ### Configuration
60
+
61
+ You can configure Solid Errors via the Rails configuration object, under the `solid_errors` key. Currently, only 3 configuration options are available:
62
+
63
+ * `connects_to` - The database configuration to use for the Solid Errors database. See [Database Configuration](#database-configuration) for more information.
64
+ * `username` - The username to use for HTTP authentication. See [Authentication](#authentication) for more information.
65
+ * `password` - The password to use for HTTP authentication. See [Authentication](#authentication) for more information.
66
+
67
+ #### Database Configuration
68
+
69
+ `config.solid_errors.connects_to` takes a custom database configuration hash that will be used in the abstract `SolidErrors::Record` Active Record model. This is required to use a different database than the main app. For example:
8
70
 
9
- Add this line to your application's Gemfile:
71
+ ```ruby
72
+ # Use a single separate DB for Solid Errors
73
+ config.solid_errors.connects_to = { database: { writing: :solid_errors, reading: :solid_errors } }
74
+ ```
75
+
76
+ or
10
77
 
11
78
  ```ruby
12
- gem 'solid_errors'
79
+ # Use a separate primary/replica pair for Solid Errors
80
+ config.solid_errors.connects_to = { database: { writing: :solid_errors_primary, reading: :solid_errors_replica } }
13
81
  ```
14
82
 
15
- And then execute:
83
+ #### Authentication
16
84
 
17
- $ bundle install
85
+ Solid Errors does not restrict access out of the box. You must secure the dashboard yourself. However, it does provide basic HTTP authentication that can be used with basic authentication or Devise. All you need to do is setup a username and password.
18
86
 
19
- Or install it yourself as:
87
+ There are two ways to setup a username and password. First, you can use the `SOLIDERRORS_USERNAME` and `SOLIDERRORS_PASSWORD` environment variables:
20
88
 
21
- $ gem install solid_errors
89
+ ```ruby
90
+ ENV["SOLIDERRORS_USERNAME"] = "frodo"
91
+ ENV["SOLIDERRORS_PASSWORD"] = "ikeptmysecrets"
92
+ ```
22
93
 
23
- ## Usage
94
+ Second, you can set the `SolidErrors.username` and `SolidErrors.password` variables in an initializer:
95
+
96
+ ```ruby
97
+ # Set authentication credentials for Solid Errors
98
+ config.solid_errors.username = Rails.application.credentials.solid_errors.username
99
+ config.solid_errors.password = Rails.application.credentials.solid_errors.password
100
+ ```
101
+
102
+ Either way, if you have set a username and password, Solid Errors will use basic HTTP authentication. If you have not set a username and password, Solid Errors will not require any authentication to view the dashboard.
103
+
104
+ If you use Devise for authenctication in your app, you can also restrict access to the dashboard by using their `authenticate` contraint in your routes file:
105
+
106
+ ```ruby
107
+ authenticate :user, -> (user) { user.admin? } do
108
+ mount SolidErrors::Engine, at: "/solid_errors"
109
+ end
110
+ ```
111
+
112
+ ### Examples
113
+
114
+ There are only two screens in the dashboard.
115
+
116
+ * the index view of all unresolved errors:
117
+
118
+ ![image description](images/index-screenshot.png)
119
+
120
+ * and the show view of a particular error:
24
121
 
25
- TODO: Write usage instructions here
122
+ ![image description](images/show-screenshot.png)
26
123
 
27
124
  ## Development
28
125
 
@@ -32,7 +129,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
32
129
 
33
130
  ## Contributing
34
131
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/solid_errors. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/solid_errors/blob/main/CODE_OF_CONDUCT.md).
132
+ Bug reports and pull requests are welcome on GitHub at https://github.com/fractaledmind/solid_errors. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/fractaledmind/solid_errors/blob/main/CODE_OF_CONDUCT.md).
36
133
 
37
134
  ## License
38
135
 
@@ -40,4 +137,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
40
137
 
41
138
  ## Code of Conduct
42
139
 
43
- Everyone interacting in the SolidErrors project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/solid_errors/blob/main/CODE_OF_CONDUCT.md).
140
+ Everyone interacting in the SolidErrors project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/fractaledmind/solid_errors/blob/main/CODE_OF_CONDUCT.md).
@@ -1,4 +1,7 @@
1
1
  module SolidErrors
2
2
  class ApplicationController < ActionController::Base
3
+ protect_from_forgery with: :exception
4
+
5
+ http_basic_authenticate_with name: SolidErrors.username, password: SolidErrors.password if SolidErrors.password
3
6
  end
4
7
  end
@@ -1,20 +1,17 @@
1
1
  module SolidErrors
2
2
  class ErrorsController < ApplicationController
3
- before_action :set_error, only: %i[ show update ]
3
+ before_action :set_error, only: %i[show update]
4
4
 
5
5
  # GET /errors
6
6
  def index
7
7
  errors_table = Error.arel_table
8
8
  occurrences_table = Occurrence.arel_table
9
- recent_occurrence = occurrences_table
10
- .project(occurrences_table[:created_at].maximum)
11
- .as('recent_occurrence')
12
9
 
13
10
  @errors = Error.unresolved
14
- .joins(:occurrences)
15
- .select(errors_table[Arel.star], recent_occurrence)
16
- .group(errors_table[:id])
17
- .order(recent_occurrence: :desc)
11
+ .joins(:occurrences)
12
+ .select(errors_table[Arel.star], occurrences_table[:created_at].maximum.as("recent_occurrence"))
13
+ .group(errors_table[:id])
14
+ .order(recent_occurrence: :desc)
18
15
  end
19
16
 
20
17
  # GET /errors/1
@@ -28,13 +25,14 @@ module SolidErrors
28
25
  end
29
26
 
30
27
  private
31
- # Only allow a list of trusted parameters through.
32
- def error_params
33
- params.require(:error).permit(:resolved_at)
34
- end
35
28
 
36
- def set_error
37
- @error = Error.find(params[:id])
38
- end
29
+ # Only allow a list of trusted parameters through.
30
+ def error_params
31
+ params.require(:error).permit(:resolved_at)
32
+ end
33
+
34
+ def set_error
35
+ @error = Error.find(params[:id])
36
+ end
39
37
  end
40
38
  end
@@ -10,7 +10,7 @@ module SolidErrors
10
10
  BacktraceLine.parse(unparsed_line.to_s, opts)
11
11
  end.compact
12
12
 
13
- instance = new(lines)
13
+ new(lines)
14
14
  end
15
15
 
16
16
  def initialize(lines)
@@ -22,9 +22,9 @@ module SolidErrors
22
22
  #
23
23
  # Returns array containing backtrace lines.
24
24
  def to_ary
25
- lines.take(1000).map { |l| { :number => l.filtered_number, :file => l.filtered_file, :method => l.filtered_method, :source => l.source } }
25
+ lines.take(1000).map { |l| {number: l.filtered_number, file: l.filtered_file, method: l.filtered_method, source: l.source} }
26
26
  end
27
- alias :to_a :to_ary
27
+ alias_method :to_a, :to_ary
28
28
 
29
29
  # JSON support.
30
30
  #
@@ -2,14 +2,14 @@ module SolidErrors
2
2
  class BacktraceLine
3
3
  # Backtrace line regexp (optionally allowing leading X: for windows support).
4
4
  INPUT_FORMAT = %r{^((?:[a-zA-Z]:)?[^:]+):(\d+)(?::in `([^']+)')?$}.freeze
5
- STRING_EMPTY = ''.freeze
6
- GEM_ROOT = '[GEM_ROOT]'.freeze
7
- PROJECT_ROOT = '[PROJECT_ROOT]'.freeze
5
+ STRING_EMPTY = "".freeze
6
+ GEM_ROOT = "[GEM_ROOT]".freeze
7
+ PROJECT_ROOT = "[PROJECT_ROOT]".freeze
8
8
  PROJECT_ROOT_CACHE = {}
9
9
  GEM_ROOT_CACHE = {}
10
10
  RELATIVE_ROOT = Regexp.new('^\.\/').freeze
11
11
  RAILS_ROOT = ::Rails.root.to_s.dup.freeze
12
- ROOT_REGEXP = Regexp.new("^#{ Regexp.escape(RAILS_ROOT) }").freeze
12
+ ROOT_REGEXP = Regexp.new("^#{Regexp.escape(RAILS_ROOT)}").freeze
13
13
  BACKTRACE_FILTERS = [
14
14
  lambda { |line|
15
15
  return line unless defined?(Gem)
@@ -47,21 +47,19 @@ module SolidErrors
47
47
  file, number, method = match[1], match[2], match[3]
48
48
  filtered_args = [fmatch[1], fmatch[2], fmatch[3]]
49
49
  new(file, number, method, *filtered_args, opts.fetch(:source_radius, 2))
50
- else
51
- nil
52
50
  end
53
51
  end
54
52
 
55
53
  def initialize(file, number, method, filtered_file = file,
56
- filtered_number = number, filtered_method = method,
57
- source_radius = 2)
58
- self.filtered_file = filtered_file
54
+ filtered_number = number, filtered_method = method,
55
+ source_radius = 2)
56
+ self.filtered_file = filtered_file
59
57
  self.filtered_number = filtered_number
60
58
  self.filtered_method = filtered_method
61
- self.file = file
62
- self.number = number
63
- self.method = method
64
- self.source_radius = source_radius
59
+ self.file = file
60
+ self.number = number
61
+ self.method = method
62
+ self.source_radius = source_radius
65
63
  end
66
64
 
67
65
  # Reconstructs the line in a readable fashion.
@@ -74,7 +72,7 @@ module SolidErrors
74
72
  end
75
73
 
76
74
  def inspect
77
- "<Line:#{to_s}>"
75
+ "<Line:#{self}>"
78
76
  end
79
77
 
80
78
  # Determines if this line is part of the application trace or not.
@@ -105,8 +103,11 @@ module SolidErrors
105
103
 
106
104
  l = 0
107
105
  File.open(file) do |f|
108
- start.times { f.gets ; l += 1 }
109
- return Hash[duration.times.map { (line = f.gets) ? [(l += 1), line] : nil }.compact]
106
+ start.times {
107
+ f.gets
108
+ l += 1
109
+ }
110
+ return duration.times.map { (line = f.gets) ? [(l += 1), line] : nil }.compact.to_h
110
111
  end
111
112
  end
112
113
  end
@@ -27,7 +27,7 @@ module SolidErrors
27
27
  end
28
28
 
29
29
  def badge_classes
30
- "px-2 inline-flex text-[.75em] font-semibold rounded-md #{SEVERITY_TO_BADGE_CLASSES[severity.to_sym]}"
30
+ "px-2 inline-flex text-sm font-semibold rounded-md #{SEVERITY_TO_BADGE_CLASSES[severity.to_sym]}"
31
31
  end
32
32
  end
33
33
  end
@@ -14,8 +14,8 @@ module SolidErrors
14
14
 
15
15
  private
16
16
 
17
- def parse_backtrace(backtrace)
18
- Backtrace.parse(backtrace)
19
- end
17
+ def parse_backtrace(backtrace)
18
+ Backtrace.parse(backtrace)
19
+ end
20
20
  end
21
21
  end
@@ -4,7 +4,7 @@ module SolidErrors
4
4
  class Record < ActiveRecord::Base
5
5
  self.abstract_class = true
6
6
 
7
- connects_to **SolidErrors.connects_to if SolidErrors.connects_to
7
+ connects_to(**SolidErrors.connects_to) if SolidErrors.connects_to
8
8
  end
9
9
  end
10
10
 
@@ -95,7 +95,8 @@
95
95
  */
96
96
 
97
97
  abbr:where([title]) {
98
- text-decoration: underline dotted;
98
+ text-decoration-line: underline;
99
+ text-decoration-style: dotted;
99
100
  }
100
101
 
101
102
  /*
@@ -589,14 +590,26 @@
589
590
  border-width: 0
590
591
  }
591
592
 
592
- .static{
593
- position: static
593
+ .fixed{
594
+ position: fixed
594
595
  }
595
596
 
596
597
  .relative{
597
598
  position: relative
598
599
  }
599
600
 
601
+ .left-0{
602
+ left: 0px
603
+ }
604
+
605
+ .right-0{
606
+ right: 0px
607
+ }
608
+
609
+ .top-0{
610
+ top: 0px
611
+ }
612
+
600
613
  .-mx-2{
601
614
  margin-left: -0.5rem;
602
615
  margin-right: -0.5rem
@@ -611,10 +624,6 @@
611
624
  margin-bottom: 0.75rem
612
625
  }
613
626
 
614
- .mb-36{
615
- margin-bottom: 9rem
616
- }
617
-
618
627
  .ml-6{
619
628
  margin-left: 1.5rem
620
629
  }
@@ -627,37 +636,8 @@
627
636
  margin-top: 1rem
628
637
  }
629
638
 
630
- .min-h-full{
631
- min-height: 100%
632
- }
633
-
634
- .select-none{
635
- user-select: none
636
- }
637
-
638
- .overflow-auto{
639
- overflow: auto
640
- }
641
-
642
- .rounded-b-lg{
643
- border-bottom-right-radius: 0.5rem;
644
- border-bottom-left-radius: 0.5rem
645
- }
646
-
647
- .leading-normal{
648
- line-height: 1.5
649
- }
650
-
651
- .mt-4{
652
- margin-top: 1rem
653
- }
654
-
655
- .\!block{
656
- display: block !important
657
- }
658
-
659
- .block{
660
- display: block
639
+ .inline-block{
640
+ display: inline-block
661
641
  }
662
642
 
663
643
  .flex{
@@ -668,10 +648,6 @@
668
648
  display: inline-flex
669
649
  }
670
650
 
671
- .flex-col{
672
- flex-direction: column
673
- }
674
-
675
651
  .table{
676
652
  display: table
677
653
  }
@@ -680,12 +656,8 @@
680
656
  display: grid
681
657
  }
682
658
 
683
- .hidden{
684
- display: none
685
- }
686
-
687
- .w-full{
688
- width: 100%
659
+ .min-h-full{
660
+ min-height: 100%
689
661
  }
690
662
 
691
663
  .min-w-full{
@@ -712,10 +684,18 @@
712
684
  grid-template-columns: repeat(2, minmax(0, 1fr))
713
685
  }
714
686
 
687
+ .flex-col{
688
+ flex-direction: column
689
+ }
690
+
715
691
  .flex-wrap{
716
692
  flex-wrap: wrap
717
693
  }
718
694
 
695
+ .items-start{
696
+ align-items: flex-start
697
+ }
698
+
719
699
  .items-center{
720
700
  align-items: center
721
701
  }
@@ -773,10 +753,18 @@
773
753
  border-color: rgb(209 213 219 / var(--tw-divide-opacity))
774
754
  }
775
755
 
756
+ .overflow-auto{
757
+ overflow: auto
758
+ }
759
+
776
760
  .whitespace-nowrap{
777
761
  white-space: nowrap
778
762
  }
779
763
 
764
+ .whitespace-pre-wrap{
765
+ white-space: pre-wrap
766
+ }
767
+
780
768
  .rounded{
781
769
  border-radius: 0.25rem
782
770
  }
@@ -785,8 +773,9 @@
785
773
  border-radius: 0.5rem
786
774
  }
787
775
 
788
- .rounded-md{
789
- border-radius: 0.375rem
776
+ .rounded-b-lg{
777
+ border-bottom-right-radius: 0.5rem;
778
+ border-bottom-left-radius: 0.5rem
790
779
  }
791
780
 
792
781
  .border{
@@ -797,14 +786,14 @@
797
786
  border-bottom-width: 1px
798
787
  }
799
788
 
800
- .border-gray-300{
789
+ .border-blue-500{
801
790
  --tw-border-opacity: 1;
802
- border-color: rgb(209 213 219 / var(--tw-border-opacity))
791
+ border-color: rgb(59 130 246 / var(--tw-border-opacity))
803
792
  }
804
793
 
805
- .border-blue-500{
794
+ .border-gray-300{
806
795
  --tw-border-opacity: 1;
807
- border-color: rgb(59 130 246 / var(--tw-border-opacity))
796
+ border-color: rgb(209 213 219 / var(--tw-border-opacity))
808
797
  }
809
798
 
810
799
  .bg-gray-100{
@@ -812,18 +801,14 @@
812
801
  background-color: rgb(243 244 246 / var(--tw-bg-opacity))
813
802
  }
814
803
 
815
- .bg-gray-200{
804
+ .bg-green-50{
816
805
  --tw-bg-opacity: 1;
817
- background-color: rgb(229 231 235 / var(--tw-bg-opacity))
806
+ background-color: rgb(240 253 244 / var(--tw-bg-opacity))
818
807
  }
819
808
 
820
- .bg-transparent{
821
- background-color: transparent
822
- }
823
-
824
- .bg-white{
809
+ .bg-red-50{
825
810
  --tw-bg-opacity: 1;
826
- background-color: rgb(255 255 255 / var(--tw-bg-opacity))
811
+ background-color: rgb(254 242 242 / var(--tw-bg-opacity))
827
812
  }
828
813
 
829
814
  .bg-slate-800{
@@ -831,34 +816,17 @@
831
816
  background-color: rgb(30 41 59 / var(--tw-bg-opacity))
832
817
  }
833
818
 
834
- .bg-blue-100{
835
- --tw-bg-opacity: 1;
836
- background-color: rgb(219 234 254 / var(--tw-bg-opacity))
837
- }
838
-
839
- .bg-red-100{
840
- --tw-bg-opacity: 1;
841
- background-color: rgb(254 226 226 / var(--tw-bg-opacity))
819
+ .bg-transparent{
820
+ background-color: transparent
842
821
  }
843
822
 
844
- .bg-yellow-100{
823
+ .bg-white{
845
824
  --tw-bg-opacity: 1;
846
- background-color: rgb(254 249 195 / var(--tw-bg-opacity))
847
- }
848
-
849
- .text-blue-800{
850
- --tw-text-opacity: 1;
851
- color: rgb(30 64 175 / var(--tw-text-opacity))
852
- }
853
-
854
- .text-red-800{
855
- --tw-text-opacity: 1;
856
- color: rgb(153 27 27 / var(--tw-text-opacity))
825
+ background-color: rgb(255 255 255 / var(--tw-bg-opacity))
857
826
  }
858
827
 
859
- .text-yellow-800{
860
- --tw-text-opacity: 1;
861
- color: rgb(133 77 14 / var(--tw-text-opacity))
828
+ .p-2{
829
+ padding: 0.5rem
862
830
  }
863
831
 
864
832
  .p-4{
@@ -870,10 +838,6 @@
870
838
  padding-right: 0px
871
839
  }
872
840
 
873
- .p-2{
874
- padding: 0.5rem
875
- }
876
-
877
841
  .px-2{
878
842
  padding-left: 0.5rem;
879
843
  padding-right: 0.5rem
@@ -894,6 +858,11 @@
894
858
  padding-bottom: 0.25rem
895
859
  }
896
860
 
861
+ .py-2{
862
+ padding-top: 0.5rem;
863
+ padding-bottom: 0.5rem
864
+ }
865
+
897
866
  .py-3{
898
867
  padding-top: 0.75rem;
899
868
  padding-bottom: 0.75rem
@@ -933,10 +902,18 @@
933
902
  padding-top: 1.75rem
934
903
  }
935
904
 
905
+ .rounded-md{
906
+ border-radius: 0.375rem
907
+ }
908
+
936
909
  .text-left{
937
910
  text-align: left
938
911
  }
939
912
 
913
+ .text-center{
914
+ text-align: center
915
+ }
916
+
940
917
  .text-right{
941
918
  text-align: right
942
919
  }
@@ -950,10 +927,6 @@
950
927
  line-height: 2rem
951
928
  }
952
929
 
953
- .text-\[\.75em\]{
954
- font-size: .75em
955
- }
956
-
957
930
  .text-base{
958
931
  font-size: 1rem;
959
932
  line-height: 1.5rem
@@ -976,9 +949,23 @@
976
949
  font-weight: 600
977
950
  }
978
951
 
979
- .text-white{
980
- --tw-text-opacity: 1;
981
- color: rgb(255 255 255 / var(--tw-text-opacity))
952
+ .leading-normal{
953
+ line-height: 1.5
954
+ }
955
+
956
+ .bg-blue-100{
957
+ --tw-bg-opacity: 1;
958
+ background-color: rgb(219 234 254 / var(--tw-bg-opacity))
959
+ }
960
+
961
+ .bg-red-100{
962
+ --tw-bg-opacity: 1;
963
+ background-color: rgb(254 226 226 / var(--tw-bg-opacity))
964
+ }
965
+
966
+ .bg-yellow-100{
967
+ --tw-bg-opacity: 1;
968
+ background-color: rgb(254 249 195 / var(--tw-bg-opacity))
982
969
  }
983
970
 
984
971
  .text-blue-400{
@@ -1001,11 +988,6 @@
1001
988
  color: rgb(75 85 99 / var(--tw-text-opacity))
1002
989
  }
1003
990
 
1004
- .text-gray-800{
1005
- --tw-text-opacity: 1;
1006
- color: rgb(31 41 55 / var(--tw-text-opacity))
1007
- }
1008
-
1009
991
  .text-gray-900{
1010
992
  --tw-text-opacity: 1;
1011
993
  color: rgb(17 24 39 / var(--tw-text-opacity))
@@ -1016,10 +998,45 @@
1016
998
  color: rgb(34 197 94 / var(--tw-text-opacity))
1017
999
  }
1018
1000
 
1001
+ .text-red-500{
1002
+ --tw-text-opacity: 1;
1003
+ color: rgb(239 68 68 / var(--tw-text-opacity))
1004
+ }
1005
+
1006
+ .text-blue-800{
1007
+ --tw-text-opacity: 1;
1008
+ color: rgb(30 64 175 / var(--tw-text-opacity))
1009
+ }
1010
+
1011
+ .text-red-800{
1012
+ --tw-text-opacity: 1;
1013
+ color: rgb(153 27 27 / var(--tw-text-opacity))
1014
+ }
1015
+
1016
+ .text-yellow-800{
1017
+ --tw-text-opacity: 1;
1018
+ color: rgb(133 77 14 / var(--tw-text-opacity))
1019
+ }
1020
+
1021
+ .text-white{
1022
+ --tw-text-opacity: 1;
1023
+ color: rgb(255 255 255 / var(--tw-text-opacity))
1024
+ }
1025
+
1019
1026
  .underline{
1020
1027
  text-decoration-line: underline
1021
1028
  }
1022
1029
 
1030
+ .transition-opacity{
1031
+ transition-property: opacity;
1032
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1033
+ transition-duration: 150ms
1034
+ }
1035
+
1036
+ .opacity-0{
1037
+ opacity: 0
1038
+ }
1039
+
1023
1040
  .even\:bg-gray-50:nth-child(even){
1024
1041
  --tw-bg-opacity: 1;
1025
1042
  background-color: rgb(249 250 251 / var(--tw-bg-opacity))
@@ -1036,14 +1053,14 @@
1036
1053
  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000)
1037
1054
  }
1038
1055
 
1039
- .hover\:ring-gray-200:hover{
1056
+ .hover\:ring-blue-200:hover{
1040
1057
  --tw-ring-opacity: 1;
1041
- --tw-ring-color: rgb(229 231 235 / var(--tw-ring-opacity))
1058
+ --tw-ring-color: rgb(191 219 254 / var(--tw-ring-opacity))
1042
1059
  }
1043
1060
 
1044
- .hover\:ring-blue-200:hover{
1061
+ .hover\:ring-gray-200:hover{
1045
1062
  --tw-ring-opacity: 1;
1046
- --tw-ring-color: rgb(191 219 254 / var(--tw-ring-opacity))
1063
+ --tw-ring-color: rgb(229 231 235 / var(--tw-ring-opacity))
1047
1064
  }
1048
1065
 
1049
1066
  @media (min-width: 640px){
@@ -1094,21 +1111,20 @@
1094
1111
  <% end %>
1095
1112
  </div>
1096
1113
 
1097
- <script>
1098
- var element = document.querySelector('[data-controller="fade"]');
1099
- function fadeOut(el) {
1100
- var opacity = 1; // Initial opacity
1101
- var interval = setInterval(function() {
1102
- if (opacity > 0) {
1103
- opacity -= 0.1;
1104
- el.style.opacity = opacity;
1105
- } else {
1106
- clearInterval(interval); // Stop the interval when opacity reaches 0
1107
- el.style.display = 'none'; // Hide the element
1108
- }
1109
- }, 50);
1110
- }
1111
- fadeOut(element);
1114
+ <script nonce="<%= content_security_policy_nonce %>">
1115
+ function fadeOut(element) {
1116
+ element.classList.add('transition-opacity')
1117
+ setTimeout(
1118
+ () => {
1119
+ element.classList.add('opacity-0')
1120
+ element.remove()
1121
+ },
1122
+ 2000
1123
+ )
1124
+ }
1125
+ document.querySelectorAll('[data-controller="fade"]').forEach(element => {
1126
+ fadeOut(element);
1127
+ });
1112
1128
  </script>
1113
1129
  </body>
1114
1130
  </html>
@@ -8,13 +8,16 @@
8
8
  from
9
9
  <em><code><%= error.source %></code></em>
10
10
  </div>
11
- <pre class="ml-6 mt-4"><%= error.message %></pre>
11
+ <pre class="whitespace-pre-wrap ml-6 mt-4"><%= error.message %></pre>
12
12
  </td>
13
13
  <td scope="col" class="whitespace-nowrap px-3 py-4 pt-7 text-gray-500 text-right">
14
14
  <%= error.occurrences.size %>
15
15
  </td>
16
16
  <td scope="col" class="whitespace-nowrap px-3 py-4 pt-7 text-gray-500 text-right">
17
- <%= time_ago_in_words error.occurrences.maximum(:created_at), scope: 'datetime.distance_in_words.short' %>
17
+ <% last_seen_at = error.occurrences.maximum(:created_at) %>
18
+ <abbr title="<%= last_seen_at.iso8601 %>" class="cursor-help">
19
+ <%= time_tag last_seen_at, time_ago_in_words(last_seen_at, scope: 'datetime.distance_in_words.short') %>
20
+ </abbr>
18
21
  </td>
19
22
  <td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-3">
20
23
  <%= button_to error_path(error), method: :patch, class: "inline-flex items-center justify-center gap-2 font-medium cursor-pointer border rounded-lg py-3 px-5 bg-transparent text-blue-500 border-blue-500 hover:ring-blue-200 hover:ring-8", params: { error: { resolved_at: Time.now } } do %>
@@ -74,20 +74,24 @@
74
74
  <em><%= @error.source %></em>
75
75
  </dd>
76
76
  </div>
77
- <div class="flex items-center justify-between flex-wrap gap-x-2">
77
+ <div class="flex items-start justify-between flex-wrap gap-x-2">
78
78
  <dt class="font-bold">
79
79
  <%= SolidErrors::Error.human_attribute_name(:project_root) %>
80
80
  </dt>
81
- <dd class="inline-flex items-center gap-1">
81
+ <dd class="">
82
82
  <span><%= SolidErrors::BacktraceLine::RAILS_ROOT %></span>
83
83
  </dd>
84
84
  </div>
85
- <div class="flex items-center justify-between flex-wrap gap-x-2">
85
+ <div class="flex items-start justify-between flex-wrap gap-x-2">
86
86
  <dt class="font-bold">
87
87
  <%= SolidErrors::Error.human_attribute_name(:gem_root) %>
88
88
  </dt>
89
- <dd class="inline-flex items-center gap-1">
90
- <span><%= Gem.path.join("\n") %></span>
89
+ <dd class="">
90
+ <ul>
91
+ <% Gem.path.each do |path| %>
92
+ <li><%= path %></li>
93
+ <% end %>
94
+ </ul>
91
95
  </dd>
92
96
  </div>
93
97
  </dl>
@@ -95,7 +99,8 @@
95
99
  <%= render "solid_errors/errors/actions", error: @error %>
96
100
  <% end %>
97
101
 
98
- <hr class="mt-4 pb-4">
102
+ <br>
103
+ <br>
99
104
 
100
105
  <%= render "solid_errors/occurrences/collection",
101
106
  occurrences: @error.occurrences,
@@ -10,12 +10,12 @@
10
10
  <div class="">
11
11
  <dl class="ml-6">
12
12
  <% occurrence.context&.each do |key, value| %>
13
- <div class="flex items-center justify-between flex-wrap gap-x-2">
13
+ <div class="flex items-center justify-start flex-wrap gap-x-2">
14
14
  <dt class="font-bold">
15
15
  <%= SolidErrors::Occurrence.human_attribute_name(key) %>
16
16
  </dt>
17
17
  <dd class="">
18
- <%= value %>
18
+ <code><%= value %></code>
19
19
  </dd>
20
20
  </div>
21
21
  <% end %>
@@ -27,9 +27,9 @@
27
27
  <% backtrace.lines.each_with_index do |line, i| %>
28
28
  <%= tag.details open: line.application? || i.zero? do %>
29
29
  <summary class="hover:bg-gray-50 px-2 py-1 rounded cursor-pointer">
30
- <span class="text-gray-500"><%= File.dirname(line.filtered_file) %>/</span><span class="text-blue-500"><%= File.basename(line.filtered_file) %></span>:<span class="text-gray-900"><%= line.filtered_number %></span>
30
+ <span class="text-gray-500"><%= File.dirname(line.filtered_file) %>/</span><span class="text-blue-500 font-medium"><%= File.basename(line.filtered_file) %></span>:<span class="text-gray-900 font-medium"><%= line.filtered_number %></span>
31
31
  <span class="text-gray-500">in</span>
32
- <code class="text-green-500"><%= line.filtered_method %></code>
32
+ <code class="text-green-500 font-medium"><%= line.filtered_method %></code>
33
33
  </summary>
34
34
  <div><pre class="flex overflow-auto rounded-b-lg bg-slate-800 p-4 text-sm leading-normal text-white sm:rounded-t-lg"><code class="flex flex-col min-h-full min-w-content px-0"><% line.source.each do |n, code| %>
35
35
  <div class="line"><span class="mr-2 text-right select-none text-gray-600"><%= n %></span><span><%= code %></span></div>
data/config/routes.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  SolidErrors::Engine.routes.draw do
2
- root to: "errors#index"
2
+ get "/" => "errors#index", :as => :root
3
3
 
4
- resources :errors, only: [:index, :show, :update]
4
+ resources :errors, only: [:index, :show, :update], path: ""
5
5
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rails/generators'
4
- require 'rails/generators/active_record'
3
+ require "rails/generators"
4
+ require "rails/generators/active_record"
5
5
 
6
6
  module SolidErrors
7
7
  #
@@ -13,14 +13,14 @@ module SolidErrors
13
13
 
14
14
  source_root File.expand_path("templates", __dir__)
15
15
 
16
- class_option :database, type: :string, aliases: %i(--db), desc: "The database for your migration. By default, the current environment's primary database is used."
16
+ class_option :database, type: :string, aliases: %i[--db], desc: "The database for your migration. By default, the current environment's primary database is used."
17
17
  class_option :skip_migrations, type: :boolean, default: nil, desc: "Skip migrations"
18
18
 
19
19
  # Generates monolithic migration file that contains all database changes.
20
20
  def create_migration_file
21
21
  return if options[:skip_migrations]
22
22
 
23
- migration_template 'create_solid_errors_tables.rb.erb', File.join(db_migrate_path, "create_solid_errors_tables.rb")
23
+ migration_template "create_solid_errors_tables.rb.erb", File.join(db_migrate_path, "create_solid_errors_tables.rb")
24
24
  end
25
25
 
26
26
  private
@@ -1,11 +1,11 @@
1
1
  module SolidErrors
2
2
  # adapted from: https://github.com/honeybadger-io/honeybadger-ruby/blob/master/lib/honeybadger/util/sanitizer.rb
3
3
  class Sanitizer
4
- BASIC_OBJECT = '#<BasicObject>'.freeze
5
- DEPTH = '[DEPTH]'.freeze
6
- RAISED = '[RAISED]'.freeze
7
- RECURSION = '[RECURSION]'.freeze
8
- TRUNCATED = '[TRUNCATED]'.freeze
4
+ BASIC_OBJECT = "#<BasicObject>".freeze
5
+ DEPTH = "[DEPTH]".freeze
6
+ RAISED = "[RAISED]".freeze
7
+ RECURSION = "[RECURSION]".freeze
8
+ TRUNCATED = "[TRUNCATED]".freeze
9
9
  MAX_STRING_SIZE = 65536
10
10
 
11
11
  def self.sanitize(data)
@@ -21,7 +21,7 @@ module SolidErrors
21
21
  return BASIC_OBJECT if basic_object?(data)
22
22
 
23
23
  if recursive?(data)
24
- return RECURSION if stack && stack.include?(data.object_id)
24
+ return RECURSION if stack&.include?(data.object_id)
25
25
 
26
26
  stack = stack ? stack.dup : Set.new
27
27
  stack << data.object_id
@@ -33,8 +33,8 @@ module SolidErrors
33
33
 
34
34
  new_hash = {}
35
35
  data.each do |key, value|
36
- key = key.kind_of?(Symbol) ? key : sanitize(key, depth+1, stack)
37
- value = sanitize(value, depth+1, stack)
36
+ key = key.is_a?(Symbol) ? key : sanitize(key, depth + 1, stack)
37
+ value = sanitize(value, depth + 1, stack)
38
38
  new_hash[key] = value
39
39
  end
40
40
  new_hash
@@ -42,7 +42,7 @@ module SolidErrors
42
42
  return DEPTH if depth >= max_depth
43
43
 
44
44
  data.to_a.map do |value|
45
- sanitize(value, depth+1, stack)
45
+ sanitize(value, depth + 1, stack)
46
46
  end
47
47
  when Numeric, TrueClass, FalseClass, NilClass
48
48
  data
@@ -64,28 +64,29 @@ module SolidErrors
64
64
  end
65
65
 
66
66
  private
67
- attr_reader :max_depth
68
-
69
- def basic_object?(object)
70
- object.respond_to?(:to_s)
71
- false
72
- rescue
73
- # BasicObject doesn't respond to `#respond_to?`.
74
- true
75
- end
76
67
 
77
- def recursive?(data)
78
- data.is_a?(Hash) || data.is_a?(Array) || data.is_a?(Set)
79
- end
68
+ attr_reader :max_depth
80
69
 
81
- def sanitize_string(string)
82
- string.gsub!(/#<(.*?):0x.*?>/, '#<\1>') # remove object_id
83
- return string unless string.respond_to?(:size) && string.size > MAX_STRING_SIZE
84
- string[0...MAX_STRING_SIZE] + TRUNCATED
85
- end
70
+ def basic_object?(object)
71
+ object.respond_to?(:to_s)
72
+ false
73
+ rescue
74
+ # BasicObject doesn't respond to `#respond_to?`.
75
+ true
76
+ end
86
77
 
87
- def inspected?(string)
88
- String(string) =~ /#<.*>/
89
- end
78
+ def recursive?(data)
79
+ data.is_a?(Hash) || data.is_a?(Array) || data.is_a?(Set)
80
+ end
81
+
82
+ def sanitize_string(string)
83
+ string.gsub!(/#<(.*?):0x.*?>/, '#<\1>') # remove object_id
84
+ return string unless string.respond_to?(:size) && string.size > MAX_STRING_SIZE
85
+ string[0...MAX_STRING_SIZE] + TRUNCATED
86
+ end
87
+
88
+ def inspected?(string)
89
+ String(string) =~ /#<.*>/
90
+ end
90
91
  end
91
92
  end
@@ -1,26 +1,25 @@
1
1
  module SolidErrors
2
2
  class Subscriber
3
- IGNORED_ERRORS = ['ActionController::RoutingError',
4
- 'AbstractController::ActionNotFound',
5
- 'ActionController::MethodNotAllowed',
6
- 'ActionController::UnknownHttpMethod',
7
- 'ActionController::NotImplemented',
8
- 'ActionController::UnknownFormat',
9
- 'ActionController::InvalidAuthenticityToken',
10
- 'ActionController::InvalidCrossOriginRequest',
11
- 'ActionDispatch::Http::Parameters::ParseError',
12
- 'ActionController::BadRequest',
13
- 'ActionController::ParameterMissing',
14
- 'ActiveRecord::RecordNotFound',
15
- 'ActionController::UnknownAction',
16
- 'ActionDispatch::Http::MimeNegotiation::InvalidType',
17
- 'Rack::QueryParser::ParameterTypeError',
18
- 'Rack::QueryParser::InvalidParameterError',
19
- 'CGI::Session::CookieStore::TamperedWithCookie',
20
- 'Mongoid::Errors::DocumentNotFound',
21
- 'Sinatra::NotFound',
22
- 'Sidekiq::JobRetry::Skip'].map(&:freeze).freeze
23
-
3
+ IGNORED_ERRORS = ["ActionController::RoutingError",
4
+ "AbstractController::ActionNotFound",
5
+ "ActionController::MethodNotAllowed",
6
+ "ActionController::UnknownHttpMethod",
7
+ "ActionController::NotImplemented",
8
+ "ActionController::UnknownFormat",
9
+ "ActionController::InvalidAuthenticityToken",
10
+ "ActionController::InvalidCrossOriginRequest",
11
+ "ActionDispatch::Http::Parameters::ParseError",
12
+ "ActionController::BadRequest",
13
+ "ActionController::ParameterMissing",
14
+ "ActiveRecord::RecordNotFound",
15
+ "ActionController::UnknownAction",
16
+ "ActionDispatch::Http::MimeNegotiation::InvalidType",
17
+ "Rack::QueryParser::ParameterTypeError",
18
+ "Rack::QueryParser::InvalidParameterError",
19
+ "CGI::Session::CookieStore::TamperedWithCookie",
20
+ "Mongoid::Errors::DocumentNotFound",
21
+ "Sinatra::NotFound",
22
+ "Sidekiq::JobRetry::Skip"].map(&:freeze).freeze
24
23
 
25
24
  def report(error, handled:, severity:, context:, source: nil)
26
25
  return if ignore_by_class?(error.class.name)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidErrors
4
- VERSION = "0.2.16"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/solid_errors.rb CHANGED
@@ -7,4 +7,20 @@ require_relative "solid_errors/engine"
7
7
 
8
8
  module SolidErrors
9
9
  mattr_accessor :connects_to
10
+ mattr_accessor :username
11
+ mattr_accessor :password
12
+
13
+ class << self
14
+ # use method instead of attr_accessor to ensure
15
+ # this works if variable set after SolidErrors is loaded
16
+ def username
17
+ @username ||= ENV["SOLIDERRORS_USERNAME"]
18
+ end
19
+
20
+ # use method instead of attr_accessor to ensure
21
+ # this works if variable set after SolidErrors is loaded
22
+ def password
23
+ @password ||= ENV["SOLIDERRORS_PASSWORD"]
24
+ end
25
+ end
10
26
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solid_errors
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.16
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Margheim
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-01-15 00:00:00.000000000 Z
11
+ date: 2024-01-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '7.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sqlite3
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  description:
28
42
  email:
29
43
  - stephen.margheim@gmail.com