eventhub-processor2 1.26.2 → 1.27.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
  SHA256:
3
- metadata.gz: 3195a1503417b8ac4ce8dc345ad7263ceacde2d473229ccbc1ad5b1c18e3a2e4
4
- data.tar.gz: cf9b94c789e85c2b05fc3e9eea1c4f4ef4773b6f29fec0bdbf58256e73fbe280
3
+ metadata.gz: 71103595a699bfa32e3c771073f3b870a5e10bba02a9191585e5bd2369a4fb8b
4
+ data.tar.gz: 82045e01a8f8c136ba5413c6a1153093fd441db66704352d58034f371602dbba
5
5
  SHA512:
6
- metadata.gz: 9d3236c5f6db00ff7baf430811d34c1d2eb10be68a9cc7ed69c3463dc637528a6fd9d2b9433c3b94dd36c014bb2413b07366bbd9ebfce364527921a18a5db9b1
7
- data.tar.gz: c40045da1746f9aea76bb658e8a309921e80becdae9979d8adaa7989d018f96564870145ac72a9a8af38b20a7dce3c661b13b9a53ca1352de6af3cc7ea2828af
6
+ metadata.gz: 99f35f36aacf9e25df8c7ecafc8c26dfd41f4dac7e3d486ae904aa8555b6af7b2c60688aab5b68c1cc1285117212c9b7b6e10ef1ce6b38489860bdd21094188e
7
+ data.tar.gz: a6011ab9c3345016e4f3b3c35d00685ea64635683f61ff38e7d6a555a8b1edd1993ce8e6be38460be45251d1e371b0810277a9df78d93891d155239aefe5f040
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog of EventHub::Processor2
2
2
 
3
+ # 1.27.0 / 2026-04-01
4
+
5
+ * Adapt to Bunny 3.0 publisher confirms: use `confirm_select(tracking: true)` for automatic backpressure, remove manual `wait_for_confirms` calls
6
+ * Remove `:configuration` from default HTTP resources to prevent automatic exposure of sensitive config on upgrade (opt-in via `http_resources` method)
7
+ * Improve configuration page: compact rendering for nested hashes and arrays, show `(not set)` for empty values, add client-side filter with reset button, visual distinction for top-level sections
8
+ * Fix deprecation logic to only warn when `heartbeat.*` and `http.*` values actually differ; remove defaults from deprecated `heartbeat` config block
9
+ * Fix example processors: add unique HTTP ports, create missing `data/` directory in publisher
10
+ * Fix flaky sleeper specs by widening timing tolerance and using idiomatic RSpec `be_within` matcher
11
+
3
12
  # 1.26.2 / 2026-03-25
4
13
 
5
14
  * Change default `http.bind_address` from `localhost` to `0.0.0.0` for container compatibility (ECS, Docker, K8s)
data/README.md CHANGED
@@ -321,7 +321,7 @@ Resources are mounted under the `base_path`:
321
321
  - `{base_path}/heartbeat` - Health check
322
322
  - `{base_path}/version` - Version info as JSON
323
323
  - `{base_path}/docs` - README documentation as HTML
324
- - `{base_path}/docs/configuration` - Configuration as HTML table
324
+ - `{base_path}/docs/configuration` - Configuration as HTML table (opt-in, see [Enabling Resources](#enabling-resources))
325
325
  - `{base_path}/docs/changelog` - CHANGELOG as HTML
326
326
  - `{base_path}/assets/*` - Static assets (CSS, images)
327
327
 
@@ -469,7 +469,7 @@ GET {base_path}/docs/configuration
469
469
 
470
470
  **Response:** `200 OK` with HTML page
471
471
 
472
- By default, the following keys are redacted: `password`, `secret`, `token`, `api_key`, `credential`. You can customize the list by defining a `sensitive_keys` method in your processor:
472
+ By default, the following keys are redacted: `password`, `secret`, `token`, `api_key`, `credential`, `username`, `user`, `login`. You can customize the list by defining a `sensitive_keys` method in your processor:
473
473
 
474
474
  ```ruby
475
475
  # Override the entire list
@@ -497,28 +497,22 @@ class MyProcessor < EventHub::Processor2
497
497
  end
498
498
  ```
499
499
 
500
- ### Disabling Resources
500
+ ### Enabling Resources
501
501
 
502
- By default, all HTTP resources are enabled. You can control which resources are available by defining an `http_resources` method in your processor. The navbar adapts automatically.
502
+ By default, the following HTTP resources are enabled: `:heartbeat`, `:version`, `:docs`, and `:changelog`. The `:configuration` resource is **disabled by default** because it displays server configuration which may contain sensitive values. Although passwords, tokens, and keys are automatically redacted, we prefer a secure-by-default approach where processors must explicitly opt in to exposing configuration.
503
503
 
504
- ```ruby
505
- class MyProcessor < EventHub::Processor2
506
- def http_resources
507
- [:heartbeat, :version, :docs, :changelog, :configuration] # default: all enabled
508
- end
509
- end
510
- ```
511
-
512
- To disable the configuration page for example:
504
+ To enable the configuration page, define an `http_resources` method in your processor:
513
505
 
514
506
  ```ruby
515
507
  class MyProcessor < EventHub::Processor2
516
508
  def http_resources
517
- [:heartbeat, :version, :docs, :changelog]
509
+ [:heartbeat, :version, :docs, :changelog, :configuration]
518
510
  end
519
511
  end
520
512
  ```
521
513
 
514
+ You can also use `http_resources` to disable any of the default resources. The navbar adapts automatically.
515
+
522
516
  ### Customizing Footer
523
517
 
524
518
  The documentation pages display company name, version, and environment in the footer. Company name defaults to "Novartis" but can be customized by defining a `company_name` method in your processor:
@@ -24,7 +24,7 @@ Gem::Specification.new do |spec|
24
24
 
25
25
  spec.add_dependency "celluloid", "~> 0.18"
26
26
  spec.add_dependency "webrick", "~> 1.8"
27
- spec.add_dependency "bunny", "~> 2.23"
27
+ spec.add_dependency "bunny", "~> 3.0"
28
28
  spec.add_dependency "eventhub-components", "~> 0.4"
29
29
  spec.add_dependency "base64", "~> 0.3.0"
30
30
  spec.add_dependency "logger", "~> 1.6"
@@ -0,0 +1,15 @@
1
+ {
2
+ "development": {
3
+ "server": {
4
+ "user": "guest",
5
+ "password": "guest",
6
+ "host": "localhost",
7
+ "vhost": "event_hub",
8
+ "port": 5672,
9
+ "tls": false,
10
+ "http": {
11
+ "port": 8083
12
+ }
13
+ }
14
+ }
15
+ }
@@ -6,7 +6,10 @@
6
6
  "host": "localhost",
7
7
  "vhost": "event_hub",
8
8
  "port": 5672,
9
- "tls": false
9
+ "tls": false,
10
+ "http": {
11
+ "port": 8081
12
+ }
10
13
  },
11
14
  "processor": {
12
15
  "listener_queues": [
@@ -6,7 +6,10 @@
6
6
  "host": "localhost",
7
7
  "vhost": "event_hub",
8
8
  "port": 5672,
9
- "tls": false
9
+ "tls": false,
10
+ "http": {
11
+ "port": 8082
12
+ }
10
13
  },
11
14
  "processor": {
12
15
  "listener_queues": [
data/example/example.rb CHANGED
@@ -6,6 +6,10 @@ module EventHub
6
6
  "1.0.0" # define your version
7
7
  end
8
8
 
9
+ def http_resources
10
+ [:heartbeat, :version, :docs, :changelog, :configuration]
11
+ end
12
+
9
13
  def handle_message(message, args = {})
10
14
  # deal with your parsed EventHub message
11
15
  # message.class => EventHub::Message
data/example/publisher.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require "bunny"
2
2
  require "celluloid"
3
+ require "fileutils"
3
4
  require "json"
4
5
  require "securerandom"
5
6
  require "eventhub/components"
@@ -38,6 +39,7 @@ module Publisher
38
39
  @files_sent = 0
39
40
 
40
41
  @filename = "data/store.json"
42
+ FileUtils.mkdir_p(File.dirname(@filename))
41
43
  if File.exist?(@filename)
42
44
  cleanup
43
45
  else
@@ -119,7 +121,7 @@ module Publisher
119
121
  logger: Logger.new(File::NULL))
120
122
  @connection.start
121
123
  @channel = @connection.create_channel
122
- @channel.confirm_select
124
+ @channel.confirm_select(tracking: true)
123
125
  @exchange = @channel.direct("example.outbound", durable: true)
124
126
  end
125
127
 
@@ -135,13 +137,8 @@ module Publisher
135
137
  Publisher.logger.info("[#{id}] - Message/File created")
136
138
 
137
139
  @exchange.publish(data, persistent: true)
138
- success = @channel.wait_for_confirms
139
- if success
140
- Celluloid::Actor[:transaction_store]&.stop(id)
141
- Publisher&.logger&.info("[#{id}] - Message sent")
142
- else
143
- Publisher&.logger&.error("[#{id}] - Published message not confirmed")
144
- end
140
+ Celluloid::Actor[:transaction_store]&.stop(id)
141
+ Publisher&.logger&.info("[#{id}] - Message sent")
145
142
  end
146
143
  end
147
144
 
data/example/receiver.rb CHANGED
@@ -10,6 +10,10 @@ module EventHub
10
10
  "Example Company"
11
11
  end
12
12
 
13
+ def http_resources
14
+ [:heartbeat, :version, :docs, :changelog, :configuration]
15
+ end
16
+
13
17
  # Custom README served via method instead of file
14
18
  def readme_as_html
15
19
  <<~HTML
data/example/router.rb CHANGED
@@ -7,6 +7,10 @@ module EventHub
7
7
  "1.0.0"
8
8
  end
9
9
 
10
+ def http_resources
11
+ [:heartbeat, :version, :docs, :changelog, :configuration]
12
+ end
13
+
10
14
  def handle_message(message, args = {})
11
15
  id = message.body["id"]
12
16
  EventHub.logger.info("Received: [#{id}]")
@@ -38,15 +38,9 @@ module EventHub
38
38
  connection = create_bunny_connection
39
39
  connection.start
40
40
  channel = connection.create_channel
41
- channel.confirm_select
41
+ channel.confirm_select(tracking: true)
42
42
  exchange = channel.direct(EventHub::EH_X_INBOUND, durable: true)
43
43
  exchange.publish(message, persistent: true)
44
- success = channel.wait_for_confirms
45
-
46
- unless success
47
- raise "Published heartbeat message has " \
48
- "not been confirmed by the server"
49
- end
50
44
  ensure
51
45
  connection&.close
52
46
  end
@@ -10,7 +10,7 @@ module EventHub
10
10
  include Helper
11
11
 
12
12
  DEFAULT_VERSION = "?.?.?"
13
- DEFAULT_HTTP_RESOURCES = [:heartbeat, :version, :docs, :changelog, :configuration].freeze
13
+ DEFAULT_HTTP_RESOURCES = [:heartbeat, :version, :docs, :changelog].freeze
14
14
  CONTENT_TYPES = {
15
15
  ".css" => "text/css",
16
16
  ".svg" => "image/svg+xml",
@@ -183,16 +183,17 @@ module EventHub
183
183
  private
184
184
 
185
185
  def http_config(key)
186
- # Try new http config first, fall back to deprecated heartbeat config
186
+ # Prefer deprecated heartbeat config for backward compatibility,
187
+ # fall back to http config. Only warn when values actually differ.
187
188
  heartbeat_value = EventHub::Configuration.server.dig(:heartbeat, key)
188
189
  http_value = EventHub::Configuration.server.dig(:http, key)
189
190
 
190
- if heartbeat_value && http_value != heartbeat_value
191
+ if heartbeat_value && http_value && heartbeat_value != http_value
191
192
  EventHub.logger.warn("[DEPRECATION] heartbeat.#{key} is deprecated. Please use http.#{key} instead.")
192
193
  return heartbeat_value
193
194
  end
194
195
 
195
- http_value
196
+ http_value || heartbeat_value
196
197
  end
197
198
 
198
199
  def resource_enabled?(name)
@@ -25,7 +25,7 @@ module EventHub
25
25
  exchange_name = args[:exchange_name] || EH_X_INBOUND
26
26
 
27
27
  channel = @connection.create_channel
28
- channel.confirm_select
28
+ channel.confirm_select(tracking: true)
29
29
  exchange = channel.direct(exchange_name, durable: true)
30
30
 
31
31
  publish_options = {persistent: true}
@@ -33,12 +33,6 @@ module EventHub
33
33
  publish_options[:correlation_id] = correlation_id if correlation_id
34
34
 
35
35
  exchange.publish(message, publish_options)
36
- success = channel.wait_for_confirms
37
-
38
- unless success
39
- raise "Published message from Listener actor " \
40
- "has not been confirmed by the server"
41
- end
42
36
  ensure
43
37
  channel&.close
44
38
  end
@@ -94,6 +94,54 @@ body {
94
94
  width: auto;
95
95
  }
96
96
 
97
+ /* Config filter */
98
+ .config-filter {
99
+ margin-bottom: 1rem;
100
+ }
101
+
102
+ .config-filter-row {
103
+ display: flex;
104
+ align-items: center;
105
+ max-width: 400px;
106
+ }
107
+
108
+ .config-filter .input {
109
+ flex: 1;
110
+ padding: 0.5rem 0.75rem;
111
+ font-size: 0.95rem;
112
+ border: 1px solid #dbdbdb;
113
+ border-radius: 4px 0 0 4px;
114
+ }
115
+
116
+ .config-filter .input:focus {
117
+ border-color: hsl(212, 55%, 48%);
118
+ outline: none;
119
+ box-shadow: 0 0 0 2px hsla(212, 55%, 48%, 0.2);
120
+ }
121
+
122
+ .config-filter-reset {
123
+ padding: 0 0.75rem;
124
+ font-size: 1.1rem;
125
+ border: 1px solid #dbdbdb;
126
+ border-left: none;
127
+ border-radius: 0 4px 4px 0;
128
+ background: #f5f5f5;
129
+ color: #7a7a7a;
130
+ cursor: pointer;
131
+ align-self: stretch;
132
+ }
133
+
134
+ .config-filter-reset:hover {
135
+ background: #e8e8e8;
136
+ color: #363636;
137
+ }
138
+
139
+ .config-filter-count {
140
+ margin-top: 0.25rem;
141
+ font-size: 0.85rem;
142
+ color: #7a7a7a;
143
+ }
144
+
97
145
  /* Config table */
98
146
  .config-table thead th {
99
147
  background-color: #1a1a1a !important;
@@ -107,6 +155,10 @@ body {
107
155
  font-weight: 600;
108
156
  }
109
157
 
158
+ .config-table .is-section-top td {
159
+ background-color: #d5d5d5;
160
+ }
161
+
110
162
  .config-table tbody tr:not(.is-section):hover td {
111
163
  background-color: hsl(212, 55%, 93%) !important;
112
164
  }
@@ -116,11 +168,37 @@ body {
116
168
  padding-left: 2rem;
117
169
  }
118
170
 
119
- .config-table .redacted {
171
+ .config-table .redacted,
172
+ .config-table .not-set {
120
173
  color: #b5b5b5;
121
174
  font-style: italic;
122
175
  }
123
176
 
177
+ .config-array {
178
+ list-style: none;
179
+ margin: 0;
180
+ padding: 0;
181
+ }
182
+
183
+ .config-array > li {
184
+ padding: 0.25rem 0;
185
+ }
186
+
187
+ .config-array > li + li {
188
+ border-top: 1px solid #e8e8e8;
189
+ }
190
+
191
+ .config-subtable {
192
+ margin: 0 !important;
193
+ font-size: 0.9rem;
194
+ }
195
+
196
+ .config-subtable td:first-child {
197
+ font-weight: 600;
198
+ white-space: nowrap;
199
+ width: 1%;
200
+ }
201
+
124
202
  /* Footer */
125
203
  .footer {
126
204
  padding: 1.5rem;
@@ -147,11 +147,7 @@ module EventHub
147
147
  }
148
148
  },
149
149
  # deprecated: use http instead (kept for backward compatibility)
150
- heartbeat: {
151
- bind_address: "0.0.0.0",
152
- port: 8080,
153
- path: "/svc/#{@name}/heartbeat"
154
- }
150
+ heartbeat: {}
155
151
  },
156
152
  processor: {
157
153
  heartbeat_cycle_in_s: 300,
@@ -10,7 +10,7 @@ module EventHub
10
10
  DEFAULT_CHANGELOG_LOCATIONS = ["CHANGELOG.md", "doc/CHANGELOG.md"].freeze
11
11
  DEFAULT_COMPANY_NAME = "Novartis"
12
12
 
13
- DEFAULT_HTTP_RESOURCES = [:heartbeat, :version, :docs, :changelog, :configuration].freeze
13
+ DEFAULT_HTTP_RESOURCES = [:heartbeat, :version, :docs, :changelog].freeze
14
14
 
15
15
  def initialize(processor:, base_path:)
16
16
  @processor = processor
@@ -91,19 +91,101 @@ module EventHub
91
91
  "<p>Active configuration for the <strong>#{ERB::Util.html_escape(EventHub::Configuration.environment)}</strong> environment. " \
92
92
  "Sensitive values such as passwords, tokens, and keys are automatically redacted.</p>"
93
93
 
94
- intro + config_to_html_table(config)
94
+ filter = '<div class="config-filter">' \
95
+ '<div class="config-filter-row">' \
96
+ '<input type="text" id="config-filter-input" class="input" placeholder="Filter configuration keys..." autocomplete="off">' \
97
+ '<button type="button" id="config-filter-reset" class="config-filter-reset" title="Reset filter">&times;</button>' \
98
+ "</div>" \
99
+ '<p id="config-filter-count" class="config-filter-count"></p>' \
100
+ "</div>"
101
+
102
+ script = <<~JS
103
+ <script>
104
+ (function() {
105
+ var input = document.getElementById('config-filter-input');
106
+ var reset = document.getElementById('config-filter-reset');
107
+ var count = document.getElementById('config-filter-count');
108
+ var table = document.querySelector('.config-table');
109
+ if (!input || !table) return;
110
+
111
+ input.addEventListener('input', function() {
112
+ var term = this.value.toLowerCase();
113
+ var rows = table.querySelectorAll('tbody tr');
114
+ var visible = 0;
115
+ var total = 0;
116
+
117
+ // Filter individual rows by content (includes sub-tables)
118
+ rows.forEach(function(row) {
119
+ if (row.classList.contains('is-section')) {
120
+ row.style.display = '';
121
+ return;
122
+ }
123
+ total++;
124
+ if (!term || row.textContent.toLowerCase().indexOf(term) !== -1) {
125
+ row.style.display = '';
126
+ visible++;
127
+ } else {
128
+ row.style.display = 'none';
129
+ }
130
+ });
131
+
132
+ // Hide section headers with no visible rows after them
133
+ var sections = table.querySelectorAll('tbody tr.is-section');
134
+ sections.forEach(function(section) {
135
+ var next = section.nextElementSibling;
136
+ var hasVisible = false;
137
+ while (next && !next.classList.contains('is-section')) {
138
+ if (next.style.display !== 'none') hasVisible = true;
139
+ next = next.nextElementSibling;
140
+ }
141
+ section.style.display = hasVisible ? '' : 'none';
142
+ });
143
+
144
+ if (term) {
145
+ count.textContent = visible + ' of ' + total + ' entries';
146
+ } else {
147
+ count.textContent = '';
148
+ }
149
+ });
150
+ reset.addEventListener('click', function() {
151
+ input.value = '';
152
+ input.dispatchEvent(new Event('input'));
153
+ input.focus();
154
+ });
155
+ })();
156
+ </script>
157
+ JS
158
+
159
+ intro + filter + config_to_html_table(config) + script
95
160
  end
96
161
 
97
- def config_to_html_table(hash, depth = 0)
162
+ def config_to_html_table(hash, depth = 0, prefix = "")
98
163
  rows = hash.map do |key, value|
99
- if value.is_a?(Hash)
100
- "<tr class=\"is-section\"><td colspan=\"2\"><strong>#{ERB::Util.html_escape(key)}</strong></td></tr>\n" \
101
- "#{config_to_html_table(value, depth + 1)}"
164
+ full_key = prefix.empty? ? key.to_s : "#{prefix}.#{key}"
165
+ if depth == 0 && value.is_a?(Hash) && !value.empty?
166
+ "<tr class=\"is-section is-section-top\"><td colspan=\"2\"><strong>#{ERB::Util.html_escape(full_key)}</strong></td></tr>\n" \
167
+ "#{config_to_html_table(value, 1, full_key)}"
168
+ elsif value.is_a?(Hash) && value.empty?
169
+ "<tr><td class=\"config-key\">#{ERB::Util.html_escape(full_key)}</td><td><span class=\"not-set\">(empty)</span></td></tr>"
170
+ elsif value.is_a?(Hash) && value.values.all? { |v| v.is_a?(Hash) && v.empty? }
171
+ items = value.keys.map { |k| "<li>#{ERB::Util.html_escape(k)}</li>" }.join("\n")
172
+ "<tr><td class=\"config-key\">#{ERB::Util.html_escape(full_key)}</td><td><ul class=\"config-array\">#{items}</ul></td></tr>"
173
+ elsif value.is_a?(Hash) && compact_hash?(value)
174
+ "<tr><td class=\"config-key\">#{ERB::Util.html_escape(full_key)}</td><td>#{format_nested_value(value)}</td></tr>"
175
+ elsif value.is_a?(Hash)
176
+ "<tr class=\"is-section\"><td colspan=\"2\"><strong>#{ERB::Util.html_escape(full_key)}</strong></td></tr>\n" \
177
+ "#{config_to_html_table(value, depth + 1, full_key)}"
102
178
  elsif value.is_a?(Array)
103
- format_array_rows(key, value, depth)
179
+ format_array_rows(full_key, key, value, depth)
104
180
  else
105
- display_value = sensitive_key?(key) ? "<span class=\"redacted\">[REDACTED]</span>" : ERB::Util.html_escape(value.to_s)
106
- "<tr><td class=\"config-key\">#{ERB::Util.html_escape(key)}</td><td>#{display_value}</td></tr>"
181
+ display_value = if sensitive_key?(key)
182
+ "<span class=\"redacted\">***</span>"
183
+ elsif value.nil? || value.to_s.strip.empty?
184
+ "<span class=\"not-set\">(not set)</span>"
185
+ else
186
+ ERB::Util.html_escape(value.to_s)
187
+ end
188
+ "<tr><td class=\"config-key\">#{ERB::Util.html_escape(full_key)}</td><td>#{display_value}</td></tr>"
107
189
  end
108
190
  end.join("\n")
109
191
 
@@ -114,24 +196,59 @@ module EventHub
114
196
  end
115
197
  end
116
198
 
117
- def format_array_rows(key, array, depth)
118
- if array.any? { |item| item.is_a?(Hash) }
119
- array.each_with_index.map do |item, index|
120
- if item.is_a?(Hash)
121
- "<tr class=\"is-section\"><td colspan=\"2\"><strong>#{ERB::Util.html_escape(key)}[#{index}]</strong></td></tr>\n" \
122
- "#{config_to_html_table(item, depth + 1)}"
123
- else
124
- display_value = sensitive_key?(key) ? "<span class=\"redacted\">[REDACTED]</span>" : ERB::Util.html_escape(item.to_s)
125
- "<tr><td class=\"config-key\">#{ERB::Util.html_escape(key)}[#{index}]</td><td>#{display_value}</td></tr>"
126
- end
127
- end.join("\n")
199
+ def format_array_rows(full_key, key, array, _depth)
200
+ return "<tr><td class=\"config-key\">#{ERB::Util.html_escape(full_key)}</td><td><span class=\"not-set\">(empty)</span></td></tr>" if array.empty?
201
+
202
+ if sensitive_key?(key)
203
+ return "<tr><td class=\"config-key\">#{ERB::Util.html_escape(full_key)}</td><td><span class=\"redacted\">***</span></td></tr>"
204
+ end
205
+
206
+ inner = array.map { |item| format_array_item(item) }.join("\n")
207
+ "<tr><td class=\"config-key\">#{ERB::Util.html_escape(full_key)}</td><td><ul class=\"config-array\">#{inner}</ul></td></tr>"
208
+ end
209
+
210
+ def format_array_item(item)
211
+ if item.is_a?(Hash)
212
+ rows = item.map do |k, v|
213
+ value = format_nested_value(v)
214
+ "<tr><td>#{ERB::Util.html_escape(k)}</td><td>#{value}</td></tr>"
215
+ end.join
216
+ "<li><table class=\"table is-bordered is-narrow config-subtable\">#{rows}</table></li>"
217
+ elsif item.is_a?(Array)
218
+ inner = item.map { |i| format_array_item(i) }.join("\n")
219
+ "<li><ul class=\"config-array\">#{inner}</ul></li>"
128
220
  else
129
- display_value = sensitive_key?(key) ? "<span class=\"redacted\">[REDACTED]</span>" : ERB::Util.html_escape(array.join(", "))
130
- "<tr><td class=\"config-key\">#{ERB::Util.html_escape(key)}</td><td>#{display_value}</td></tr>"
221
+ "<li>#{ERB::Util.html_escape(item.to_s)}</li>"
222
+ end
223
+ end
224
+
225
+ def format_nested_value(value)
226
+ if value.is_a?(Hash)
227
+ rows = value.map do |k, v|
228
+ "<tr><td>#{ERB::Util.html_escape(k)}</td><td>#{format_nested_value(v)}</td></tr>"
229
+ end.join
230
+ "<table class=\"table is-bordered is-narrow config-subtable\">#{rows}</table>"
231
+ elsif value.is_a?(Array)
232
+ items = value.map { |i| format_array_item(i) }.join("\n")
233
+ "<ul class=\"config-array\">#{items}</ul>"
234
+ elsif value.nil? || value.to_s.strip.empty?
235
+ "<span class=\"not-set\">(not set)</span>"
236
+ else
237
+ ERB::Util.html_escape(value.to_s)
238
+ end
239
+ end
240
+
241
+ def compact_hash?(hash)
242
+ hash.values.all? do |v|
243
+ if v.is_a?(Hash)
244
+ compact_hash?(v)
245
+ else
246
+ !v.is_a?(Array)
247
+ end
131
248
  end
132
249
  end
133
250
 
134
- DEFAULT_SENSITIVE_KEYS = %w[password secret token api_key credential].freeze
251
+ DEFAULT_SENSITIVE_KEYS = %w[password secret token api_key credential username user login].freeze
135
252
 
136
253
  def sensitive_key?(key)
137
254
  keys = if @processor&.class&.method_defined?(:sensitive_keys)
@@ -1,3 +1,3 @@
1
1
  module EventHub
2
- VERSION = "1.26.2".freeze
2
+ VERSION = "1.27.0".freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eventhub-processor2
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.26.2
4
+ version: 1.27.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steiner, Thomas
@@ -43,14 +43,14 @@ dependencies:
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: '2.23'
46
+ version: '3.0'
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: '2.23'
53
+ version: '3.0'
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: eventhub-components
56
56
  requirement: !ruby/object:Gem::Requirement
@@ -192,6 +192,7 @@ files:
192
192
  - eventhub-processor2.gemspec
193
193
  - example/CHANGELOG.md
194
194
  - example/README.md
195
+ - example/config/example.json
195
196
  - example/config/receiver.json
196
197
  - example/config/router.json
197
198
  - example/crasher.rb