asana_exception_notifier 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|