asana_exception_notifier 0.4.0 → 0.5.0
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 +4 -4
- data/README.md +1 -1
- data/asana_exception_notifier.gemspec +1 -1
- data/lib/asana_exception_notifier/classes/asana.rb +139 -7
- data/lib/asana_exception_notifier/classes/error_page.rb +145 -3
- data/lib/asana_exception_notifier/classes/unsafe_filter.rb +49 -1
- data/lib/asana_exception_notifier/helpers/application_helper.rb +171 -24
- data/lib/asana_exception_notifier/helpers/heredoc_helper.rb +9 -2
- data/lib/asana_exception_notifier/initializers/hash.rb +34 -0
- data/lib/asana_exception_notifier/initializers/zip.rb +35 -7
- data/lib/asana_exception_notifier/version.rb +1 -1
- data/lib/asana_exception_notifier.rb +1 -0
- data/lib/generators/asana_exception_notifier/install_generator.rb +4 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4347775ca62029a21993e4af21d0ce3ab69fba95
|
4
|
+
data.tar.gz: dc0fbe28a472e7b9ed3c32d30f2539b60f3693b2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8ec4281a81123c92a7065b0d93e3418d3bf7d5f86cfc358db5f062a890212d777a1a42d1297697874aee2c037430bff1151c0226eeca1e65888b8d33b99d78bb
|
7
|
+
data.tar.gz: 7e8cb03ef5d3decfee940816af3747c483818c4e2329136c87811beabaccd2a7bff77e0aaf8ab22ef4d456fc5e79257aee71548e05c68e3776d5ebf500f1b31f
|
data/README.md
CHANGED
@@ -179,7 +179,7 @@ Array of projects this task is associated with and the section it is in. At task
|
|
179
179
|
|
180
180
|
*Array, optional*
|
181
181
|
|
182
|
-
Array of tags associated with this task. This property may be specified on creation using just an array of existing tag IDs. (Default:
|
182
|
+
Array of tags associated with this task. This property may be specified on creation using just an array of existing tag IDs. (Default: []).
|
183
183
|
|
184
184
|
##### name
|
185
185
|
|
@@ -34,7 +34,7 @@ Gem::Specification.new do |s|
|
|
34
34
|
s.add_runtime_dependency 'rubyzip', '~> 1.0', '>= 1.0.0' # will load new rubyzip version
|
35
35
|
s.add_runtime_dependency 'zip-zip', '~> 0.3', '>= 0.3' # will load compatibility for old rubyzip API
|
36
36
|
s.add_runtime_dependency 'sys-uname', '~> 1.0', '>= 1.0.2'
|
37
|
-
|
37
|
+
|
38
38
|
s.add_development_dependency 'appraisal', '~> 2.1', '>= 2.1'
|
39
39
|
s.add_development_dependency 'rspec', '~> 3.4', '>= 3.4'
|
40
40
|
s.add_development_dependency 'simplecov', '~> 0.11', '>= 0.10'
|
@@ -1,22 +1,68 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require_relative '../helpers/application_helper'
|
3
|
-
# class used for connecting to
|
3
|
+
# class used for connecting to connecting to Asana and creation of task and upload of archives
|
4
4
|
#
|
5
|
-
# @!attribute
|
6
|
-
# @return [
|
5
|
+
# @!attribute [r] initial_options
|
6
|
+
# @return [Hash] THe initial options that the notifier received ( blank values are filtered )
|
7
|
+
# @!attribute [r] default_options
|
8
|
+
# @return [Hash] The permitted_options that are merged with initial options ( blank values are filtered )
|
7
9
|
module ExceptionNotifier
|
8
10
|
# module that is used for formatting numbers using metrics
|
9
11
|
class AsanaNotifier < ExceptionNotifier::BaseNotifier
|
10
12
|
include AsanaExceptionNotifier::ApplicationHelper
|
11
|
-
# the base url to which the API will connect for fetching information about gems
|
12
|
-
attr_reader :initial_options, :default_options
|
13
13
|
|
14
|
+
# The initial options that the middleware was configured with
|
15
|
+
# @return [Hash] THe initial options that the notifier received ( blank values are filtered )
|
16
|
+
attr_reader :initial_options
|
17
|
+
|
18
|
+
# The resulting options after merging with permitted_options and with initial_options
|
19
|
+
# @return [Hash] The permitted_options that are merged with initial options ( blank values are filtered )
|
20
|
+
attr_reader :default_options
|
21
|
+
|
22
|
+
# Initializes the instance with the options from the configuration and
|
23
|
+
# parses the options
|
24
|
+
# @see #parse_options
|
25
|
+
#
|
26
|
+
# @param [options] options The options that can be set in the configuration block
|
27
|
+
# @option params [String] :asana_api_key Your Personal Access Token from Asana. You can get it from https://app.asana.com/-/account_api.
|
28
|
+
# Please make sure you keep the token secret, and don't commit it in your repository.
|
29
|
+
# I suggest to put it into an environment variable and use it from that variable. ( This is REQUIRED )
|
30
|
+
# @option params [Integer] :workspace The workspace ID where the task will be created. ( This is REQUIRED )
|
31
|
+
# @option params [String, nil] :assignee Who will be assigned by default to the task that is going to be created. (Default: 'me').
|
32
|
+
# Can be disabled by setting it to NIL value
|
33
|
+
# @option params [String, nil] :assignee_status Scheduling status of this task for the user it is assigned to.
|
34
|
+
# This field can only be set if the assignee is non-null. (Default: 'today'). Can be disabled by setting it to NIL value.
|
35
|
+
# @option params [Time, nil] :due_at Date and time on which this task is due, or null if the task has no due time.
|
36
|
+
# This takes a UTC timestamp and should not be used together with due_on. Default ( Time.now.iso8601)
|
37
|
+
# @option params [Time, nil] :due_on Date on which this task is due, or null if the task has no due date.
|
38
|
+
# This takes a date with YYYY-MM-DD format and should not be used together with due_at
|
39
|
+
# @option params [Boolean, nil] :hearted True if the task is hearted by the authorized user, false if not (Default: false).
|
40
|
+
# @option params [Array<String>] :hearts Array of users who will heart the task after creation. (Default: empty Array)
|
41
|
+
# @option params [Array<String>] :projects Array of projects this task is associated with.
|
42
|
+
# At task creation time, this array can be used to add the task to many projects at once.(Default: empty array).
|
43
|
+
# @option params [Array<String>] :followers Array of users following this task. (Default: empty array).
|
44
|
+
# @option params [Array<String>] :memberships Array of projects this task is associated with and the section it is in.
|
45
|
+
# At task creation time, this array can be used to add the task to specific sections.
|
46
|
+
# Note that over time, more types of memberships may be added to this property.(Default: []).
|
47
|
+
# @option params [Array<String>] :tags Array of tags associated with this task.
|
48
|
+
# This property may be specified on creation using just an array of existing tag IDs. (Default: false).
|
49
|
+
# @option params [String] :notes More detailed, free-form textual information associated with the task. (Default: '')
|
50
|
+
# @option params [String] :name Name of the task. This is generally a short sentence fragment that fits on a line in the UI for maximum readability.
|
51
|
+
# However, it can be longer. (Default: "[AsanaExceptionNotifier] %Exception Class Name%").
|
52
|
+
# @option params [String] :template_path This can be used to override the default template when rendering the exception details with customized template.
|
53
|
+
# @option params [Array<String>] :unsafe_options This can be used to specify options as strings that will be filtered from session and from request parameters
|
54
|
+
# ( The options will not be displayed in the HTML template)
|
55
|
+
# @return [void]
|
14
56
|
def initialize(options)
|
15
57
|
super
|
16
58
|
@initial_options = options.symbolize_keys.reject { |_key, value| value.blank? }
|
17
59
|
parse_options(@initial_options)
|
18
60
|
end
|
19
61
|
|
62
|
+
# Returns the asana client that will be used to connect to Asana API and sets the configuration for the client
|
63
|
+
# @see #faraday_configuration
|
64
|
+
#
|
65
|
+
# @return [Asana::Client] Returns the client used for connecting to Asana API's
|
20
66
|
def asana_client
|
21
67
|
@asana_client = Asana::Client.new do |config|
|
22
68
|
config.authentication :access_token, asana_api_key
|
@@ -26,54 +72,107 @@ module ExceptionNotifier
|
|
26
72
|
end
|
27
73
|
end
|
28
74
|
|
75
|
+
# Returns the asana client that will be used to connect to Asana API
|
76
|
+
# @param [Asana::Configuration] config The configuration object that will be used to set the faraday adapter options for connecting to API's
|
77
|
+
#
|
78
|
+
# @return [void]
|
29
79
|
def faraday_configuration(config)
|
30
80
|
config.configure_faraday do |conn|
|
31
|
-
conn.request
|
81
|
+
conn.request :url_encoded
|
32
82
|
conn.response :logger
|
33
83
|
end
|
34
84
|
end
|
35
85
|
|
86
|
+
# When a exception is caught , this method will be called to publish to Asana the exception details
|
87
|
+
# In order not to block the main thread, while we are parsing the exception, and constructing the template date,
|
88
|
+
# and connecting to asana, this will spawn a new thread to ensure that the processing of the exception is deferred
|
89
|
+
# from the main thread.
|
90
|
+
# This method will also create the asana task after the processing of the exception and all the other data is gathered
|
91
|
+
# by the AsanaExceptionNotifier::ErrorPage class
|
92
|
+
#
|
93
|
+
#
|
94
|
+
# @see #ensure_thread_running
|
95
|
+
# @see #execute_with_rescue
|
96
|
+
# @see AsanaExceptionNotifier::ErrorPage#new
|
97
|
+
# @see #create_asana_task
|
98
|
+
#
|
99
|
+
# @param [Exception] exception The exception that was caught by the middleware
|
100
|
+
# @param [Hash] options Additional options that the middleware can send ( Default : {})
|
101
|
+
#
|
102
|
+
# @return [void]
|
36
103
|
def call(exception, options = {})
|
37
104
|
ensure_thread_running do
|
38
105
|
execute_with_rescue do
|
39
|
-
EM::HttpRequest.use AsanaExceptionNotifier::Request::Middleware if ENV['DEBUG_ASANA_EXCEPTION_NOTIFIER']
|
40
106
|
error_page = AsanaExceptionNotifier::ErrorPage.new(template_path, exception, options)
|
41
107
|
create_asana_task(error_page) if active?
|
42
108
|
end
|
43
109
|
end
|
44
110
|
end
|
45
111
|
|
112
|
+
# Method that is used to fetch the Asana api key from the default_options
|
113
|
+
#
|
114
|
+
# @return [String, nil] returns the asana api key if was provided in configuration, or nil otherwise
|
46
115
|
def asana_api_key
|
47
116
|
@default_options.fetch(:asana_api_key, nil)
|
48
117
|
end
|
49
118
|
|
119
|
+
# Method that is used to fetch the workspace ID from the default_options
|
120
|
+
#
|
121
|
+
# @return [String, nil] returns the workspace ID if was provided in configuration, or nil otherwise
|
50
122
|
def workspace
|
51
123
|
@default_options.fetch(:workspace, nil)
|
52
124
|
end
|
53
125
|
|
126
|
+
# Method that is used to fetch the notes from the default_options
|
127
|
+
#
|
128
|
+
# @return [String, nil] returns the notes if they were provided in configuration, or nil otherwise
|
54
129
|
def notes
|
55
130
|
@default_options.fetch(:notes, nil)
|
56
131
|
end
|
57
132
|
|
133
|
+
# Method that is used to fetch the task name from the default_options
|
134
|
+
#
|
135
|
+
# @return [String, nil] returns the task name if was were provided in configuration, or nil otherwise
|
58
136
|
def task_name
|
59
137
|
@default_options.fetch(:name, nil)
|
60
138
|
end
|
61
139
|
|
140
|
+
# Method that is used by the ExceptionNotifier gem to check if this notifier can be activated.
|
141
|
+
# The method checks if the asana api key and workspace ID were provided
|
142
|
+
#
|
143
|
+
# @return [Boolean] returns true if the asana api key and the workspace ID were provided in the configuration, otherwise false
|
62
144
|
def active?
|
63
145
|
asana_api_key.present? && workspace.present?
|
64
146
|
end
|
65
147
|
|
148
|
+
# Method that retrieves the template_path for rendering the exception details
|
149
|
+
#
|
150
|
+
# @return [String, nil] returns the template_path if was were provided in configuration, or nil otherwise
|
66
151
|
def template_path
|
67
152
|
@default_options.fetch(:template_path, nil)
|
68
153
|
end
|
69
154
|
|
70
155
|
private
|
71
156
|
|
157
|
+
# Method that parses the options, and rejects keys that are not permitted , and values that are blank
|
158
|
+
# @see #permitted_options
|
159
|
+
#
|
160
|
+
# @param [Hash] options Additional options that are merged in the default options
|
161
|
+
#
|
162
|
+
# @return [void]
|
72
163
|
def parse_options(options)
|
73
164
|
options = options.reject { |key, _value| !permitted_options.key?(key) }
|
74
165
|
@default_options = permitted_options.merge(options).reject { |_key, value| value.blank? }
|
75
166
|
end
|
76
167
|
|
168
|
+
# Method that tries to render a custom notes template or the default notes template
|
169
|
+
# @see #path_is_a_template
|
170
|
+
# @see #expanded_path
|
171
|
+
# @see AsanaExceptionNotifier::ErrorPage#render_template
|
172
|
+
#
|
173
|
+
# @param [AsanaExceptionNotifier::ErrorPage] error_page the Erorr page class that is responsible for rendering the exception templates
|
174
|
+
#
|
175
|
+
# @return [String] The content of the notes templates after being rendered
|
77
176
|
def note_content(error_page)
|
78
177
|
if path_is_a_template?(notes)
|
79
178
|
error_page.render_template(expanded_path(notes))
|
@@ -82,10 +181,22 @@ module ExceptionNotifier
|
|
82
181
|
end
|
83
182
|
end
|
84
183
|
|
184
|
+
# Returns the customized task name ( if any provided ) or the default one
|
185
|
+
#
|
186
|
+
# @param [AsanaExceptionNotifier::ErrorPage] error_page the Erorr page class that is responsible handling exceptions
|
187
|
+
#
|
188
|
+
# @return [String] The task name that will be used when creating the asana task
|
85
189
|
def task_name_content(error_page)
|
86
190
|
task_name.present? ? task_name : "[AsanaExceptionNotifier] #{error_page.exception_data[:message]}"
|
87
191
|
end
|
88
192
|
|
193
|
+
# Builds all the options needed for creating a asana task
|
194
|
+
# @see #task_name_content
|
195
|
+
# @see #note_content
|
196
|
+
#
|
197
|
+
# @param [AsanaExceptionNotifier::ErrorPage] error_page the Erorr page class that is responsible handling exceptions
|
198
|
+
#
|
199
|
+
# @return [void]
|
89
200
|
def build_request_options(error_page)
|
90
201
|
@default_options.except(:asana_api_key, :template_path).merge(
|
91
202
|
name: task_name_content(error_page),
|
@@ -94,7 +205,12 @@ module ExceptionNotifier
|
|
94
205
|
).symbolize_keys!
|
95
206
|
end
|
96
207
|
|
208
|
+
# Method that is used to create the asana task and upload the log files to the task
|
209
|
+
# @see Asana::Resources::Task#create
|
210
|
+
# @see #build_request_options
|
211
|
+
# @see #upload_log_file_to_task
|
97
212
|
#
|
213
|
+
# @param [AsanaExceptionNotifier::ErrorPage] error_page the Erorr page class that is responsible handling exceptions
|
98
214
|
#
|
99
215
|
# @return [void]
|
100
216
|
def create_asana_task(error_page)
|
@@ -104,6 +220,15 @@ module ExceptionNotifier
|
|
104
220
|
end
|
105
221
|
end
|
106
222
|
|
223
|
+
# Method that is used to fetch all the needed archives that will be uploaded to the task
|
224
|
+
# and upload each of them
|
225
|
+
# @see AsanaExceptionNotifier::ErrorPage#fetch_all_archives
|
226
|
+
# @see #upload_archive
|
227
|
+
#
|
228
|
+
# @param [AsanaExceptionNotifier::ErrorPage] error_page the Erorr page class that is responsible handling exceptions
|
229
|
+
# @param [Asana::Resources::Task] task the task that was created, and needed to upload archives to the task
|
230
|
+
#
|
231
|
+
# @return [void]
|
107
232
|
def upload_log_file_to_task(error_page, task)
|
108
233
|
archives = error_page.fetch_all_archives
|
109
234
|
archives.each do |zip|
|
@@ -111,6 +236,13 @@ module ExceptionNotifier
|
|
111
236
|
end
|
112
237
|
end
|
113
238
|
|
239
|
+
# Method that is used to upload an archive to a task, The file will be deleted after the upload finishes
|
240
|
+
# @see Asana::Resources::Task#attach
|
241
|
+
#
|
242
|
+
# @param [String] zip the file path to the archive that will be uploaded
|
243
|
+
# @param [Asana::Resources::Task] task the task that was created, and needed to upload archives to the task
|
244
|
+
#
|
245
|
+
# @return [void]
|
114
246
|
def upload_archive(zip, task)
|
115
247
|
return if task.blank?
|
116
248
|
task.attach(
|
@@ -3,11 +3,39 @@ require_relative '../helpers/application_helper'
|
|
3
3
|
require_relative './unsafe_filter'
|
4
4
|
module AsanaExceptionNotifier
|
5
5
|
# class used for rendering the template for exception
|
6
|
+
#
|
7
|
+
# @!attribute [r] template_path
|
8
|
+
# @return [Hash] The template_path that will be used to render the exception details
|
9
|
+
# @!attribute [r] exception
|
10
|
+
# @return [Hash] The exception that will be parsed
|
11
|
+
# @!attribute [r] options
|
12
|
+
# @return [Hash] Additional options sent by the middleware that will be used to provide additional informatio
|
13
|
+
# @!attribute [r] template_details
|
14
|
+
# @return [Hash] The name and the extension of the template
|
15
|
+
# @!attribute [r] env
|
16
|
+
# @return [Hash] The environment that was sent by the middleware or the ENV variable
|
17
|
+
# @!attribute [r] request
|
18
|
+
# @return [Hash] The request that is built based on the environment, in order to provide more information
|
19
|
+
# @!attribute [r] tempfile
|
20
|
+
# @return [Hash] The archive that will be created and then splitted into multiple archives (if needed )
|
21
|
+
# @!attribute [r] template_params
|
22
|
+
# @return [Hash] The template params that will be sent to the template
|
6
23
|
class ErrorPage
|
7
24
|
include AsanaExceptionNotifier::ApplicationHelper
|
8
25
|
|
9
|
-
attr_reader :template_path, :exception, :options, :
|
26
|
+
attr_reader :template_path, :exception, :options, :template_details, :env, :request, :tempfile, :template_params
|
10
27
|
|
28
|
+
# Initializes the instance with the template path that will be used to render the template,
|
29
|
+
# the exception caught by the middleware and additional options sent by the middleware
|
30
|
+
# @see #html_template
|
31
|
+
# @see #setup_template_details
|
32
|
+
# @see #parse_exception_options
|
33
|
+
#
|
34
|
+
# @param [String] template_path The template_path that will be used to render the exception details
|
35
|
+
# @param [Exception] exception The exception that was caught by the middleware
|
36
|
+
# @param [Hash] options Additional options that the middleware can send ( Default : {})
|
37
|
+
#
|
38
|
+
# @return [void]
|
11
39
|
def initialize(template_path, exception, options)
|
12
40
|
@exception = exception
|
13
41
|
@options = options.symbolize_keys
|
@@ -19,6 +47,15 @@ module AsanaExceptionNotifier
|
|
19
47
|
parse_exception_options
|
20
48
|
end
|
21
49
|
|
50
|
+
# Initializes the instance with the template path that will be used to render the template,
|
51
|
+
# the exception caught by the middleware and additional options sent by the middleware
|
52
|
+
# @see #path_is_a_template
|
53
|
+
# @see #expanded_path
|
54
|
+
# @see #template_dir
|
55
|
+
#
|
56
|
+
# @param [String] path The template_path that will be used to render the exception details
|
57
|
+
#
|
58
|
+
# @return [void]
|
22
59
|
def html_template(path)
|
23
60
|
@template_path = if path_is_a_template?(path)
|
24
61
|
expanded_path(path)
|
@@ -27,10 +64,18 @@ module AsanaExceptionNotifier
|
|
27
64
|
end
|
28
65
|
end
|
29
66
|
|
67
|
+
# Returns true or false if ActionDispatch is available
|
68
|
+
#
|
69
|
+
# @return [Boolean] returns true if ActionDispatch::Request is defined, false otherwise
|
30
70
|
def action_dispatch?
|
31
71
|
defined?(ActionDispatch::Request)
|
32
72
|
end
|
33
73
|
|
74
|
+
# Gets the name and the extension of the template path, if was provided custom
|
75
|
+
# ( this is needed in case someone wants something else than ERB template , since Tilt can support multiple formats)
|
76
|
+
# @see #get_extension_and_name_from_file
|
77
|
+
#
|
78
|
+
# @return [Hash] Returns a hash containing the name and the extension of the template
|
34
79
|
def setup_template_details
|
35
80
|
template_extension = @template_path.scan(/\.(\w+)\.?(.*)?/)[0][0]
|
36
81
|
get_extension_and_name_from_file(@template_path).merge(
|
@@ -39,6 +84,19 @@ module AsanaExceptionNotifier
|
|
39
84
|
end
|
40
85
|
|
41
86
|
# :reek:TooManyStatements: { max_statements: 10 }
|
87
|
+
#
|
88
|
+
# Fetches information about request, exception, environment and other additional information needed for the template
|
89
|
+
# @see #fetch_basic_info
|
90
|
+
# @see #exception_data
|
91
|
+
# @see #setup_env_params
|
92
|
+
# @see #filter_params
|
93
|
+
# @see #session
|
94
|
+
# @see #request_params
|
95
|
+
# @see Rack::Request#cookies
|
96
|
+
# @see ActionDispatch::Request#filtered_env
|
97
|
+
# @see ActionDispatch::Request#filtered_parameters
|
98
|
+
#
|
99
|
+
# @return [Hash] Returns a hash containing all the information gathered about the exception, including env, cookies, session, and other additional information
|
42
100
|
def parse_exception_options
|
43
101
|
@template_params ||= {
|
44
102
|
basic_info: fetch_basic_info,
|
@@ -54,10 +112,19 @@ module AsanaExceptionNotifier
|
|
54
112
|
}.merge(@options).reject { |_key, value| value.blank? }
|
55
113
|
end
|
56
114
|
|
115
|
+
# returns the session from the request, (either from ActionDispatch or from Rack)
|
116
|
+
#
|
117
|
+
# @return [Hash] Returns the session of the request
|
57
118
|
def session
|
58
119
|
@request.session
|
59
120
|
end
|
60
121
|
|
122
|
+
# returns basic information about the system, like hostname, rails root directory, the process Id, the uname , the timestamp, and the Program name
|
123
|
+
# @see Socket#gethostname
|
124
|
+
# @see Rails::root
|
125
|
+
# @see Sys::Uname#uname
|
126
|
+
#
|
127
|
+
# @return [Hash] Returns basic information about the system, like hostname, and other additionl information
|
61
128
|
def fetch_basic_info
|
62
129
|
{
|
63
130
|
server: Socket.gethostname,
|
@@ -69,6 +136,9 @@ module AsanaExceptionNotifier
|
|
69
136
|
}
|
70
137
|
end
|
71
138
|
|
139
|
+
# returns information about the exception, like the class name, the message, the backtrace, the cause ( if gem 'cause' is used)
|
140
|
+
#
|
141
|
+
# @return [Hash] Returns information about the exception, like the class name, the message, the backtrace, the cause ( if gem 'cause' is used)
|
72
142
|
def exception_data
|
73
143
|
exception_service.merge(
|
74
144
|
error_class: @exception.class.to_s,
|
@@ -78,6 +148,9 @@ module AsanaExceptionNotifier
|
|
78
148
|
)
|
79
149
|
end
|
80
150
|
|
151
|
+
# returns the instance variables defined by the exception, useful when using custom exceptions
|
152
|
+
#
|
153
|
+
# @return [Hash] Returns information about the instance variables defined by the exception, useful when using custom exceptions
|
81
154
|
def exception_service
|
82
155
|
hash = {}
|
83
156
|
@exception.instance_variables.select do |ivar|
|
@@ -87,6 +160,9 @@ module AsanaExceptionNotifier
|
|
87
160
|
hash
|
88
161
|
end
|
89
162
|
|
163
|
+
# returns information about URL, referer, http_method used, ip address and user agent
|
164
|
+
#
|
165
|
+
# @return [Hash] Returns information about URL, referer, http_method used, ip address and user agent
|
90
166
|
def setup_env_params
|
91
167
|
{
|
92
168
|
url: @request.respond_to?(:original_url) ? @request.original_url : @request.path_info,
|
@@ -97,25 +173,46 @@ module AsanaExceptionNotifier
|
|
97
173
|
}
|
98
174
|
end
|
99
175
|
|
176
|
+
# Filters sensitive information from parameters so that they won't get leaked into the template
|
177
|
+
# @see AsanaExceptionNotifier::UnsafeFilter#new
|
178
|
+
#
|
179
|
+
# @return [Hash] Returns the information filtered , by using custom filters or the default one
|
100
180
|
def filter_params(params)
|
101
181
|
AsanaExceptionNotifier::UnsafeFilter.new(params, @options.fetch(:unsafe_options, [])).arguments
|
102
182
|
end
|
103
183
|
|
184
|
+
# returns the params sent with the initial request
|
185
|
+
#
|
186
|
+
# @return [Hash] Returns the params sent with the initial request
|
104
187
|
def request_params
|
105
188
|
@request.params
|
106
189
|
rescue
|
107
190
|
{}
|
108
191
|
end
|
109
192
|
|
193
|
+
# returns the names that will be used on the table header in the template
|
194
|
+
# @see #fieldsets
|
195
|
+
# @see #link_helper
|
196
|
+
#
|
197
|
+
# @return [String] returns the names that will be used on the table header in the template
|
110
198
|
def fieldsets_links
|
111
199
|
fieldsets.map { |key, _value| link_helper(key.to_s) }.join(' | ')
|
112
200
|
end
|
113
201
|
|
202
|
+
# returns fieldsets that will be showned in the template on separate table
|
203
|
+
# @see #mount_tables_for_fieldsets
|
204
|
+
#
|
205
|
+
# @return [Array<Hash>] returns fieldsets that will be showned in the template on separate table
|
114
206
|
def fieldsets
|
115
207
|
@fieldsets ||= mount_tables_for_fieldsets
|
116
208
|
@fieldsets
|
117
209
|
end
|
118
210
|
|
211
|
+
# returns fieldsets that will be showned in the template on separate table
|
212
|
+
# @see #fetch_fieldsets
|
213
|
+
# @see #mount_table_for_hash
|
214
|
+
#
|
215
|
+
# @return [Hash] returns the tables that will be used to render on the template as a Hash
|
119
216
|
def mount_tables_for_fieldsets
|
120
217
|
hash = fetch_fieldsets
|
121
218
|
hash.each do |key, value|
|
@@ -125,6 +222,12 @@ module AsanaExceptionNotifier
|
|
125
222
|
hash
|
126
223
|
end
|
127
224
|
|
225
|
+
# iterates over the template params and sets the fieldsets that will be will be displayed in tables
|
226
|
+
# @see #set_fieldset_key
|
227
|
+
#
|
228
|
+
# @param [Hash] hash the hash that will contain the data will be displayed in tables
|
229
|
+
#
|
230
|
+
# @return [void]
|
128
231
|
def build_template_params_hash(hash)
|
129
232
|
@template_params.each_with_parent do |parent, key, value|
|
130
233
|
next if value.blank? || key.blank?
|
@@ -133,17 +236,33 @@ module AsanaExceptionNotifier
|
|
133
236
|
end
|
134
237
|
end
|
135
238
|
|
239
|
+
# builds the template params that wil be used to construct the fieldsets and sorts them alphabetically
|
240
|
+
# @see #build_template_params_hash
|
241
|
+
#
|
242
|
+
# @param [Hash] hash the hash that will contain the template params that wil be used to construct the fieldsets sorted alphabetically
|
243
|
+
#
|
244
|
+
# @return [Hash] returns the hash that will contain the template params that wil be used to construct the fieldsets sorted alphabetically
|
136
245
|
def fetch_fieldsets(hash = {})
|
137
246
|
build_template_params_hash(hash)
|
138
247
|
hash.keys.map(&:to_s).sort
|
139
248
|
hash
|
140
249
|
end
|
141
250
|
|
251
|
+
# adds the fieldsets and the fieldsets_links to the template params
|
252
|
+
# @see #fieldsets
|
253
|
+
# @see #fieldsets_links
|
254
|
+
#
|
255
|
+
# @return [void]
|
142
256
|
def setup_template_params_for_rendering
|
143
257
|
@template_params[:fieldsets] = fieldsets
|
144
258
|
@template_params[:fieldsets_links] = fieldsets_links
|
145
259
|
end
|
146
260
|
|
261
|
+
# renders the template or the default template with the template params
|
262
|
+
# @see #execute_with_rescue
|
263
|
+
# @see #setup_template_params_for_rendering
|
264
|
+
#
|
265
|
+
# @return [void]
|
147
266
|
def render_template(template = nil)
|
148
267
|
execute_with_rescue do
|
149
268
|
current_template = template.present? ? template : @template_path
|
@@ -152,6 +271,14 @@ module AsanaExceptionNotifier
|
|
152
271
|
end
|
153
272
|
end
|
154
273
|
|
274
|
+
# Creates a archive from the render_template outpout and returns the filename and the path of the file
|
275
|
+
# @see Tempfile#new
|
276
|
+
# @see Tempfile#write
|
277
|
+
# @see ObjectSpace#undefine_finalizer
|
278
|
+
# @see Tempfile#close
|
279
|
+
# @see #tempfile_details
|
280
|
+
#
|
281
|
+
# @return [Array<String>] returns an array containing the filename as first value, and the path to the tempfile created as second value
|
155
282
|
def create_tempfile(output = render_template)
|
156
283
|
tempfile = Tempfile.new([SecureRandom.uuid, ".#{@template_details[:template_extension]}"], encoding: 'utf-8')
|
157
284
|
tempfile.write(output)
|
@@ -160,20 +287,35 @@ module AsanaExceptionNotifier
|
|
160
287
|
tempfile_details(tempfile).slice(:filename, :path).values
|
161
288
|
end
|
162
289
|
|
290
|
+
# Executes the fetch_archives and returns the result or empty array in case of exception
|
291
|
+
# @see #fetch_archives
|
292
|
+
#
|
293
|
+
# @return [Array] returns an array with file paths to the created archives
|
163
294
|
def fetch_all_archives
|
164
295
|
fetch_archives
|
165
296
|
rescue
|
166
297
|
[]
|
167
298
|
end
|
168
299
|
|
300
|
+
# Creates the archive, compresses it , and then removes the temporary file and splits the archive if needed
|
301
|
+
# @see #create_tempfile
|
302
|
+
# @see #archive_files
|
303
|
+
# @see #remove_tempfile
|
304
|
+
# @see #split_archive
|
305
|
+
#
|
306
|
+
# @return [Array] returns an array with file paths to the created archives
|
169
307
|
def fetch_archives(output = render_template)
|
170
308
|
return [] if output.blank?
|
171
309
|
filename, path = create_tempfile(output)
|
172
|
-
archive =
|
310
|
+
archive = archive_files(File.dirname(path), filename, [expanded_path(path)])
|
173
311
|
remove_tempfile(path)
|
174
|
-
split_archive(archive, "part_#{filename}", 1024 * 1024 * 100)
|
312
|
+
split_archive(archive, "part_#{filename}", 1024 * 1024 * 100) # 104_857_600
|
175
313
|
end
|
176
314
|
|
315
|
+
# If DEBUG_ASANA_TEMPLATE is present this method will only log the path , otherwise will remove the file.
|
316
|
+
# @param [String] path The path of the Tempfile that needs to be removed or logged
|
317
|
+
#
|
318
|
+
# @return [void]
|
177
319
|
def remove_tempfile(path)
|
178
320
|
if ENV['DEBUG_ASANA_TEMPLATE']
|
179
321
|
logger.debug(path)
|
@@ -2,16 +2,36 @@
|
|
2
2
|
require_relative '../helpers/application_helper'
|
3
3
|
module AsanaExceptionNotifier
|
4
4
|
# class used to filter unsafe params
|
5
|
+
#
|
6
|
+
# @!attribute [r] arguments
|
7
|
+
# @return [#delete] THe arguments that will be filtered
|
8
|
+
# @!attribute [r] unsafe_options
|
9
|
+
# @return [Array<String>, Array<Symbol>] Additional unsafe options that will be used for filtering
|
5
10
|
class UnsafeFilter
|
6
11
|
include AsanaExceptionNotifier::ApplicationHelper
|
7
12
|
|
13
|
+
# the default options that are considered unsafe
|
8
14
|
UNSAFE_OPTIONS = %w(
|
9
15
|
password password_confirmation new_password new_password_confirmation
|
10
16
|
old_password email_address email authenticity_token utf8
|
11
17
|
).freeze
|
12
18
|
|
13
|
-
|
19
|
+
# The arguments that will be filtered
|
20
|
+
# @return [#delete] THe arguments that will be filtered
|
21
|
+
attr_reader :arguments
|
14
22
|
|
23
|
+
# Additional unsafe options that will be used for filtering
|
24
|
+
# @return [Array<String>, Array<Symbol>] Additional unsafe options that will be used for filtering
|
25
|
+
attr_reader :unsafe_options
|
26
|
+
|
27
|
+
# Initializes the instance with the arguments that will be filtered and the additional unsafe options
|
28
|
+
# and starts filtering the arguments
|
29
|
+
# @see #remove_unsafe
|
30
|
+
#
|
31
|
+
# @param [#delete] arguments The arguments that will be filtered
|
32
|
+
# @param [Array<String>, Array<Symbol>] unsafe_options Additional unsafe options that will be used for filtering
|
33
|
+
#
|
34
|
+
# @return [void]
|
15
35
|
def initialize(arguments, unsafe_options = [])
|
16
36
|
@unsafe_options = unsafe_options.present? && unsafe_options.is_a?(Array) ? unsafe_options.map(&:to_s) : []
|
17
37
|
@arguments = arguments.present? ? arguments : {}
|
@@ -20,6 +40,15 @@ module AsanaExceptionNotifier
|
|
20
40
|
|
21
41
|
private
|
22
42
|
|
43
|
+
# Returns the arguments, if they are blank
|
44
|
+
# Otherwise first tries to remove attributes
|
45
|
+
# then the blank values, and then tries to remove any remaining unsafe from the remaining object
|
46
|
+
# @see #remove_blank
|
47
|
+
# @see #remove_unsafe_from_object
|
48
|
+
#
|
49
|
+
# @param [#delete] args The arguments that will be filtered
|
50
|
+
#
|
51
|
+
# @return [Object, nil]
|
23
52
|
def remove_unsafe(args)
|
24
53
|
return args if args.blank?
|
25
54
|
args.delete(:attributes!)
|
@@ -28,6 +57,14 @@ module AsanaExceptionNotifier
|
|
28
57
|
args
|
29
58
|
end
|
30
59
|
|
60
|
+
# If arguments is a hash will try to remove any unsafe values
|
61
|
+
# otherwise will call the remove_unsafe to start removing from object
|
62
|
+
# @see #verify_unsafe_pair
|
63
|
+
# @see #remove_unsafe
|
64
|
+
#
|
65
|
+
# @param [#delete] args The arguments that will be filtered
|
66
|
+
#
|
67
|
+
# @return [Object, nil]
|
31
68
|
def remove_unsafe_from_object(args)
|
32
69
|
if args.is_a?(Hash)
|
33
70
|
args.each_pair do |key, value|
|
@@ -38,10 +75,21 @@ module AsanaExceptionNotifier
|
|
38
75
|
end
|
39
76
|
end
|
40
77
|
|
78
|
+
# returns true if the key is included in the default unsafe options or in the custom ones, otherwise false
|
79
|
+
#
|
80
|
+
# @param [String] key The key that will be checked if is unsafe
|
81
|
+
#
|
82
|
+
# @return [Boolean] returns true if the key is included in the default unsafe options or in the custom ones, otherwise false
|
41
83
|
def unsafe?(key)
|
42
84
|
@unsafe_options.include?(key) || AsanaExceptionNotifier::UnsafeFilter::UNSAFE_OPTIONS.include?(key)
|
43
85
|
end
|
44
86
|
|
87
|
+
# If the value is a hash, we start removing unsafe options from the hash, otherwise we check the key
|
88
|
+
# @see #unsafe?
|
89
|
+
# @param [String] key The key that will be checked if is unsafe
|
90
|
+
# @param [Object] value The value that will be checked if it is unsafe
|
91
|
+
#
|
92
|
+
# @return [void]
|
45
93
|
def verify_unsafe_pair(key, value)
|
46
94
|
case value
|
47
95
|
when Hash
|
@@ -8,6 +8,9 @@ module AsanaExceptionNotifier
|
|
8
8
|
|
9
9
|
module_function
|
10
10
|
|
11
|
+
# returns the Hash containing as keys the permitted options and as values their default values
|
12
|
+
#
|
13
|
+
# @return [Hash] Returns the Hash containing as keys the permitted options and as values their default values
|
11
14
|
def permitted_options
|
12
15
|
{
|
13
16
|
asana_api_key: nil,
|
@@ -29,18 +32,28 @@ module AsanaExceptionNotifier
|
|
29
32
|
}
|
30
33
|
end
|
31
34
|
|
35
|
+
# returns the expanded path of a file path
|
36
|
+
#
|
37
|
+
# @param [String] path The file path that will be expanded
|
38
|
+
#
|
39
|
+
# @return [String] returns the expanded path of a file path
|
32
40
|
def expanded_path(path)
|
33
41
|
File.expand_path(path)
|
34
42
|
end
|
35
43
|
|
44
|
+
# checks to see if a path is valid
|
45
|
+
# @see #template_path_exist
|
46
|
+
# @param [String] path The file path that will be used
|
47
|
+
#
|
48
|
+
# @return [Boolean] returns true if the path is valid otherwise false
|
36
49
|
def path_is_a_template?(path)
|
37
|
-
path.present? && template_path_exist(
|
38
|
-
end
|
39
|
-
|
40
|
-
def multi_request_manager
|
41
|
-
@multi_manager ||= EventMachine::MultiRequest.new
|
50
|
+
path.present? && template_path_exist(path)
|
42
51
|
end
|
43
52
|
|
53
|
+
# method used to extract the body of a IO object
|
54
|
+
# @param [IO] io The IO object that will be used
|
55
|
+
#
|
56
|
+
# @return [String] returns the body of the IO object by rewinding it and reading the content, or executes inspect if a exception happens
|
44
57
|
def extract_body(io)
|
45
58
|
return unless io.respond_to?(:rewind)
|
46
59
|
io.rewind
|
@@ -49,6 +62,11 @@ module AsanaExceptionNotifier
|
|
49
62
|
io.inspect
|
50
63
|
end
|
51
64
|
|
65
|
+
# method used to return the file and the path of a tempfile , along with the extension and the name of the file
|
66
|
+
# @see #get_extension_and_name_from_file
|
67
|
+
# @param [Tempfile] tempfile the temporary file that will be used
|
68
|
+
#
|
69
|
+
# @return [Hash] returns the the file and the path of a tempfile , along with the extension and the name of the file
|
52
70
|
def tempfile_details(tempfile)
|
53
71
|
file_details = get_extension_and_name_from_file(tempfile)
|
54
72
|
{
|
@@ -59,39 +77,62 @@ module AsanaExceptionNotifier
|
|
59
77
|
|
60
78
|
# Returns utf8 encoding of the msg
|
61
79
|
# @param [String] msg
|
62
|
-
# @return [String]
|
80
|
+
# @return [String] Returns utf8 encoding of the msg
|
63
81
|
def force_utf8_encoding(msg)
|
64
82
|
msg.respond_to?(:force_encoding) && msg.encoding.name != 'UTF-8' ? msg.force_encoding('UTF-8') : msg
|
65
83
|
end
|
66
84
|
|
67
|
-
#
|
85
|
+
# returns the logger used to log messages and errors
|
68
86
|
#
|
69
87
|
# @return [Logger]
|
70
|
-
#
|
71
|
-
# @api public
|
72
88
|
def logger
|
73
89
|
@logger ||= (defined?(Rails) && rails_logger.present? ? rails_logger : ExceptionNotifier.logger)
|
74
90
|
@logger = @logger.present? ? @logger : Logger.new(STDOUT)
|
75
91
|
end
|
76
92
|
|
93
|
+
# returns the rails logger
|
94
|
+
#
|
95
|
+
# @return [Rails::Logger]
|
77
96
|
def rails_logger
|
78
97
|
Rails.logger
|
79
98
|
end
|
80
99
|
|
100
|
+
# returns the newly created thread
|
101
|
+
# @see #run_new_thread
|
102
|
+
# @see Thread#abort_on_exception
|
103
|
+
# @param [Proc] &block the block that the new thread will execute
|
104
|
+
#
|
105
|
+
# @return [Thread] returns the newly created thread
|
81
106
|
def ensure_thread_running(&block)
|
82
107
|
Thread.abort_on_exception = true
|
83
108
|
run_new_thread(&block)
|
84
109
|
end
|
85
110
|
|
111
|
+
# method used to log exceptions
|
112
|
+
# @see #log_bactrace
|
113
|
+
# @param [Exception] exception the exception that will be used
|
114
|
+
#
|
115
|
+
# @return [void]
|
86
116
|
def log_exception(exception)
|
87
117
|
logger.debug exception.inspect
|
88
118
|
log_bactrace(exception) if exception.respond_to?(:backtrace)
|
89
119
|
end
|
90
120
|
|
121
|
+
# method used to log exception backtrace
|
122
|
+
# @param [Exception] exception the exception that will be used
|
123
|
+
#
|
124
|
+
# @return [void]
|
91
125
|
def log_bactrace(exception)
|
92
126
|
logger.debug exception.backtrace.join("\n")
|
93
127
|
end
|
94
128
|
|
129
|
+
# method used to rescue exceptions
|
130
|
+
# @see #rescue_interrupt
|
131
|
+
# @see #log_exception
|
132
|
+
#
|
133
|
+
# @param [Hash] options Additional options used for returning values when a exception occurs, or empty string
|
134
|
+
#
|
135
|
+
# @return [String, nil, Object] Returns nil if the exception is a interrupt or a String empty if no value was provided in the options hash or the value from the options
|
95
136
|
def execute_with_rescue(options = {})
|
96
137
|
yield if block_given?
|
97
138
|
rescue Interrupt
|
@@ -101,87 +142,165 @@ module AsanaExceptionNotifier
|
|
101
142
|
options.fetch(:value, '')
|
102
143
|
end
|
103
144
|
|
145
|
+
# method used to rescue from interrupt and show a message
|
146
|
+
#
|
147
|
+
# @return [void]
|
104
148
|
def rescue_interrupt
|
105
149
|
`stty icanon echo`
|
106
150
|
puts "\n Command was cancelled due to an Interrupt error."
|
107
151
|
end
|
108
152
|
|
153
|
+
# method used to create a thread and execute a block
|
154
|
+
# @see Thread#new
|
155
|
+
# @return [Thread] returns the newly created thread
|
109
156
|
def run_new_thread
|
110
157
|
Thread.new do
|
111
158
|
yield if block_given?
|
112
159
|
end.join
|
113
160
|
end
|
114
161
|
|
162
|
+
# returns the templates directory
|
163
|
+
# @see #root
|
164
|
+
# @return [String] returns the path to the templates directory
|
115
165
|
def template_dir
|
116
166
|
File.expand_path(File.join(root, 'templates'))
|
117
167
|
end
|
118
168
|
|
169
|
+
# returns true if file exists or false otherwise
|
170
|
+
#
|
171
|
+
# @see File#exist?
|
172
|
+
#
|
173
|
+
# @return [String] returns the path to the templates directory
|
119
174
|
def template_path_exist(path)
|
120
|
-
File.exist?(path)
|
175
|
+
File.exist?(expanded_path(path))
|
121
176
|
end
|
122
177
|
|
123
|
-
|
178
|
+
# Method used to construct table rows from a Hash, by constructing an array of arrays with two elements ( First is the key and the value )
|
179
|
+
# This is useful for constructing the table, the number of elements in a array means the number of columns of the table
|
180
|
+
#
|
181
|
+
# This is a recursive function if the Hash contains other Hash values.
|
182
|
+
# @see #inspect_value
|
183
|
+
#
|
184
|
+
# @param [Hash] hash the Hash that wil be used to construct the array of arrays with two columns
|
185
|
+
# @param [Array<Array<String>>] rows This is the array that will contain the result ( Default: empty array).
|
186
|
+
#
|
187
|
+
# @return [Array<Array<String>>] Returns an array of arrays (with two elements), useful for printing tables from a Hash
|
188
|
+
def get_hash_rows(hash, rows = [])
|
124
189
|
hash.each do |key, value|
|
125
190
|
if value.is_a?(Hash)
|
126
|
-
|
191
|
+
get_hash_rows(value, rows)
|
127
192
|
else
|
128
|
-
rows.push([key
|
193
|
+
rows.push([inspect_value(key), inspect_value(value)])
|
129
194
|
end
|
130
195
|
end
|
131
196
|
rows
|
132
197
|
end
|
133
198
|
|
199
|
+
# Method used to inspect a value, by checking if is a IO object, and in that case extract the body from the IO object,
|
200
|
+
# otherwise will just use the "inpspect" method. The final result will be escaped so that it can be printed in HTML
|
201
|
+
# @see #extract_body
|
202
|
+
# @see #escape
|
203
|
+
#
|
204
|
+
# @param [#inspect, #to_s] value The value that will be inspected and escaped
|
205
|
+
#
|
206
|
+
# @return [String] Returns the value inspected and escaped
|
134
207
|
def inspect_value(value)
|
135
|
-
value.is_a?(IO) ? extract_body(value) : value
|
208
|
+
inspected_value = value.is_a?(IO) ? extract_body(value) : value.inspect
|
209
|
+
escape(inspected_value)
|
136
210
|
end
|
137
211
|
|
212
|
+
# Method used to escape a text by escaping some characters like '&', '<' and '>' , which could affect HTML format
|
213
|
+
# @param [#to_s] text The text that will be escaped
|
214
|
+
#
|
215
|
+
# @return [String] Returns the text HTML escaped
|
138
216
|
def escape(text)
|
139
|
-
text.gsub('&', '&').gsub('<', '<').gsub('>', '>')
|
217
|
+
text.to_s.gsub('&', '&').gsub('<', '<').gsub('>', '>')
|
140
218
|
end
|
141
219
|
|
220
|
+
# Method used to set the prefix name on the links Hash, this is needed when building a table from a hash,
|
221
|
+
# because when going through the first level of a Hash, we don't have a title of what this level is about ,
|
222
|
+
# but deeper levels can have a title, by using the key to which the value is associated
|
223
|
+
#
|
224
|
+
# Because of this this method expects a default name, in case the prefix is blank
|
225
|
+
# @param [Hash] links The links Hash object that will be used to print the fieldsets links in HTML template
|
226
|
+
# @param [String] prefix The prefix that will be set as key on the links Hash and associated with a empty Hash , if the links hash does not have this key yet
|
227
|
+
# @param [String] default The default prefix that will be set as key on the links Hash and associated with a empty Hash , if the links hash does not have this key yet and the prefix is blank
|
228
|
+
#
|
229
|
+
# @return [String] Returns the prefix that was used to set the key on the links Hash, either the 'prefix' variable or the 'default' variable
|
142
230
|
def set_fieldset_key(links, prefix, default)
|
143
231
|
prefix_name = prefix.present? ? prefix : default
|
144
232
|
links[prefix_name] ||= {}
|
145
233
|
prefix_name
|
146
234
|
end
|
147
235
|
|
148
|
-
#
|
149
|
-
# to the generated table.
|
236
|
+
# This method is used to mount a table from a hash. After the table is mounted, since the generated table has two columns ( Array of array with two elements),
|
237
|
+
# We're going to prepend to this generated table a array with two elements (Name and Value) , which will be the columns headers on the generated table .
|
238
|
+
# We also will add a HTML class attribute to the generated table ('name_values')
|
239
|
+
# @see #get_hash_rows
|
240
|
+
# @see #mount_table
|
150
241
|
#
|
242
|
+
# @param [Hash] hash The Hash that will be used to mount a table from the keys and values
|
243
|
+
# @param [Hash] options Additional options that will be used to set HTML attributes on the generated table
|
244
|
+
#
|
245
|
+
# @return [String] Returns the HTML table generated as a string, which can be printed anywhere
|
151
246
|
def mount_table_for_hash(hash, options = {})
|
152
247
|
return if hash.blank?
|
153
248
|
rows = get_hash_rows(hash, options.fetch('rows', []))
|
154
249
|
mount_table(rows.unshift(%w(Name Value)), { class: 'name_values' }.merge(options))
|
155
250
|
end
|
156
251
|
|
252
|
+
# This method receives a options list which will be used to construct a string which will be used to set HTML attributes on a HTML element
|
253
|
+
#
|
254
|
+
# @param [Hash] hash The Hash that will be used to construct the string of HTML attributes
|
255
|
+
#
|
256
|
+
# @return [String] Returns the string of HTML attributes which can be used on any HTML element
|
157
257
|
def hash_to_html_attributes(hash)
|
158
258
|
hash.map do |key, value|
|
159
259
|
"#{key}=\"#{value.gsub('"', '\"')}\" "
|
160
260
|
end.join(' ')
|
161
261
|
end
|
162
262
|
|
263
|
+
# This method can receive either a Hash or an Array, which will be filtered of blank values
|
264
|
+
#
|
265
|
+
# @param [Hash, Array] args The Hash or the array which will be used for filtering blank values
|
266
|
+
#
|
267
|
+
# @return [Hash, Array] Returns the Hash or the array received , filtered of blank values
|
163
268
|
def remove_blank(args)
|
164
269
|
args.delete_if { |_key, value| value.blank? } if args.is_a?(Hash)
|
165
270
|
args.reject!(&:blank?) if args.is_a?(Array)
|
166
271
|
end
|
167
272
|
|
273
|
+
# This method is used to construct the Th header elements that can be used on HTML table from a array, by humanizing and escaping the values
|
274
|
+
# @see #escape
|
275
|
+
#
|
276
|
+
# @param [#map] header the Header array that will be used to construct the Th header elements that can be used on HTML table
|
277
|
+
#
|
278
|
+
# @return [String] Returns the HTML th elements constructed from the array , that can be used on a HTML table
|
168
279
|
def get_table_headers(header)
|
169
280
|
header.map { |name| escape(name.to_s.humanize) }.join('</th><th>')
|
170
281
|
end
|
171
282
|
|
283
|
+
# This method is to construct a HTML row for each value that exists in the array, each value from the array is a array itself.
|
284
|
+
# The row is constructed by joining the values from each array with td element, so the result will be a valid HTML row element
|
285
|
+
# The final result is a concatenation of multiple row elements that can be displayed inside a tbody element from a HTML table
|
286
|
+
# @param [#map] array The Array that will be used to construct the inner rows of a HTML table
|
287
|
+
#
|
288
|
+
# @return [String] Returns a concatenation of multiple HTML tr and td elements that are in fact the inner rows of HTML table
|
172
289
|
def get_table_rows(array)
|
173
290
|
array.map { |name| "<tr><td>#{name.join('</td><td>')}</td></tr>" }.join
|
174
291
|
end
|
175
292
|
|
176
|
-
# returns the root path of the gem
|
177
|
-
#
|
178
|
-
# @return [void]
|
293
|
+
# returns the root path of the gem ( the lib directory )
|
179
294
|
#
|
180
|
-
# @
|
295
|
+
# @return [String] Returns the root path of the gem ( the lib directory )
|
181
296
|
def root
|
182
297
|
File.expand_path(File.dirname(__dir__))
|
183
298
|
end
|
184
299
|
|
300
|
+
# returns the extension of the file, the filename and the file path of the Tempfile file received as argument
|
301
|
+
# @param [Tempfile] tempfile the Tempfile that will be used
|
302
|
+
#
|
303
|
+
# @return [Hash] returns the extension of the file, the filename and the file path of the Tempfile file received as argument
|
185
304
|
def get_extension_and_name_from_file(tempfile)
|
186
305
|
path = tempfile.respond_to?(:path) ? tempfile.path : tempfile
|
187
306
|
pathname = Pathname.new(path)
|
@@ -193,6 +312,13 @@ module AsanaExceptionNotifier
|
|
193
312
|
}
|
194
313
|
end
|
195
314
|
|
315
|
+
# Splits a archive into multiple archives if the size of the archive is greater than the segment_size received as argument
|
316
|
+
# and returns a array that contains the paths to each of the archives that were resulted after splitting
|
317
|
+
# @param [::Zip::File] archive the archive that will try to be splitted
|
318
|
+
# @param [String] partial_name the partial name that will be used when splitting the archives
|
319
|
+
# @param [Integer] segment_size the size that will be used for splitting the archive
|
320
|
+
#
|
321
|
+
# @return [Array<String>] returns a array that contains the paths to each of the archives that were resulted after splitting
|
196
322
|
def split_archive(archive, partial_name, segment_size)
|
197
323
|
indexes = Zip::File.split(archive, segment_size, true, partial_name)
|
198
324
|
archives = Array.new(indexes) do |index|
|
@@ -201,21 +327,42 @@ module AsanaExceptionNotifier
|
|
201
327
|
archives.blank? ? [archive] : archives
|
202
328
|
end
|
203
329
|
|
204
|
-
|
205
|
-
|
330
|
+
# This method receives multiple files, that will be added to a archive and will return the resulting archive
|
331
|
+
# @see #prepare_archive_creation
|
332
|
+
# @see #add_files_to_zip
|
333
|
+
# @see Zip::File::open
|
334
|
+
#
|
335
|
+
# @param [String] directory The directory where the archive will be created
|
336
|
+
# @param [String] name The name of the archive ( without the .zip extension )
|
337
|
+
# @param [Array<File>] files The Array of files that will be added to the archive
|
338
|
+
#
|
339
|
+
# @return [Zip::File] returns the archive that was created after each of the files were added to the archive and compressed
|
340
|
+
def archive_files(directory, name, files)
|
341
|
+
archive = prepare_archive_creation(directory, name)
|
206
342
|
::Zip::File.open(archive, Zip::File::CREATE) do |zipfile|
|
207
343
|
add_files_to_zip(zipfile, files)
|
208
344
|
end
|
209
345
|
archive
|
210
346
|
end
|
211
347
|
|
348
|
+
# This method receives multiple files, that will be added to a archive
|
349
|
+
# @param [::Zip::File] zipfile The archive that will be used to add files to it
|
350
|
+
# @param [Array<File>] files The Array of files that will be added to the archive
|
351
|
+
#
|
352
|
+
# @return [void]
|
212
353
|
def add_files_to_zip(zipfile, files)
|
213
354
|
files.each do |file|
|
214
355
|
zipfile.add(file.sub(File.dirname(file) + '/', ''), file)
|
215
356
|
end
|
216
357
|
end
|
217
358
|
|
218
|
-
|
359
|
+
# This method prepares the creation of a archive, by making sure that the directory is created and
|
360
|
+
# if the archive already exists, will be removed, and the path to where this archive needs to be created will be returned
|
361
|
+
# @param [String] directory The directory where the archive should be created
|
362
|
+
# @param [String] name The name of the archive ( without the .zip extension )
|
363
|
+
#
|
364
|
+
# @return [String] returns the path to where this archive needs to be created
|
365
|
+
def prepare_archive_creation(directory, name)
|
219
366
|
archive = File.join(directory, name + '.zip')
|
220
367
|
archive_dir = File.dirname(archive)
|
221
368
|
FileUtils.mkdir_p(archive_dir) unless File.directory?(archive_dir)
|
@@ -4,6 +4,10 @@ module AsanaExceptionNotifier
|
|
4
4
|
module HeredocHelper
|
5
5
|
module_function
|
6
6
|
|
7
|
+
# This method creates a HTML link , that when will be clicked will trigger the toggle of a fieldset, by either making it hidden or visible
|
8
|
+
# @param [String] link The link id of the fieldset that will be toggled when the resulting HTML link will be clicked
|
9
|
+
#
|
10
|
+
# @return [String] returns HTML link that will be used to toggle between fieldsets
|
7
11
|
def link_helper(link)
|
8
12
|
<<-HTML
|
9
13
|
<a href="javascript:void(0)" onclick="AjaxExceptionNotifier.hideAllAndToggle('#{link.downcase}')">#{link.camelize}</a>
|
@@ -12,13 +16,16 @@ module AsanaExceptionNotifier
|
|
12
16
|
|
13
17
|
# Gets a bidimensional array and create a table.
|
14
18
|
# The first array is used as label.
|
19
|
+
# @param [Array<Array<String>>] array The array of arrays of strings that will be used for constructing the HTML table
|
20
|
+
# @param [Hash] options The options list that will be used to construct the HTML attributes on the HTML table
|
15
21
|
#
|
22
|
+
# @return [String] returns the HTML table that was generated from the received array
|
16
23
|
def mount_table(array, options = {})
|
17
24
|
header = array.extract_options!
|
18
25
|
<<-HTML
|
19
26
|
<table #{hash_to_html_attributes(options)}>
|
20
|
-
|
21
|
-
|
27
|
+
<thead><tr><th>#{get_table_headers(header)}</th></tr></thead>
|
28
|
+
<tbody>#{get_table_rows(array)}</tbody>
|
22
29
|
</table>
|
23
30
|
HTML
|
24
31
|
end
|
@@ -1,6 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
# override Hash class
|
3
3
|
class Hash
|
4
|
+
# This method is used for iterating over a Hash , but besides yielding the key and the value, this will also yield the parent
|
5
|
+
# key of the current Hash object if the Hash is associated to a key
|
6
|
+
#
|
7
|
+
# @example Printing a Hash that contains other hashes inside, and fetching the parent keys
|
8
|
+
# hash = {
|
9
|
+
# key: :my_value,
|
10
|
+
# key2: {
|
11
|
+
# innner_key: :inner_value
|
12
|
+
# }
|
13
|
+
# }
|
14
|
+
# hash.each_with_parent { |parent, key, value| puts [parent, key, value].inspect }
|
15
|
+
# will print:
|
16
|
+
# [nil, :key, :my_value]
|
17
|
+
# [:key2, :innner_key, :inner_value]
|
18
|
+
#
|
19
|
+
# @see #deep_hash_with_parent
|
20
|
+
#
|
21
|
+
# @param [String, nil] parent The parent key of the current level of the Hash
|
22
|
+
# ( first level of any Hash has no parent, but if the hash has a value which is also a Hash,
|
23
|
+
# the parent of that value will be the key associated to the value )
|
24
|
+
# @param [Proc] &block The block which will be used to yield the parent string, the current key and the value,
|
25
|
+
# while the Hash is being iterated over
|
26
|
+
#
|
27
|
+
# @return [void]
|
4
28
|
def each_with_parent(parent = nil, &block)
|
5
29
|
each do |key, value|
|
6
30
|
if value.is_a?(Hash)
|
@@ -11,7 +35,17 @@ class Hash
|
|
11
35
|
end
|
12
36
|
end
|
13
37
|
|
38
|
+
# Checks if the value is a Hash , and will execute the each with parent for the given hash
|
39
|
+
# @see #each_with_parent
|
40
|
+
#
|
41
|
+
# @param [Hash] value The Hash that will be used for iteration
|
42
|
+
# @param [Hash] key The key that will be sent as the parent key of the specified Hash
|
43
|
+
# @param [Proc] &block The block which will be used to yield the parent string, the current key and the value,
|
44
|
+
# while the Hash is being iterated over
|
45
|
+
#
|
46
|
+
# @return [void]
|
14
47
|
def deep_hash_with_parent(value, key, &block)
|
48
|
+
return unless value.is_a?(Hash)
|
15
49
|
value.each_with_parent(key, &block)
|
16
50
|
end
|
17
51
|
end
|
@@ -1,15 +1,43 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
Zip.setup do |c|
|
3
|
+
# By default, rubyzip will not overwrite files if they already exist inside of the extracted path.
|
4
|
+
# To change this behavior, you may specify a configuration option like so:
|
3
5
|
c.on_exists_proc = true
|
6
|
+
# Additionally, if you want to configure rubyzip to overwrite existing files while creating a .zip file, you can do so with the following:
|
4
7
|
c.continue_on_exists_proc = true
|
8
|
+
# If you want to store non-english names and want to open them on Windows(pre 7) you need to set this option: ( We don't want that)
|
5
9
|
c.unicode_names = false
|
10
|
+
# Some zip files might have an invalid date format, which will raise a warning. You can hide this warning with the following setting:
|
11
|
+
c.warn_invalid_date = false
|
12
|
+
# You can set the default compression level like so: Possible values are Zlib::BEST_COMPRESSION, Zlib::DEFAULT_COMPRESSION and Zlib::NO_COMPRESSION
|
6
13
|
c.default_compression = Zlib::BEST_COMPRESSION
|
14
|
+
# To save zip archives in sorted order like below, you need to set ::Zip.sort_entries to true
|
15
|
+
c.sort_entries = true
|
7
16
|
end
|
8
17
|
|
9
|
-
Zip::File.class_eval do
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
18
|
+
# Zip::File.class_eval do
|
19
|
+
# singleton_class.send(:alias_method, :original_get_segment_size_for_split, :get_segment_size_for_split)
|
20
|
+
#
|
21
|
+
# # This method was overidden from the original method that contained this code
|
22
|
+
# # case
|
23
|
+
# # when MIN_SEGMENT_SIZE > segment_size
|
24
|
+
# # MIN_SEGMENT_SIZE
|
25
|
+
# # when MAX_SEGMENT_SIZE < segment_size
|
26
|
+
# # MAX_SEGMENT_SIZE
|
27
|
+
# # else
|
28
|
+
# # segment_size
|
29
|
+
# # end
|
30
|
+
# # where
|
31
|
+
# # MAX_SEGMENT_SIZE = 3_221_225_472 (1024 * 1024 * 1024 * 3)
|
32
|
+
# # MIN_SEGMENT_SIZE = 65_536 (1024 * 64)
|
33
|
+
# #
|
34
|
+
# # Because if you wanted to split a archive using a smaller size than the minimum size, it wouldn't be possible
|
35
|
+
# # because will always return the minimum size which is 64 Kb
|
36
|
+
# #
|
37
|
+
# # @param [Integer] segment_size the size that will be used to split an archive called by the Zip::File.split method
|
38
|
+
# #
|
39
|
+
# # @return [Integer] returns the size that will be used for splitting an archive
|
40
|
+
# def self.get_segment_size_for_split(segment_size)
|
41
|
+
# segment_size
|
42
|
+
# end
|
43
|
+
# end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module AsanaExceptionNotifier
|
3
|
+
# module that defines the available generators for this gem
|
3
4
|
module Generators
|
4
5
|
# module that is used for formatting numbers using metrics
|
5
6
|
class InstallGenerator < Rails::Generators::Base
|
@@ -7,6 +8,9 @@ module AsanaExceptionNotifier
|
|
7
8
|
|
8
9
|
source_root File.expand_path('../templates', __FILE__)
|
9
10
|
|
11
|
+
# This method will copy the template from this gem into the `config/initializers/asana_exception_notifier.rb` file in the application
|
12
|
+
#
|
13
|
+
# @return [void]
|
10
14
|
def copy_initializer
|
11
15
|
template 'asana_exception_notifier.rb', 'config/initializers/asana_exception_notifier.rb'
|
12
16
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: asana_exception_notifier
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- bogdanRada
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-08-
|
11
|
+
date: 2016-08-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|