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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bfdc4b272be9f6414781dafe42e0162f1ffee60e
4
- data.tar.gz: f4f13163c9e1f825764b1d5c08c5934f97e75ded
3
+ metadata.gz: 4347775ca62029a21993e4af21d0ce3ab69fba95
4
+ data.tar.gz: dc0fbe28a472e7b9ed3c32d30f2539b60f3693b2
5
5
  SHA512:
6
- metadata.gz: 0f6f2005e5eebd44234b8f8307e171ef50aebf5f287a7d1055e1b6a7743c9612fd66f59659f6908dc4d6aab0dd86980345c7ac61765787167ebbecec9d9dfabf
7
- data.tar.gz: 2d4d567f75b6aa041c22e6fb7d7e3f9943218bf7b1c8afb11bb1c62e5aedfab82e0dd7871d6817e46eaccd0950d4fbb6bc137d03e07f8389517047e44e2a0b19
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: false).
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 github api and retrieves information about repository
3
+ # class used for connecting to connecting to Asana and creation of task and upload of archives
4
4
  #
5
- # @!attribute callback
6
- # @return [Proc] The callback that is executed after the info is fetched from Github API
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 :url_encoded
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, :boundary, :template_details, :env, :request, :tempfile, :template_params, :content
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 = compress_files(File.dirname(path), filename, [expanded_path(path)])
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
- attr_reader :arguments, :unsafe_options
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(expanded_path(path))
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] ReturnsReturns utf8 encoding of the msg
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
- # returns the logger used to log messages and errors
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
- def get_hash_rows(hash, rows = [], _prefix = '')
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
- get_object_rows(value, rows)
191
+ get_hash_rows(value, rows)
127
192
  else
128
- rows.push([key.inspect, escape(inspect_value(value).inspect)])
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('&', '&amp;').gsub('<', '&lt;').gsub('>', '&gt;')
217
+ text.to_s.gsub('&', '&amp;').gsub('<', '&lt;').gsub('>', '&gt;')
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
- # Mount table for hash, using name and value and adding a name_value class
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
- # @api public
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
- def compress_files(directory, name, files)
205
- archive = create_archive(directory, name)
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
- def create_archive(directory, name)
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
- <thead><tr><th>#{get_table_headers(header)}</th></tr></thead>
21
- <tbody>#{get_table_rows(array)}</tbody>
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
- singleton_class.send(:alias_method, :original_get_segment_size_for_split, :get_segment_size_for_split)
11
-
12
- def self.get_segment_size_for_split(segment_size)
13
- segment_size
14
- end
15
- end
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
@@ -16,7 +16,7 @@ module AsanaExceptionNotifier
16
16
  # major release version
17
17
  MAJOR = 0
18
18
  # minor release version
19
- MINOR = 4
19
+ MINOR = 5
20
20
  # tiny release version
21
21
  TINY = 0
22
22
  # prelease version ( set this only if it is a prelease)
@@ -22,6 +22,7 @@ require 'exception_notification'
22
22
  require 'asana'
23
23
  require 'typhoeus'
24
24
  require 'typhoeus/adapters/faraday'
25
+ require 'faraday_middleware'
25
26
 
26
27
  require 'rack'
27
28
  require 'zip'
@@ -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.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-08 00:00:00.000000000 Z
11
+ date: 2016-08-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport