solid_errors 0.4.3 → 0.6.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: 0a881ce47539838e01f1c118484b72ca112c5dd5abd87452113d3f74c4539746
4
- data.tar.gz: 25c83eacccc50d1c54461ded048dab4b89a86bad71d6963e292af3e6f1cc49b2
3
+ metadata.gz: eb0e5daf78d8b51d88fd8e153e81c76cf7714fb622ab1257f32ae6e129e09114
4
+ data.tar.gz: 8ef8871cfa529fd24a008f0d6d9c013a741c412d731b103a4bc6e181515305c7
5
5
  SHA512:
6
- metadata.gz: 0adf72d51727ed89a8d8cc27d9145cbfc4e95e7d16d951a5c3849405ab957d6633ffdf84787c492cfb10a37efaf674ed75480404a18640e46e56498c1179b307
7
- data.tar.gz: beaedce40a2d77577b2c8dc27fc0dd228f39c8d6090510a55538e92a144e845ec781f01d5bc093405c2d493c8d41f470ada4502eaee2582d7ba5c8c80c2ead84
6
+ metadata.gz: f20c5fda185ac458cdcabd3a7d451f049144fe570c0c5c3fa5594296a832e5f217199167bbc94abfa2d5a0e00915eb86e03bff674d999ab242108d5451194591
7
+ data.tar.gz: 969d7c226e9be6f9ab75ca98713ba241430fae9a6c713c7834034d37ae3076ecc65af2becef30b29cef52a74fab9b6aff8682d21d0a8d875d97433c96ed86dfd
data/README.md CHANGED
@@ -49,7 +49,40 @@ After installing the gem, run the installer:
49
49
  $ rails generate solid_errors:install
50
50
  ```
51
51
 
52
- This will copy the required migration over to your app.
52
+ This will create the `db/errors_schema.rb` file.
53
+
54
+ You will then have to add the configuration for the errors database in `config/database.yml`. If you're using sqlite, it'll look something like this:
55
+
56
+ ```yaml
57
+ production:
58
+ primary:
59
+ <<: *default
60
+ database: storage/production.sqlite3
61
+ errors:
62
+ <<: *default
63
+ database: storage/production_errors.sqlite3
64
+ migrations_paths: db/errors_migrate
65
+ ```
66
+
67
+ ...or if you're using MySQL/PostgreSQL/Trilogy:
68
+
69
+ ```yaml
70
+ production:
71
+ primary: &primary_production
72
+ <<: *default
73
+ database: app_production
74
+ username: app
75
+ password: <%= ENV["APP_DATABASE_PASSWORD"] %>
76
+ errors:
77
+ <<: *primary_production
78
+ database: app_production_errors
79
+ migrations_paths: db/errors_migrate
80
+ ```
81
+
82
+ > [!NOTE]
83
+ > Calling `bin/rails solid_errors:install` will automatically add `config.solid_errors.connects_to = { database: { writing: :errors } }` to `config/environments/production.rb`, so no additional configuration is needed there (although you must make sure that you use the errors name in database.yml for this to match!). But if you want to use Solid Queue in a different environment (like staging or even development), you'll have to manually add that `config.solid_errors.connects_to` line to the respective environment file. And, as always, make sure that the name you're using for the database in `config/database.yml` matches the name you use in `config.solid_errors.connects_to`.
84
+
85
+ Then run `db:prepare` in production to ensure the database is created and the schema is loaded.
53
86
 
54
87
  Then mount the engine in your `config/routes.rb` file:
55
88
  ```ruby
@@ -69,6 +102,54 @@ Please consult the [official guides](https://guides.rubyonrails.org/error_report
69
102
 
70
103
  There are intentionally few features; you can view and resolve errors. That’s it. The goal is to provide a simple, lightweight, and performant solution for tracking exceptions in your Rails application. If you need more features, you should probably use a 3rd party service like [Honeybadger](https://www.honeybadger.io/), whose MIT-licensed [Ruby agent gem](https://github.com/honeybadger-io/honeybadger-ruby) provided a couple of critical pieces of code for this project.
71
104
 
105
+ ### Manually reporting an Error
106
+
107
+ Errors can be added to Solid Errors via the [Rails error reporter](https://guides.rubyonrails.org/error_reporting.html).
108
+
109
+ There are [three ways](https://guides.rubyonrails.org/error_reporting.html#using-the-error-reporter) you can use the error reporter:
110
+
111
+ `Rails.error.handle` will report any error raised within the block. It will then swallow the error, and the rest of your code outside the block will continue as normal.
112
+
113
+ ```ruby
114
+ result = Rails.error.handle do
115
+ 1 + '1' # raises TypeError
116
+ end
117
+ result # => nil
118
+ 1 + 1 # This will be executed
119
+ ```
120
+
121
+ `Rails.error.record` will report errors to all registered subscribers and then re-raise the error, meaning that the rest of your code won't execute.
122
+
123
+ ```ruby
124
+ Rails.error.record do
125
+ 1 + '1' # raises TypeError
126
+ end
127
+ 1 + 1 # This won't be executed
128
+ ```
129
+
130
+ You can also manually report errors by calling `Rails.error.report`:
131
+
132
+ ```ruby
133
+ begin
134
+ # code
135
+ rescue StandardError => e
136
+ Rails.error.report(e)
137
+ end
138
+ ```
139
+
140
+ All 3 reporting APIs (`#handle`, `#record`, and `#report`) support the following options, which are then passed along to all registered subscribers:
141
+
142
+ * `handled`: a Boolean to indicate if the error was handled. This is set to `true` by default. `#record` sets this to `false`.
143
+ * `severity`: a Symbol describing the severity of the error. Expected values are: `:error`, `:warning`, and `:info`. `#handle` sets this to `:warning`, while `#record` sets it to `:error`.
144
+ * `context`: a Hash to provide more context about the error, like request or user details
145
+ * `source`: a String about the source of the error. The default source is `"application"`. Errors reported by internal libraries may set other sources; the Redis cache library may use "redis_cache_store.active_support", for instance. Your subscriber can use the source to ignore errors you aren't interested in.
146
+
147
+ ```ruby
148
+ Rails.error.handle(context: { user_id: user.id }, severity: :info) do
149
+ # ...
150
+ end
151
+ ```
152
+
72
153
  ### Configuration
73
154
 
74
155
  You can configure Solid Errors via the Rails configuration object, under the `solid_errors` key. Currently, 6 configuration options are available:
@@ -2,14 +2,16 @@ module SolidErrors
2
2
  class ErrorsController < ApplicationController
3
3
  around_action :force_english_locale!
4
4
 
5
- before_action :set_error, only: %i[show update]
5
+ before_action :set_error, only: %i[show update destroy]
6
+
7
+ helper_method :error_scope
6
8
 
7
9
  # GET /errors
8
10
  def index
9
11
  errors_table = Error.arel_table
10
12
  occurrences_table = Occurrence.arel_table
11
-
12
- @errors = Error.unresolved
13
+ query_scope = error_scope.resolved? ? Error.resolved : Error.unresolved
14
+ @errors = query_scope
13
15
  .joins(:occurrences)
14
16
  .select(errors_table[Arel.star],
15
17
  occurrences_table[:created_at].maximum.as("recent_occurrence"),
@@ -30,6 +32,16 @@ module SolidErrors
30
32
  redirect_to errors_path, notice: "Error marked as resolved."
31
33
  end
32
34
 
35
+ # DELETE /errors/1
36
+ def destroy
37
+ if @error.resolved?
38
+ @error.destroy
39
+ redirect_to errors_path(scope: :resolved), notice: "Error deleted."
40
+ else
41
+ redirect_to error_path(@error), alert: "You must resolve the error before deleting it."
42
+ end
43
+ end
44
+
33
45
  private
34
46
 
35
47
  # Only allow a list of trusted parameters through.
@@ -44,5 +56,9 @@ module SolidErrors
44
56
  def force_english_locale!(&action)
45
57
  I18n.with_locale(:en, &action)
46
58
  end
59
+
60
+ def error_scope
61
+ ActiveSupport::StringInquirer.new(params[:scope] || "unresolved")
62
+ end
47
63
  end
48
64
  end
@@ -6,7 +6,7 @@ module SolidErrors
6
6
  @error = occurrence.error
7
7
 
8
8
  mail(
9
- subject: "#{@error.emoji} #{@error.exception_class}",
9
+ subject: "#{@error.severity_emoji} #{@error.exception_class}",
10
10
  from: SolidErrors.email_from,
11
11
  to: SolidErrors.email_to
12
12
  )
@@ -12,6 +12,14 @@ module SolidErrors
12
12
  warning: "bg-yellow-100 text-yellow-800",
13
13
  info: "bg-blue-100 text-blue-800"
14
14
  }
15
+ STATUS_TO_EMOJI = {
16
+ resolved: "✅",
17
+ unresolved: "⏳"
18
+ }
19
+ STATUS_TO_BADGE_CLASSES = {
20
+ resolved: "bg-green-100 text-green-800",
21
+ unresolved: "bg-violet-100 text-violet-800"
22
+ }
15
23
 
16
24
  has_many :occurrences, class_name: "SolidErrors::Occurrence", dependent: :destroy
17
25
 
@@ -22,12 +30,28 @@ module SolidErrors
22
30
  scope :resolved, -> { where.not(resolved_at: nil) }
23
31
  scope :unresolved, -> { where(resolved_at: nil) }
24
32
 
25
- def emoji
33
+ def severity_emoji
26
34
  SEVERITY_TO_EMOJI[severity.to_sym]
27
35
  end
28
36
 
29
- def badge_classes
37
+ def severity_badge_classes
30
38
  "px-2 inline-flex text-sm font-semibold rounded-md #{SEVERITY_TO_BADGE_CLASSES[severity.to_sym]}"
31
39
  end
40
+
41
+ def status
42
+ resolved? ? :resolved : :unresolved
43
+ end
44
+
45
+ def status_emoji
46
+ STATUS_TO_EMOJI[status]
47
+ end
48
+
49
+ def status_badge_classes
50
+ "px-2 inline-flex text-sm font-semibold rounded-md #{STATUS_TO_BADGE_CLASSES[status]}"
51
+ end
52
+
53
+ def resolved?
54
+ resolved_at.present?
55
+ end
32
56
  end
33
57
  end
@@ -617,6 +617,10 @@
617
617
  margin-bottom: 0.75rem
618
618
  }
619
619
 
620
+ .mb-6{
621
+ margin-bottom: 1.5rem
622
+ }
623
+
620
624
  .ml-6{
621
625
  margin-left: 1.5rem
622
626
  }
@@ -629,6 +633,10 @@
629
633
  margin-top: 1rem
630
634
  }
631
635
 
636
+ .-mb-px {
637
+ margin-bottom: -1px;
638
+ }
639
+
632
640
  .inline-block{
633
641
  display: inline-block
634
642
  }
@@ -743,6 +751,12 @@
743
751
  margin-bottom: calc(1.5rem * var(--tw-space-y-reverse))
744
752
  }
745
753
 
754
+ .space-x-8 > :not([hidden]) ~ :not([hidden]) {
755
+ --tw-space-x-reverse: 0;
756
+ margin-right: calc(2rem * var(--tw-space-x-reverse));
757
+ margin-left: calc(2rem * calc(1 - var(--tw-space-x-reverse)));
758
+ }
759
+
746
760
  .divide-y > :not([hidden]) ~ :not([hidden]){
747
761
  --tw-divide-y-reverse: 0;
748
762
  border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
@@ -770,6 +784,10 @@
770
784
  border-radius: 0.25rem
771
785
  }
772
786
 
787
+ .rounded-md{
788
+ border-radius: 0.375rem
789
+ }
790
+
773
791
  .rounded-lg{
774
792
  border-radius: 0.5rem
775
793
  }
@@ -787,6 +805,10 @@
787
805
  border-bottom-width: 1px
788
806
  }
789
807
 
808
+ .border-b-2 {
809
+ border-bottom-width: 2px;
810
+ }
811
+
790
812
  .border-black{
791
813
  --tw-border-opacity: 1;
792
814
  border-color: rgb(0 0 0 / var(--tw-border-opacity))
@@ -802,6 +824,15 @@
802
824
  border-color: rgb(209 213 219 / var(--tw-border-opacity))
803
825
  }
804
826
 
827
+ .border-red-500{
828
+ --tw-border-opacity: 1;
829
+ border-color: rgb(239 68 68 / var(--tw-border-opacity))
830
+ }
831
+
832
+ .border-transparent {
833
+ border-color: transparent;
834
+ }
835
+
805
836
  .bg-gray-100{
806
837
  --tw-bg-opacity: 1;
807
838
  background-color: rgb(243 244 246 / var(--tw-bg-opacity))
@@ -848,6 +879,11 @@
848
879
  padding-right: 0px
849
880
  }
850
881
 
882
+ .px-1 {
883
+ padding-left: 0.25rem;
884
+ padding-right: 0.25rem;
885
+ }
886
+
851
887
  .px-2{
852
888
  padding-left: 0.5rem;
853
889
  padding-right: 0.5rem
@@ -916,10 +952,6 @@
916
952
  padding-top: 1.75rem
917
953
  }
918
954
 
919
- .rounded-md{
920
- border-radius: 0.375rem
921
- }
922
-
923
955
  .text-left{
924
956
  text-align: left
925
957
  }
@@ -931,7 +963,7 @@
931
963
  .text-right{
932
964
  text-align: right
933
965
  }
934
-
966
+
935
967
  .text-black{
936
968
  --tw-text-opacity: 1;
937
969
  color: rgb(0 0 0 / var(--tw-text-opacity))
@@ -987,6 +1019,16 @@
987
1019
  background-color: rgb(254 249 195 / var(--tw-bg-opacity))
988
1020
  }
989
1021
 
1022
+ .bg-green-100{
1023
+ --tw-bg-opacity: 1;
1024
+ background-color: rgb(220 252 231 / var(--tw-bg-opacity))
1025
+ }
1026
+
1027
+ .bg-violet-100{
1028
+ --tw-bg-opacity: 1;
1029
+ background-color: rgb(237 233 254 / var(--tw-bg-opacity))
1030
+ }
1031
+
990
1032
  .text-blue-400{
991
1033
  --tw-text-opacity: 1;
992
1034
  color: rgb(96 165 250 / var(--tw-text-opacity))
@@ -1037,6 +1079,16 @@
1037
1079
  color: rgb(133 77 14 / var(--tw-text-opacity))
1038
1080
  }
1039
1081
 
1082
+ .text-green-800{
1083
+ --tw-text-opacity: 1;
1084
+ color: rgb(22 101 52 / var(--tw-text-opacity))
1085
+ }
1086
+
1087
+ .text-violet-800{
1088
+ --tw-text-opacity: 1;
1089
+ color: rgb(91 33 182 / var(--tw-text-opacity))
1090
+ }
1091
+
1040
1092
  .text-white{
1041
1093
  --tw-text-opacity: 1;
1042
1094
  color: rgb(255 255 255 / var(--tw-text-opacity))
@@ -1046,6 +1098,14 @@
1046
1098
  text-decoration-line: underline
1047
1099
  }
1048
1100
 
1101
+ .uppercase{
1102
+ text-transform: uppercase
1103
+ }
1104
+
1105
+ .whitespace-pre-wrap{
1106
+ white-space: pre-wrap
1107
+ }
1108
+
1049
1109
  .transition-opacity{
1050
1110
  transition-property: opacity;
1051
1111
  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
@@ -1082,6 +1142,11 @@
1082
1142
  --tw-ring-color: rgb(229 231 235 / var(--tw-ring-opacity))
1083
1143
  }
1084
1144
 
1145
+ .hover\:ring-red-200:hover{
1146
+ --tw-ring-opacity: 1;
1147
+ --tw-ring-color: rgb(254 202 202 / var(--tw-ring-opacity))
1148
+ }
1149
+
1085
1150
  .hover\:bg-black:hover{
1086
1151
  --tw-bg-opacity: 1;
1087
1152
  background-color: rgb(0 0 0 / var(--tw-bg-opacity))
@@ -1092,6 +1157,16 @@
1092
1157
  color: rgb(255 255 255 / var(--tw-text-opacity))
1093
1158
  }
1094
1159
 
1160
+ .hover\:text-gray-700:hover {
1161
+ --tw-text-opacity: 1;
1162
+ color: rgb(55 65 81 / var(--tw-text-opacity));
1163
+ }
1164
+
1165
+ .hover\:border-gray-300:hover {
1166
+ --tw-border-opacity: 1;
1167
+ border-color: rgb(209 213 219 / var(--tw-border-opacity));
1168
+ }
1169
+
1095
1170
  @media (min-width: 640px){
1096
1171
  .sm\:mx-0{
1097
1172
  margin-left: 0px;
@@ -1,6 +1,6 @@
1
1
  <html>
2
2
  <head>
3
- <title>Solid Errors | <%= @error.emoji %> <%= @error.exception_class %></title>
3
+ <title>Solid Errors | <%= @error.severity_emoji %> <%= @error.exception_class %></title>
4
4
  <%= render "layouts/solid_errors/style" %>
5
5
  </head>
6
6
  <body>
@@ -1,16 +1,13 @@
1
1
  <div class="inline-flex items-center justify-start flex-wrap gap-3 whitespace-nowrap">
2
- <%= link_to errors_path, class: "inline-flex items-center justify-center gap-2 font-medium cursor-pointer border rounded-lg py-3 px-5 bg-gray-100 text-initial border-gray-300 hover:ring-gray-200 hover:ring-8" do %>
2
+ <%= link_to errors_path(scope: error.resolved? ? :resolved : :unresolved), class: "inline-flex items-center justify-center gap-2 font-medium cursor-pointer border rounded-lg py-3 px-5 bg-gray-100 text-initial border-gray-300 hover:ring-gray-200 hover:ring-8" do %>
3
3
  <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-left" viewBox="0 0 16 16">
4
4
  <path fill-rule="evenodd" d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8"/>
5
5
  </svg>
6
6
  <span>Back to errors</span>
7
7
  <% end %>
8
-
9
- <%= 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 %>
10
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check2-circle" viewBox="0 0 16 16">
11
- <path d="M2.5 8a5.5 5.5 0 0 1 8.25-4.764.5.5 0 0 0 .5-.866A6.5 6.5 0 1 0 14.5 8a.5.5 0 0 0-1 0 5.5 5.5 0 1 1-11 0"/>
12
- <path d="M15.354 3.354a.5.5 0 0 0-.708-.708L8 9.293 5.354 6.646a.5.5 0 1 0-.708.708l3 3a.5.5 0 0 0 .708 0z"/>
13
- </svg>
14
- <span>Resolve Error<span class="sr-only"> #<%= error.id %></span></span>
8
+ <% if error.resolved? %>
9
+ <%= render 'solid_errors/errors/delete_button', error: error %>
10
+ <% else %>
11
+ <%= render 'solid_errors/errors/resolve_button', error: error %>
15
12
  <% end %>
16
13
  </div>
@@ -0,0 +1,7 @@
1
+ <%# locals: (error:) -%>
2
+ <%= button_to error_path(error), method: :delete, class: "inline-flex items-center justify-center gap-2 font-medium cursor-pointer border rounded-lg py-3 px-5 bg-transparent text-red-500 border-red-500 hover:ring-red-200 hover:ring-8" do %>
3
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" class="bi bi-trash3" fill="currentColor" viewBox="0 0 16 16">
4
+ <path d="M6.5 1h3a.5.5 0 0 1 .5.5v1H6v-1a.5.5 0 0 1 .5-.5M11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3A1.5 1.5 0 0 0 5 1.5v1H1.5a.5.5 0 0 0 0 1h.538l.853 10.66A2 2 0 0 0 4.885 16h6.23a2 2 0 0 0 1.994-1.84l.853-10.66h.538a.5.5 0 0 0 0-1zm1.958 1-.846 10.58a1 1 0 0 1-.997.92h-6.23a1 1 0 0 1-.997-.92L3.042 3.5zm-7.487 1a.5.5 0 0 1 .528.47l.5 8.5a.5.5 0 0 1-.998.06L5 5.03a.5.5 0 0 1 .47-.53Zm5.058 0a.5.5 0 0 1 .47.53l-.5 8.5a.5.5 0 1 1-.998-.06l.5-8.5a.5.5 0 0 1 .528-.47M8 4.5a.5.5 0 0 1 .5.5v8.5a.5.5 0 0 1-1 0V5a.5.5 0 0 1 .5-.5"/>
5
+ </svg>
6
+ <span>Delete <span class="sr-only">, Error #<%= error.id %></span></span>
7
+ <% end %>
@@ -3,7 +3,7 @@
3
3
  <%= tag.section id: dom_id(error), class: "space-y-6" do %>
4
4
  <div class="flex justify-between items-center">
5
5
  <%= tag.h1 class: "font-bold flex items-center text-2xl gap-2" do %>
6
- <%= error.emoji %>
6
+ <%= error.severity_emoji %>
7
7
  <code><%= error.exception_class %></code>
8
8
  from
9
9
  <em><code><%= error.source %></code></em>
@@ -14,9 +14,31 @@
14
14
  </small>
15
15
  </div>
16
16
 
17
- <pre class=""><%= error.message %></pre>
17
+ <pre class="whitespace-pre-wrap"><%= error.message %></pre>
18
18
 
19
19
  <dl class="flex-1 grid grid-cols-2 gap-x-4">
20
+ <div class="flex items-center justify-between flex-wrap gap-x-2">
21
+ <dt class="font-bold">
22
+ <%= SolidErrors::Error.human_attribute_name(:severity) %>
23
+ </dt>
24
+ <dd class="inline-flex items-center gap-1">
25
+ <%= error.severity_emoji %>
26
+ <span class="<%= error.severity_badge_classes %>">
27
+ <%= error.severity %>
28
+ </span>
29
+ </dd>
30
+ </div>
31
+ <div class="flex items-center justify-between flex-wrap gap-x-2">
32
+ <dt class="font-bold">
33
+ <%= SolidErrors::Error.human_attribute_name(:status) %>
34
+ </dt>
35
+ <dd class="inline-flex items-center gap-1">
36
+ <%= error.status_emoji %>
37
+ <span class="<%= error.status_badge_classes %>">
38
+ <%= error.status %>
39
+ </span>
40
+ </dd>
41
+ </div>
20
42
  <div class="flex items-center justify-between flex-wrap gap-x-2">
21
43
  <dt class="font-bold">
22
44
  <%= SolidErrors::Error.human_attribute_name(:first_seen) %>
@@ -41,14 +63,6 @@
41
63
  </abbr>
42
64
  </dd>
43
65
  </div>
44
- <div class="flex items-center justify-between flex-wrap gap-x-2">
45
- <dt class="font-bold">
46
- <%= SolidErrors::Error.human_attribute_name(:occurrences) %>
47
- </dt>
48
- <dd class="inline-flex items-center gap-1">
49
- <%= error.occurrences.size %>
50
- </dd>
51
- </div>
52
66
  <div class="flex items-center justify-between flex-wrap gap-x-2">
53
67
  <dt class="font-bold">
54
68
  <%= SolidErrors::Error.human_attribute_name(:exception_class) %>
@@ -57,17 +71,6 @@
57
71
  <code><%= error.exception_class %></code>
58
72
  </dd>
59
73
  </div>
60
- <div class="flex items-center justify-between flex-wrap gap-x-2">
61
- <dt class="font-bold">
62
- <%= SolidErrors::Error.human_attribute_name(:severity) %>
63
- </dt>
64
- <dd class="inline-flex items-center gap-1">
65
- <%= error.emoji %>
66
- <span class="<%= error.badge_classes %>">
67
- <%= error.severity %>
68
- </span>
69
- </dd>
70
- </div>
71
74
  <div class="flex items-center justify-between flex-wrap gap-x-2">
72
75
  <dt class="font-bold">
73
76
  <%= SolidErrors::Error.human_attribute_name(:source) %>
@@ -0,0 +1,8 @@
1
+ <%# locals: (error:) -%>
2
+ <%= 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 %>
3
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check2-circle" viewBox="0 0 16 16">
4
+ <path d="M2.5 8a5.5 5.5 0 0 1 8.25-4.764.5.5 0 0 0 .5-.866A6.5 6.5 0 1 0 14.5 8a.5.5 0 0 0-1 0 5.5 5.5 0 1 1-11 0"/>
5
+ <path d="M15.354 3.354a.5.5 0 0 0-.708-.708L8 9.293 5.354 6.646a.5.5 0 1 0-.708.708l3 3a.5.5 0 0 0 .708 0z"/>
6
+ </svg>
7
+ <span>Resolve <span class="sr-only">, Error #<%= error.id %></span></span>
8
+ <% end %>
@@ -1,7 +1,8 @@
1
+ <%# locals: (error:) -%>
1
2
  <tr class="even:bg-gray-50 align-top">
2
3
  <td scope="col" class="whitespace-wrap py-4 pl-4 pr-3 font-medium text-gray-900 sm:pl-3">
3
4
  <div>
4
- <%= error.emoji %>
5
+ <%= error.severity_emoji %>
5
6
  <%= link_to error_path(error), class: "text-blue-400 underline inline-flex items-baseline gap-1" do %>
6
7
  <strong><code><%= error.exception_class %></code></strong>
7
8
  <% end %>
@@ -20,12 +21,10 @@
20
21
  </abbr>
21
22
  </td>
22
23
  <td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-3">
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 %>
24
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check2-circle" viewBox="0 0 16 16">
25
- <path d="M2.5 8a5.5 5.5 0 0 1 8.25-4.764.5.5 0 0 0 .5-.866A6.5 6.5 0 1 0 14.5 8a.5.5 0 0 0-1 0 5.5 5.5 0 1 1-11 0"/>
26
- <path d="M15.354 3.354a.5.5 0 0 0-.708-.708L8 9.293 5.354 6.646a.5.5 0 1 0-.708.708l3 3a.5.5 0 0 0 .708 0z"/>
27
- </svg>
28
- <span>Resolve<span class="sr-only">, Error #<%= error.id %></span></span>
24
+ <% if error.resolved? %>
25
+ <%= render 'solid_errors/errors/delete_button', error: error %>
26
+ <% else %>
27
+ <%= render 'solid_errors/errors/resolve_button', error: error %>
29
28
  <% end %>
30
29
  </td>
31
30
  </tr>
@@ -1,3 +1,25 @@
1
+ <div class="mb-6 border-b border-gray-300 flex justify-between items-baseline">
2
+ <h1 class="font-bold flex items-center text-2xl gap-2">
3
+ Errors
4
+ </h1>
5
+ <nav class="-mb-px flex space-x-8" aria-label="Tabs">
6
+ <%= link_to errors_path(scope: :unresolved), class: class_names(
7
+ "inline-flex items-center border-b-2 px-1 py-4 text-sm font-medium",
8
+ "border-blue-500 text-blue-500" => !error_scope.resolved?,
9
+ "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700" => error_scope.resolved?) do %>
10
+ <span class="mr-2"><%= SolidErrors::Error::STATUS_TO_EMOJI[:unresolved] %></span>
11
+ <span>Unresolved</span>
12
+ <% end %>
13
+ <%= link_to errors_path(scope: :resolved), class: class_names(
14
+ "inline-flex items-center border-b-2 px-1 py-4 text-sm font-medium",
15
+ "border-blue-500 text-blue-500" => error_scope.resolved?,
16
+ "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700" => !error_scope.resolved?) do %>
17
+ <span class="mr-2"><%= SolidErrors::Error::STATUS_TO_EMOJI[:resolved] %></span>
18
+ <span>Resolved</span>
19
+ <% end %>
20
+ </nav>
21
+ </div>
22
+
1
23
  <table class="min-w-full divide-y divide-gray-300">
2
24
  <thead>
3
25
  <tr>
data/config/routes.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  SolidErrors::Engine.routes.draw do
2
- get "/" => "errors#index", :as => :root
2
+ get "/", to: "errors#index", as: :root
3
3
 
4
- resources :errors, only: [:index, :show, :update], path: ""
4
+ resources :errors, only: [:index, :show, :update, :destroy], path: ""
5
5
  end
@@ -5,4 +5,4 @@ Example:
5
5
  bin/rails generate solid_errors:install
6
6
 
7
7
  This will perform the following:
8
- Installs solid_errors migrations
8
+ Adds solid_errors db schema
@@ -1,32 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rails/generators"
4
- require "rails/generators/active_record"
5
-
6
3
  module SolidErrors
7
4
  #
8
5
  # Rails generator used for setting up SolidErrors in a Rails application.
9
6
  # Run it with +bin/rails g solid_errors:install+ in your console.
10
7
  #
11
8
  class InstallGenerator < Rails::Generators::Base
12
- include ActiveRecord::Generators::Migration
13
-
14
9
  source_root File.expand_path("templates", __dir__)
15
10
 
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
- class_option :skip_migrations, type: :boolean, default: nil, desc: "Skip migrations"
18
-
19
- # Generates monolithic migration file that contains all database changes.
20
- def create_migration_file
21
- return if options[:skip_migrations]
22
-
23
- migration_template "create_solid_errors_tables.rb.erb", File.join(db_migrate_path, "create_solid_errors_tables.rb")
11
+ def add_solid_errors_db_schema
12
+ template "db/errors_schema.rb"
24
13
  end
25
14
 
26
- private
27
-
28
- def migration_version
29
- "[#{ActiveRecord::VERSION::STRING.to_f}]"
15
+ def configure_solid_errors
16
+ insert_into_file Pathname(destination_root).join("config/environments/production.rb"), after: /^([ \t]*).*?(?=\nend)$/ do
17
+ [
18
+ "",
19
+ '\1# Configure Solid Errors',
20
+ '\1config.solid_errors.connects_to = { database: { writing: :errors } }',
21
+ '\1config.solid_errors.send_emails = true',
22
+ '\1config.solid_errors.email_from = ""',
23
+ '\1config.solid_errors.email_to = ""',
24
+ '\1config.solid_errors.username = Rails.application.credentials.dig(:solid_errors, :username)',
25
+ '\1config.solid_errors.password = Rails.application.credentials.dig(:solid_errors, :password)',
26
+ ].join("\n")
27
+ end
30
28
  end
31
29
  end
32
30
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ ActiveRecord::Schema[7.1].define(version: 1) do
4
+ create_table "solid_errors", force: :cascade do |t|
5
+ t.text "exception_class", null: false
6
+ t.text "message", null: false
7
+ t.text "severity", null: false
8
+ t.text "source"
9
+ t.datetime "resolved_at"
10
+ t.string "fingerprint", limit: 64, null: false
11
+ t.datetime "created_at", null: false
12
+ t.datetime "updated_at", null: false
13
+ t.index ["fingerprint"], name: "index_solid_errors_on_fingerprint", unique: true
14
+ t.index ["resolved_at"], name: "index_solid_errors_on_resolved_at"
15
+ end
16
+
17
+ create_table "solid_errors_occurrences", force: :cascade do |t|
18
+ t.integer "error_id", null: false
19
+ t.text "backtrace"
20
+ t.json "context"
21
+ t.datetime "created_at", null: false
22
+ t.datetime "updated_at", null: false
23
+ t.index ["error_id"], name: "index_solid_errors_occurrences_on_error_id"
24
+ end
25
+
26
+ add_foreign_key "solid_errors_occurrences", "solid_errors", column: "error_id"
27
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidErrors
4
- VERSION = "0.4.3"
4
+ VERSION = "0.6.0"
5
5
  end
metadata CHANGED
@@ -1,97 +1,97 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solid_errors
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
4
+ version: 0.6.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-06-21 00:00:00.000000000 Z
11
+ date: 2024-09-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionmailer
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '7.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '7.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: actionpack
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '7.0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '7.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: actionview
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '7.0'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '7.0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: activerecord
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
61
  version: '7.0'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '7.0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: activesupport
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - "~>"
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
75
  version: '7.0'
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - "~>"
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '7.0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: railties
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - "~>"
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
89
  version: '7.0'
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - "~>"
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '7.0'
97
97
  - !ruby/object:Gem::Dependency
@@ -130,7 +130,9 @@ files:
130
130
  - app/views/solid_errors/error_mailer/error_occurred.html.erb
131
131
  - app/views/solid_errors/error_mailer/error_occurred.text.erb
132
132
  - app/views/solid_errors/errors/_actions.html.erb
133
+ - app/views/solid_errors/errors/_delete_button.html.erb
133
134
  - app/views/solid_errors/errors/_error.html.erb
135
+ - app/views/solid_errors/errors/_resolve_button.html.erb
134
136
  - app/views/solid_errors/errors/_row.html.erb
135
137
  - app/views/solid_errors/errors/index.html.erb
136
138
  - app/views/solid_errors/errors/show.html.erb
@@ -140,7 +142,7 @@ files:
140
142
  - config/routes.rb
141
143
  - lib/generators/solid_errors/install/USAGE
142
144
  - lib/generators/solid_errors/install/install_generator.rb
143
- - lib/generators/solid_errors/install/templates/create_solid_errors_tables.rb.erb
145
+ - lib/generators/solid_errors/install/templates/errors_schema.rb
144
146
  - lib/solid_errors.rb
145
147
  - lib/solid_errors/engine.rb
146
148
  - lib/solid_errors/sanitizer.rb
@@ -167,7 +169,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
167
169
  - !ruby/object:Gem::Version
168
170
  version: '0'
169
171
  requirements: []
170
- rubygems_version: 3.5.1
172
+ rubygems_version: 3.5.11
171
173
  signing_key:
172
174
  specification_version: 4
173
175
  summary: Database-backed Rails error subscriber
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class CreateSolidErrorsTables < ActiveRecord::Migration<%= migration_version %>
4
- def change
5
- create_table :solid_errors do |t|
6
- t.text :exception_class, null: false
7
- t.text :message, null: false
8
- t.text :severity, null: false
9
- t.text :source
10
- t.datetime :resolved_at, index: true
11
- t.string :fingerprint, null: false, limit: 64, index: { unique: true }
12
-
13
- t.timestamps
14
- end
15
-
16
- create_table :solid_errors_occurrences do |t|
17
- t.belongs_to :error, null: false, foreign_key: { to_table: :solid_errors }
18
- t.text :backtrace
19
- t.json :context
20
-
21
- t.timestamps
22
- end
23
- end
24
- end