source_monitor 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.claude/skills/sm-configuration-setting/reference/settings-catalog.md +1 -0
- data/.claude/skills/sm-configure/SKILL.md +8 -1
- data/.claude/skills/sm-configure/reference/configuration-reference.md +11 -0
- data/.claude/skills/sm-event-handler/SKILL.md +1 -1
- data/.claude/skills/sm-event-handler/reference/events-api.md +1 -1
- data/.claude/skills/sm-host-setup/SKILL.md +13 -3
- data/.claude/skills/sm-host-setup/reference/initializer-template.md +11 -0
- data/.claude/skills/sm-host-setup/reference/setup-checklist.md +9 -1
- data/.claude/skills/sm-upgrade/reference/version-history.md +12 -0
- data/CHANGELOG.md +19 -0
- data/Gemfile.lock +1 -1
- data/README.md +3 -3
- data/VERSION +1 -1
- data/app/assets/builds/source_monitor/application.css +4 -0
- data/app/controllers/source_monitor/application_controller.rb +73 -14
- data/app/controllers/source_monitor/bulk_scrape_enablements_controller.rb +1 -1
- data/app/controllers/source_monitor/import_sessions/bulk_configuration.rb +3 -1
- data/app/controllers/source_monitor/import_sessions_controller.rb +118 -72
- data/app/controllers/source_monitor/sources_controller.rb +4 -18
- data/app/models/source_monitor/source.rb +1 -1
- data/app/views/layouts/source_monitor/application.html.erb +6 -0
- data/docs/configuration.md +18 -1
- data/docs/deployment.md +1 -1
- data/docs/goals/engine-hardening/.goalbuddy-board/app.js +543 -0
- data/docs/goals/engine-hardening/.goalbuddy-board/goalbuddy-mark.png +0 -0
- data/docs/goals/engine-hardening/.goalbuddy-board/index.html +111 -0
- data/docs/goals/engine-hardening/.goalbuddy-board/styles.css +991 -0
- data/docs/goals/engine-hardening/goal.md +97 -0
- data/docs/goals/engine-hardening/notes/T001-spec-validation.md +37 -0
- data/docs/goals/engine-hardening/state.yaml +324 -0
- data/docs/setup.md +3 -3
- data/docs/upgrade.md +41 -0
- data/lib/generators/source_monitor/install/templates/source_monitor.rb.tt +10 -0
- data/lib/source_monitor/analytics/scrape_recommendations.rb +21 -2
- data/lib/source_monitor/configuration/authentication_settings.rb +5 -1
- data/lib/source_monitor/fetching/feed_fetcher/failure_outcome.rb +85 -0
- data/lib/source_monitor/fetching/feed_fetcher/success_outcome.rb +85 -0
- data/lib/source_monitor/fetching/feed_fetcher.rb +27 -88
- data/lib/source_monitor/fetching/fetch_runner.rb +12 -5
- data/lib/source_monitor/import_sessions/wizard.rb +612 -0
- data/lib/source_monitor/items/batch_item_creator.rb +7 -6
- data/lib/source_monitor/items/item_creator.rb +7 -14
- data/lib/source_monitor/items/normalized_entry.rb +61 -0
- data/lib/source_monitor/security/authentication.rb +10 -0
- data/lib/source_monitor/version.rb +1 -1
- data/lib/source_monitor.rb +2 -0
- data/source_monitor.gemspec +7 -2
- metadata +12 -68
- data/.claude/agent-memory/vbw-vbw-debugger/MEMORY.md +0 -15
- data/.claude/agent-memory/vbw-vbw-dev/MEMORY.md +0 -34
- data/.claude/agent-memory/vbw-vbw-lead/MEMORY.md +0 -49
- data/.claude/agents/rails-concern.md +0 -464
- data/.claude/agents/rails-controller.md +0 -424
- data/.claude/agents/rails-hotwire.md +0 -446
- data/.claude/agents/rails-implement.md +0 -374
- data/.claude/agents/rails-job.md +0 -334
- data/.claude/agents/rails-lint.md +0 -294
- data/.claude/agents/rails-mailer.md +0 -371
- data/.claude/agents/rails-migration.md +0 -449
- data/.claude/agents/rails-model.md +0 -420
- data/.claude/agents/rails-policy.md +0 -443
- data/.claude/agents/rails-presenter.md +0 -427
- data/.claude/agents/rails-query.md +0 -412
- data/.claude/agents/rails-review.md +0 -490
- data/.claude/agents/rails-service.md +0 -458
- data/.claude/agents/rails-state-records.md +0 -465
- data/.claude/agents/rails-tdd.md +0 -314
- data/.claude/agents/rails-test.md +0 -441
- data/.claude/agents/rails-view-component.md +0 -418
- data/.claude/commands/rails-audit.md +0 -77
- data/.claude/commands/release.md +0 -366
- data/.claude/hooks/block-secrets.sh +0 -52
- data/.claude/settings.json +0 -85
- data/.claude/skills/action-cable-patterns/SKILL.md +0 -296
- data/.claude/skills/action-mailer-patterns/SKILL.md +0 -295
- data/.claude/skills/active-storage-setup/SKILL.md +0 -311
- data/.claude/skills/api-versioning/SKILL.md +0 -294
- data/.claude/skills/authentication-flow/SKILL.md +0 -335
- data/.claude/skills/authentication-flow/reference/current.md +0 -248
- data/.claude/skills/authentication-flow/reference/passwordless.md +0 -253
- data/.claude/skills/authentication-flow/reference/sessions.md +0 -201
- data/.claude/skills/authorization-pundit/SKILL.md +0 -462
- data/.claude/skills/caching-strategies/SKILL.md +0 -350
- data/.claude/skills/database-migrations/SKILL.md +0 -354
- data/.claude/skills/form-object-patterns/SKILL.md +0 -399
- data/.claude/skills/hotwire-patterns/SKILL.md +0 -247
- data/.claude/skills/hotwire-patterns/reference/stimulus.md +0 -307
- data/.claude/skills/hotwire-patterns/reference/tailwind-integration.md +0 -112
- data/.claude/skills/hotwire-patterns/reference/turbo-frames.md +0 -158
- data/.claude/skills/hotwire-patterns/reference/turbo-streams.md +0 -218
- data/.claude/skills/i18n-patterns/SKILL.md +0 -320
- data/.claude/skills/install/SKILL.md +0 -367
- data/.claude/skills/performance-optimization/SKILL.md +0 -311
- data/.claude/skills/rails-architecture/SKILL.md +0 -259
- data/.claude/skills/rails-architecture/reference/error-handling.md +0 -333
- data/.claude/skills/rails-architecture/reference/event-tracking.md +0 -142
- data/.claude/skills/rails-architecture/reference/layer-interactions.md +0 -417
- data/.claude/skills/rails-architecture/reference/multi-tenancy.md +0 -152
- data/.claude/skills/rails-architecture/reference/query-patterns.md +0 -342
- data/.claude/skills/rails-architecture/reference/service-patterns.md +0 -286
- data/.claude/skills/rails-architecture/reference/state-records.md +0 -250
- data/.claude/skills/rails-architecture/reference/testing-strategy.md +0 -326
- data/.claude/skills/rails-concern/SKILL.md +0 -399
- data/.claude/skills/rails-controller/SKILL.md +0 -336
- data/.claude/skills/rails-model-generator/SKILL.md +0 -321
- data/.claude/skills/rails-model-generator/reference/validations.md +0 -298
- data/.claude/skills/rails-presenter/SKILL.md +0 -274
- data/.claude/skills/rails-query-object/SKILL.md +0 -289
- data/.claude/skills/rails-service-object/SKILL.md +0 -349
- data/.claude/skills/solid-queue-setup/SKILL.md +0 -307
- data/.claude/skills/tdd-cycle/SKILL.md +0 -359
- data/.claude/skills/viewcomponent-patterns/SKILL.md +0 -333
- data/app/controllers/source_monitor/import_sessions/entry_annotation.rb +0 -187
- data/app/controllers/source_monitor/import_sessions/health_check_management.rb +0 -112
- data/app/controllers/source_monitor/import_sessions/opml_parser.rb +0 -130
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SourceMonitor
|
|
4
|
+
module Fetching
|
|
5
|
+
class FeedFetcher
|
|
6
|
+
class FailureOutcome
|
|
7
|
+
def initialize(error:)
|
|
8
|
+
@error = error
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
attr_reader :error, :retry_decision
|
|
12
|
+
|
|
13
|
+
def apply(source_updater:, started_at:, instrumentation_payload:)
|
|
14
|
+
duration_ms = source_updater.elapsed_ms(started_at)
|
|
15
|
+
@retry_decision = update_source(source_updater, duration_ms)
|
|
16
|
+
create_fetch_log(source_updater, duration_ms, started_at)
|
|
17
|
+
apply_instrumentation(instrumentation_payload)
|
|
18
|
+
result
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def status
|
|
22
|
+
:failed
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def response
|
|
26
|
+
error.response
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def body
|
|
30
|
+
response&.body
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def feed
|
|
34
|
+
nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def item_processing
|
|
38
|
+
@item_processing ||= EntryProcessingResult.empty
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def update_source(source_updater, duration_ms)
|
|
44
|
+
source_updater.update_source_for_failure(error, duration_ms)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def create_fetch_log(source_updater, duration_ms, started_at)
|
|
48
|
+
source_updater.create_fetch_log(
|
|
49
|
+
response: response,
|
|
50
|
+
duration_ms: duration_ms,
|
|
51
|
+
started_at: started_at,
|
|
52
|
+
success: false,
|
|
53
|
+
error: error,
|
|
54
|
+
body: body
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def apply_instrumentation(instrumentation_payload)
|
|
59
|
+
instrumentation_payload[:success] = false
|
|
60
|
+
instrumentation_payload[:status] = status
|
|
61
|
+
instrumentation_payload[:error_class] = error.class.name
|
|
62
|
+
instrumentation_payload[:error_message] = error.message
|
|
63
|
+
instrumentation_payload[:http_status] = error.http_status if error.http_status
|
|
64
|
+
instrumentation_payload[:error_code] = error.code if error.respond_to?(:code)
|
|
65
|
+
instrumentation_payload[:items_created] = 0
|
|
66
|
+
instrumentation_payload[:items_updated] = 0
|
|
67
|
+
instrumentation_payload[:items_failed] = 0
|
|
68
|
+
instrumentation_payload[:retry_attempt] = retry_decision&.next_attempt ? retry_decision.next_attempt : 0
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def result
|
|
72
|
+
Result.new(
|
|
73
|
+
status: status,
|
|
74
|
+
response: response,
|
|
75
|
+
body: body,
|
|
76
|
+
error: error,
|
|
77
|
+
retry_decision: retry_decision,
|
|
78
|
+
item_processing: item_processing,
|
|
79
|
+
outcome: self
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SourceMonitor
|
|
4
|
+
module Fetching
|
|
5
|
+
class FeedFetcher
|
|
6
|
+
class SuccessOutcome
|
|
7
|
+
def initialize(response:, body:, feed:, item_processing:, feed_signature:, content_changed:, entries_digest:)
|
|
8
|
+
@response = response
|
|
9
|
+
@body = body
|
|
10
|
+
@feed = feed
|
|
11
|
+
@item_processing = item_processing
|
|
12
|
+
@feed_signature = feed_signature
|
|
13
|
+
@content_changed = content_changed
|
|
14
|
+
@entries_digest = entries_digest
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
attr_reader :response, :body, :feed, :item_processing, :feed_signature, :content_changed, :entries_digest
|
|
18
|
+
|
|
19
|
+
def apply(source_updater:, started_at:, instrumentation_payload:)
|
|
20
|
+
duration_ms = source_updater.elapsed_ms(started_at)
|
|
21
|
+
update_source(source_updater, duration_ms)
|
|
22
|
+
create_fetch_log(source_updater, duration_ms, started_at)
|
|
23
|
+
apply_instrumentation(instrumentation_payload)
|
|
24
|
+
result
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def status
|
|
28
|
+
:fetched
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def error
|
|
32
|
+
nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def retry_decision
|
|
36
|
+
nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def result
|
|
40
|
+
Result.new(status: status, feed: feed, response: response, body: body, item_processing: item_processing, outcome: self)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def update_source(source_updater, duration_ms)
|
|
46
|
+
source_updater.update_source_for_success(
|
|
47
|
+
response,
|
|
48
|
+
duration_ms,
|
|
49
|
+
feed,
|
|
50
|
+
feed_signature,
|
|
51
|
+
content_changed: content_changed,
|
|
52
|
+
entries_digest: entries_digest
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def create_fetch_log(source_updater, duration_ms, started_at)
|
|
57
|
+
source_updater.create_fetch_log(
|
|
58
|
+
response: response,
|
|
59
|
+
duration_ms: duration_ms,
|
|
60
|
+
started_at: started_at,
|
|
61
|
+
feed: feed,
|
|
62
|
+
success: true,
|
|
63
|
+
body: body,
|
|
64
|
+
feed_signature: feed_signature,
|
|
65
|
+
items_created: item_processing.created,
|
|
66
|
+
items_updated: item_processing.updated,
|
|
67
|
+
items_failed: item_processing.failed,
|
|
68
|
+
item_errors: item_processing.errors
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def apply_instrumentation(instrumentation_payload)
|
|
73
|
+
instrumentation_payload[:success] = true
|
|
74
|
+
instrumentation_payload[:status] = :fetched
|
|
75
|
+
instrumentation_payload[:http_status] = response.status
|
|
76
|
+
instrumentation_payload[:parser] = feed.class.name if feed
|
|
77
|
+
instrumentation_payload[:items_created] = item_processing.created
|
|
78
|
+
instrumentation_payload[:items_updated] = item_processing.updated
|
|
79
|
+
instrumentation_payload[:items_failed] = item_processing.failed
|
|
80
|
+
instrumentation_payload[:retry_attempt] = 0
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -9,11 +9,13 @@ require "source_monitor/items/item_creator"
|
|
|
9
9
|
require "source_monitor/fetching/feed_fetcher/adaptive_interval"
|
|
10
10
|
require "source_monitor/fetching/feed_fetcher/source_updater"
|
|
11
11
|
require "source_monitor/fetching/feed_fetcher/entry_processor"
|
|
12
|
+
require "source_monitor/fetching/feed_fetcher/success_outcome"
|
|
13
|
+
require "source_monitor/fetching/feed_fetcher/failure_outcome"
|
|
12
14
|
|
|
13
15
|
module SourceMonitor
|
|
14
16
|
module Fetching
|
|
15
17
|
class FeedFetcher
|
|
16
|
-
Result = Struct.new(:status, :feed, :response, :body, :error, :item_processing, :retry_decision, keyword_init: true)
|
|
18
|
+
Result = Struct.new(:status, :feed, :response, :body, :error, :item_processing, :retry_decision, :outcome, keyword_init: true)
|
|
17
19
|
EntryProcessingResult = Struct.new(
|
|
18
20
|
:created,
|
|
19
21
|
:updated,
|
|
@@ -24,7 +26,20 @@ module SourceMonitor
|
|
|
24
26
|
:created_items,
|
|
25
27
|
:updated_items,
|
|
26
28
|
keyword_init: true
|
|
27
|
-
)
|
|
29
|
+
) do
|
|
30
|
+
def self.empty
|
|
31
|
+
new(
|
|
32
|
+
created: 0,
|
|
33
|
+
updated: 0,
|
|
34
|
+
unchanged: 0,
|
|
35
|
+
failed: 0,
|
|
36
|
+
items: [],
|
|
37
|
+
errors: [],
|
|
38
|
+
created_items: [],
|
|
39
|
+
updated_items: []
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
28
43
|
ResponseWrapper = Struct.new(:status, :headers, :body, keyword_init: true)
|
|
29
44
|
|
|
30
45
|
attr_reader :source, :client, :jitter_proc
|
|
@@ -116,7 +131,6 @@ module SourceMonitor
|
|
|
116
131
|
end
|
|
117
132
|
|
|
118
133
|
def handle_success(response, started_at, instrumentation_payload)
|
|
119
|
-
duration_ms = source_updater.elapsed_ms(started_at)
|
|
120
134
|
body = response.body
|
|
121
135
|
feed_body_signature = body_digest(body)
|
|
122
136
|
feed = parse_feed(body, response)
|
|
@@ -125,45 +139,19 @@ module SourceMonitor
|
|
|
125
139
|
processing = entry_processor.process_feed_entries(feed)
|
|
126
140
|
content_changed = entries_digest_changed?(feed)
|
|
127
141
|
else
|
|
128
|
-
processing = EntryProcessingResult.
|
|
129
|
-
created: 0,
|
|
130
|
-
updated: 0,
|
|
131
|
-
unchanged: 0,
|
|
132
|
-
failed: 0,
|
|
133
|
-
items: [],
|
|
134
|
-
errors: [],
|
|
135
|
-
created_items: [],
|
|
136
|
-
updated_items: []
|
|
137
|
-
)
|
|
142
|
+
processing = EntryProcessingResult.empty
|
|
138
143
|
content_changed = false
|
|
139
144
|
end
|
|
140
145
|
|
|
141
|
-
|
|
142
|
-
source_updater.update_source_for_success(response, duration_ms, feed, feed_body_signature, content_changed: content_changed, entries_digest: feed_entries_digest)
|
|
143
|
-
source_updater.create_fetch_log(
|
|
146
|
+
SuccessOutcome.new(
|
|
144
147
|
response: response,
|
|
145
|
-
duration_ms: duration_ms,
|
|
146
|
-
started_at: started_at,
|
|
147
|
-
feed: feed,
|
|
148
|
-
success: true,
|
|
149
148
|
body: body,
|
|
149
|
+
feed: feed,
|
|
150
|
+
item_processing: processing,
|
|
150
151
|
feed_signature: feed_body_signature,
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
item_errors: processing.errors
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
instrumentation_payload[:success] = true
|
|
158
|
-
instrumentation_payload[:status] = :fetched
|
|
159
|
-
instrumentation_payload[:http_status] = response.status
|
|
160
|
-
instrumentation_payload[:parser] = feed.class.name if feed
|
|
161
|
-
instrumentation_payload[:items_created] = processing.created
|
|
162
|
-
instrumentation_payload[:items_updated] = processing.updated
|
|
163
|
-
instrumentation_payload[:items_failed] = processing.failed
|
|
164
|
-
instrumentation_payload[:retry_attempt] = 0
|
|
165
|
-
|
|
166
|
-
Result.new(status: :fetched, feed:, response:, body:, item_processing: processing)
|
|
152
|
+
content_changed: content_changed,
|
|
153
|
+
entries_digest: entries_digest(feed)
|
|
154
|
+
).apply(source_updater: source_updater, started_at: started_at, instrumentation_payload: instrumentation_payload)
|
|
167
155
|
end
|
|
168
156
|
|
|
169
157
|
def handle_not_modified(response, started_at, instrumentation_payload)
|
|
@@ -189,16 +177,7 @@ module SourceMonitor
|
|
|
189
177
|
status: :not_modified,
|
|
190
178
|
response: response,
|
|
191
179
|
body: nil,
|
|
192
|
-
item_processing: EntryProcessingResult.
|
|
193
|
-
created: 0,
|
|
194
|
-
updated: 0,
|
|
195
|
-
unchanged: 0,
|
|
196
|
-
failed: 0,
|
|
197
|
-
items: [],
|
|
198
|
-
errors: [],
|
|
199
|
-
created_items: [],
|
|
200
|
-
updated_items: []
|
|
201
|
-
)
|
|
180
|
+
item_processing: EntryProcessingResult.empty
|
|
202
181
|
)
|
|
203
182
|
end
|
|
204
183
|
|
|
@@ -263,48 +242,8 @@ module SourceMonitor
|
|
|
263
242
|
end
|
|
264
243
|
|
|
265
244
|
def handle_failure(error, started_at:, instrumentation_payload:)
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
duration_ms = source_updater.elapsed_ms(started_at)
|
|
269
|
-
|
|
270
|
-
retry_decision = source_updater.update_source_for_failure(error, duration_ms)
|
|
271
|
-
source_updater.create_fetch_log(
|
|
272
|
-
response: response,
|
|
273
|
-
duration_ms: duration_ms,
|
|
274
|
-
started_at: started_at,
|
|
275
|
-
success: false,
|
|
276
|
-
error: error,
|
|
277
|
-
body: body
|
|
278
|
-
)
|
|
279
|
-
|
|
280
|
-
instrumentation_payload[:success] = false
|
|
281
|
-
instrumentation_payload[:status] = :failed
|
|
282
|
-
instrumentation_payload[:error_class] = error.class.name
|
|
283
|
-
instrumentation_payload[:error_message] = error.message
|
|
284
|
-
instrumentation_payload[:http_status] = error.http_status if error.http_status
|
|
285
|
-
instrumentation_payload[:error_code] = error.code if error.respond_to?(:code)
|
|
286
|
-
instrumentation_payload[:items_created] = 0
|
|
287
|
-
instrumentation_payload[:items_updated] = 0
|
|
288
|
-
instrumentation_payload[:items_failed] = 0
|
|
289
|
-
instrumentation_payload[:retry_attempt] = retry_decision&.next_attempt ? retry_decision.next_attempt : 0
|
|
290
|
-
|
|
291
|
-
Result.new(
|
|
292
|
-
status: :failed,
|
|
293
|
-
response: response,
|
|
294
|
-
body: body,
|
|
295
|
-
error: error,
|
|
296
|
-
retry_decision: retry_decision,
|
|
297
|
-
item_processing: EntryProcessingResult.new(
|
|
298
|
-
created: 0,
|
|
299
|
-
updated: 0,
|
|
300
|
-
unchanged: 0,
|
|
301
|
-
failed: 0,
|
|
302
|
-
items: [],
|
|
303
|
-
errors: [],
|
|
304
|
-
created_items: [],
|
|
305
|
-
updated_items: []
|
|
306
|
-
)
|
|
307
|
-
)
|
|
245
|
+
FailureOutcome.new(error: error)
|
|
246
|
+
.apply(source_updater: source_updater, started_at: started_at, instrumentation_payload: instrumentation_payload)
|
|
308
247
|
end
|
|
309
248
|
|
|
310
249
|
def attempt_aia_recovery(_error, started_at, instrumentation_payload)
|
|
@@ -78,16 +78,17 @@ module SourceMonitor
|
|
|
78
78
|
end
|
|
79
79
|
|
|
80
80
|
# Phase 3: Post-fetch DB writes under the advisory lock (still held).
|
|
81
|
+
completion_result = completion_result_for(result)
|
|
81
82
|
begin
|
|
82
|
-
log_handler_result("RetentionHandler", retention_handler.call(source:, result:))
|
|
83
|
-
log_handler_result("FollowUpHandler", follow_up_handler.call(source:, result:))
|
|
84
|
-
schedule_retry_if_needed(
|
|
85
|
-
mark_complete!(
|
|
83
|
+
log_handler_result("RetentionHandler", retention_handler.call(source:, result: completion_result))
|
|
84
|
+
log_handler_result("FollowUpHandler", follow_up_handler.call(source:, result: completion_result))
|
|
85
|
+
schedule_retry_if_needed(completion_result)
|
|
86
|
+
mark_complete!(completion_result)
|
|
86
87
|
ensure
|
|
87
88
|
lock.release!
|
|
88
89
|
end
|
|
89
90
|
|
|
90
|
-
log_handler_result("EventPublisher", event_publisher.call(source:, result:))
|
|
91
|
+
log_handler_result("EventPublisher", event_publisher.call(source:, result: result))
|
|
91
92
|
result
|
|
92
93
|
rescue SourceMonitor::Fetching::AdvisoryLock::NotAcquiredError => error
|
|
93
94
|
raise ConcurrencyError, error.message
|
|
@@ -156,6 +157,12 @@ module SourceMonitor
|
|
|
156
157
|
update_source_state(fetch_status: "failed")
|
|
157
158
|
end
|
|
158
159
|
|
|
160
|
+
def completion_result_for(result)
|
|
161
|
+
return result unless result.respond_to?(:outcome)
|
|
162
|
+
|
|
163
|
+
result.outcome || result
|
|
164
|
+
end
|
|
165
|
+
|
|
159
166
|
def update_source_state(attrs)
|
|
160
167
|
self.class.send(:update_source_state!, source, attrs)
|
|
161
168
|
end
|