source_monitor 0.12.2 → 0.12.4
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-job/reference/job-conventions.md +3 -2
- data/.claude/skills/sm-upgrade/reference/version-history.md +22 -0
- data/CHANGELOG.md +18 -0
- data/Gemfile.lock +1 -1
- data/README.md +3 -3
- data/VERSION +1 -1
- data/app/assets/builds/source_monitor/application.css +15 -0
- data/app/assets/builds/source_monitor/application.js +74 -4
- data/app/assets/builds/source_monitor/application.js.map +2 -2
- data/app/assets/javascripts/source_monitor/controllers/modal_controller.js +4 -0
- data/app/assets/javascripts/source_monitor/controllers/select_all_controller.js +81 -4
- data/app/components/source_monitor/icon_component.rb +2 -7
- data/app/controllers/source_monitor/bulk_scrape_enablements_controller.rb +8 -5
- data/app/controllers/source_monitor/dashboard_controller.rb +1 -1
- data/app/controllers/source_monitor/import_history_dismissals_controller.rb +5 -2
- data/app/controllers/source_monitor/sources_controller.rb +1 -0
- data/app/jobs/source_monitor/fetch_feed_job.rb +24 -3
- data/app/views/source_monitor/logs/index.html.erb +1 -1
- data/app/views/source_monitor/sources/_bulk_scrape_enable_modal.html.erb +24 -26
- data/app/views/source_monitor/sources/index.html.erb +21 -2
- data/docs/setup.md +2 -2
- data/docs/upgrade.md +32 -0
- data/lib/source_monitor/scraping/state.rb +2 -2
- data/lib/source_monitor/version.rb +1 -1
- metadata +1 -1
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { Controller } from "@hotwired/stimulus";
|
|
2
2
|
|
|
3
3
|
export default class extends Controller {
|
|
4
|
-
static targets = [
|
|
4
|
+
static targets = [
|
|
5
|
+
"master",
|
|
6
|
+
"item",
|
|
7
|
+
"actionBar",
|
|
8
|
+
"count",
|
|
9
|
+
"crossPageBanner",
|
|
10
|
+
"selectAllPagesInput",
|
|
11
|
+
];
|
|
12
|
+
static values = { totalCandidates: Number };
|
|
5
13
|
|
|
6
14
|
connect() {
|
|
7
15
|
this.syncMaster();
|
|
@@ -24,14 +32,54 @@ export default class extends Controller {
|
|
|
24
32
|
if (checkbox.disabled) return;
|
|
25
33
|
checkbox.checked = checked;
|
|
26
34
|
});
|
|
35
|
+
if (!checked) {
|
|
36
|
+
this.deselectAllPages();
|
|
37
|
+
}
|
|
27
38
|
this.updateActionBar();
|
|
28
39
|
}
|
|
29
40
|
|
|
30
41
|
toggleItem() {
|
|
42
|
+
this.deselectAllPages();
|
|
31
43
|
this.syncMaster();
|
|
32
44
|
this.updateActionBar();
|
|
33
45
|
}
|
|
34
46
|
|
|
47
|
+
selectAllPages() {
|
|
48
|
+
if (this.hasSelectAllPagesInputTarget) {
|
|
49
|
+
this.selectAllPagesInputTarget.disabled = false;
|
|
50
|
+
}
|
|
51
|
+
if (this.hasCrossPageBannerTarget) {
|
|
52
|
+
this.crossPageBannerTarget.dataset.selected = "true";
|
|
53
|
+
const deselect = this.crossPageBannerTarget.querySelector(
|
|
54
|
+
"[data-role='deselect']"
|
|
55
|
+
);
|
|
56
|
+
const select = this.crossPageBannerTarget.querySelector(
|
|
57
|
+
"[data-role='select']"
|
|
58
|
+
);
|
|
59
|
+
if (deselect) deselect.classList.remove("hidden");
|
|
60
|
+
if (select) select.classList.add("hidden");
|
|
61
|
+
}
|
|
62
|
+
this.updateCount();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
deselectAllPages() {
|
|
66
|
+
if (this.hasSelectAllPagesInputTarget) {
|
|
67
|
+
this.selectAllPagesInputTarget.disabled = true;
|
|
68
|
+
}
|
|
69
|
+
if (this.hasCrossPageBannerTarget) {
|
|
70
|
+
this.crossPageBannerTarget.dataset.selected = "false";
|
|
71
|
+
const deselect = this.crossPageBannerTarget.querySelector(
|
|
72
|
+
"[data-role='deselect']"
|
|
73
|
+
);
|
|
74
|
+
const select = this.crossPageBannerTarget.querySelector(
|
|
75
|
+
"[data-role='select']"
|
|
76
|
+
);
|
|
77
|
+
if (deselect) deselect.classList.add("hidden");
|
|
78
|
+
if (select) select.classList.remove("hidden");
|
|
79
|
+
}
|
|
80
|
+
this.updateCount();
|
|
81
|
+
}
|
|
82
|
+
|
|
35
83
|
syncMaster() {
|
|
36
84
|
if (!this.hasMasterTarget) return;
|
|
37
85
|
const selectable = this.itemTargets.filter((checkbox) => !checkbox.disabled);
|
|
@@ -44,13 +92,42 @@ export default class extends Controller {
|
|
|
44
92
|
updateActionBar() {
|
|
45
93
|
if (!this.hasActionBarTarget) return;
|
|
46
94
|
const checkedCount = this.itemTargets.filter((cb) => cb.checked).length;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
95
|
+
this.updateCount();
|
|
96
|
+
this.updateCrossPageBanner();
|
|
50
97
|
if (checkedCount > 0) {
|
|
51
98
|
this.actionBarTarget.classList.remove("hidden");
|
|
52
99
|
} else {
|
|
53
100
|
this.actionBarTarget.classList.add("hidden");
|
|
54
101
|
}
|
|
55
102
|
}
|
|
103
|
+
|
|
104
|
+
updateCount() {
|
|
105
|
+
if (!this.hasCountTarget) return;
|
|
106
|
+
const isAllPages =
|
|
107
|
+
this.hasCrossPageBannerTarget &&
|
|
108
|
+
this.crossPageBannerTarget.dataset.selected === "true";
|
|
109
|
+
if (isAllPages && this.hasTotalCandidatesValue) {
|
|
110
|
+
this.countTarget.textContent = this.totalCandidatesValue;
|
|
111
|
+
} else {
|
|
112
|
+
const checkedCount = this.itemTargets.filter((cb) => cb.checked).length;
|
|
113
|
+
this.countTarget.textContent = checkedCount;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
updateCrossPageBanner() {
|
|
118
|
+
if (!this.hasCrossPageBannerTarget) return;
|
|
119
|
+
const selectable = this.itemTargets.filter((cb) => !cb.disabled);
|
|
120
|
+
const allChecked =
|
|
121
|
+
selectable.length > 0 &&
|
|
122
|
+
selectable.every((cb) => cb.checked);
|
|
123
|
+
const hasMorePages =
|
|
124
|
+
this.hasTotalCandidatesValue && this.totalCandidatesValue > selectable.length;
|
|
125
|
+
|
|
126
|
+
if (allChecked && hasMorePages) {
|
|
127
|
+
this.crossPageBannerTarget.classList.remove("hidden");
|
|
128
|
+
} else {
|
|
129
|
+
this.crossPageBannerTarget.classList.add("hidden");
|
|
130
|
+
this.deselectAllPages();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
56
133
|
}
|
|
@@ -7,14 +7,9 @@ module SourceMonitor
|
|
|
7
7
|
ICONS = {
|
|
8
8
|
menu_dots: {
|
|
9
9
|
view_box: "0 0 20 20",
|
|
10
|
-
fill: "
|
|
11
|
-
stroke: "currentColor",
|
|
12
|
-
stroke_width: "1.5",
|
|
10
|
+
fill: "currentColor",
|
|
13
11
|
paths: [
|
|
14
|
-
{ d: "M10
|
|
15
|
-
stroke_linecap: "round", stroke_linejoin: "round" },
|
|
16
|
-
{ d: "M12 10a2 2 0 1 1-4 0 2 2 0 0 1 4 0Z",
|
|
17
|
-
stroke_linecap: "round", stroke_linejoin: "round" }
|
|
12
|
+
{ d: "M10 3a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM10 8.5a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM11.5 15.5a1.5 1.5 0 1 0-3 0 1.5 1.5 0 0 0 3 0Z" }
|
|
18
13
|
]
|
|
19
14
|
},
|
|
20
15
|
refresh: {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module SourceMonitor
|
|
4
4
|
class BulkScrapeEnablementsController < ApplicationController
|
|
5
5
|
def create
|
|
6
|
-
source_ids =
|
|
6
|
+
source_ids = resolve_source_ids
|
|
7
7
|
|
|
8
8
|
if source_ids.empty?
|
|
9
9
|
handle_empty_selection
|
|
@@ -31,10 +31,13 @@ module SourceMonitor
|
|
|
31
31
|
|
|
32
32
|
private
|
|
33
33
|
|
|
34
|
-
def
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
def resolve_source_ids
|
|
35
|
+
if params.dig(:bulk_scrape_enablement, :select_all_pages) == "true"
|
|
36
|
+
Source.scrape_candidates.pluck(:id)
|
|
37
|
+
else
|
|
38
|
+
raw_ids = Array(params.dig(:bulk_scrape_enablement, :source_ids))
|
|
39
|
+
raw_ids.map(&:to_i).reject(&:zero?)
|
|
40
|
+
end
|
|
38
41
|
end
|
|
39
42
|
|
|
40
43
|
def handle_empty_selection
|
|
@@ -33,7 +33,7 @@ module SourceMonitor
|
|
|
33
33
|
raw = params.fetch(:schedule_pages, {})
|
|
34
34
|
return {} unless raw.respond_to?(:permit)
|
|
35
35
|
|
|
36
|
-
permitted_keys = raw.keys.select { |k| k.to_s.match?(/\
|
|
36
|
+
permitted_keys = raw.keys.select { |k| k.to_s.match?(/\A[\d+\-]+\z/) }
|
|
37
37
|
raw.permit(*permitted_keys).to_h
|
|
38
38
|
end
|
|
39
39
|
end
|
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
module SourceMonitor
|
|
4
4
|
class ImportHistoryDismissalsController < ApplicationController
|
|
5
5
|
def create
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
user_id = source_monitor_current_user&.id
|
|
7
|
+
# Verify the specified import history belongs to this user (authorization check)
|
|
8
|
+
ImportHistory.where(user_id: user_id).find(params[:import_history_id])
|
|
9
|
+
# Dismiss all undismissed import histories for this user so older ones don't resurface
|
|
10
|
+
ImportHistory.where(user_id: user_id).not_dismissed.update_all(dismissed_at: Time.current)
|
|
8
11
|
|
|
9
12
|
respond_to do |format|
|
|
10
13
|
format.turbo_stream do
|
|
@@ -53,6 +53,7 @@ module SourceMonitor
|
|
|
53
53
|
@avg_scraped_word_counts = word_counts[:scraped]
|
|
54
54
|
|
|
55
55
|
@scrape_candidate_ids = compute_scrape_candidate_ids
|
|
56
|
+
@total_scrape_candidate_count = Source.scrape_candidates.count
|
|
56
57
|
|
|
57
58
|
# Row partial preload requirements (V3): item_activity_rates,
|
|
58
59
|
# avg_feed_word_counts, avg_scraped_word_counts are pre-computed above
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module SourceMonitor
|
|
4
4
|
class FetchFeedJob < ApplicationJob
|
|
5
|
-
|
|
5
|
+
FETCH_CONCURRENCY_BASE_WAIT = 30.seconds
|
|
6
|
+
FETCH_CONCURRENCY_MAX_WAIT = 5.minutes
|
|
6
7
|
EARLY_EXECUTION_LEEWAY = 30.seconds
|
|
7
8
|
|
|
8
9
|
source_monitor_queue :fetch
|
|
@@ -39,13 +40,33 @@ module SourceMonitor
|
|
|
39
40
|
else
|
|
40
41
|
attempt = executions
|
|
41
42
|
if attempt < SCHEDULED_CONCURRENCY_MAX_ATTEMPTS
|
|
42
|
-
retry_job wait:
|
|
43
|
+
retry_job wait: concurrency_backoff_wait(attempt)
|
|
43
44
|
else
|
|
44
|
-
|
|
45
|
+
log_concurrency_exhausted
|
|
46
|
+
@source&.update_columns(fetch_status: "idle") if @source&.fetch_status == "queued"
|
|
45
47
|
end
|
|
46
48
|
end
|
|
47
49
|
end
|
|
48
50
|
|
|
51
|
+
def concurrency_backoff_wait(attempt)
|
|
52
|
+
base = FETCH_CONCURRENCY_BASE_WAIT.to_i
|
|
53
|
+
exponential = base * (2**attempt)
|
|
54
|
+
capped = [ exponential, FETCH_CONCURRENCY_MAX_WAIT.to_i ].min
|
|
55
|
+
jitter = rand(0..(capped * 0.25).to_i)
|
|
56
|
+
(capped + jitter).seconds
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def log_concurrency_exhausted
|
|
60
|
+
return unless defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
|
61
|
+
|
|
62
|
+
Rails.logger.info(
|
|
63
|
+
"[SourceMonitor::FetchFeedJob] Concurrency retries exhausted for source #{@source_id}, " \
|
|
64
|
+
"discarding (another worker is fetching this source)"
|
|
65
|
+
)
|
|
66
|
+
rescue StandardError
|
|
67
|
+
nil
|
|
68
|
+
end
|
|
69
|
+
|
|
49
70
|
def log_force_fetch_skipped
|
|
50
71
|
return unless defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
|
51
72
|
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
</div>
|
|
53
53
|
</div>
|
|
54
54
|
|
|
55
|
-
<%= form_with url: source_monitor.logs_path, method: :get, html: { class: "rounded-lg border border-slate-200 bg-white p-4 shadow-sm", data: { turbo_frame: "source_monitor_logs" } } do |form| %>
|
|
55
|
+
<%= form_with url: source_monitor.logs_path, method: :get, html: { class: "rounded-lg border border-slate-200 bg-white p-4 shadow-sm", data: { turbo_frame: "source_monitor_logs", controller: "filter-submit" } } do |form| %>
|
|
56
56
|
<%= form.hidden_field :status, value: @filter_set.status %>
|
|
57
57
|
<%= form.hidden_field :log_type, value: @filter_set.log_type %>
|
|
58
58
|
|
|
@@ -1,29 +1,27 @@
|
|
|
1
|
-
<div data-
|
|
2
|
-
<div
|
|
3
|
-
<div class="
|
|
4
|
-
<
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
</button>
|
|
26
|
-
</div>
|
|
1
|
+
<div data-modal-target="panel" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/50" data-action="click->modal#backdrop" role="dialog" aria-modal="true" aria-labelledby="bulk-scrape-enable-modal-heading">
|
|
2
|
+
<div class="w-full max-w-md rounded-lg bg-white shadow-xl" data-action="click->modal#stop">
|
|
3
|
+
<div class="border-b border-slate-200 px-6 py-4">
|
|
4
|
+
<h3 id="bulk-scrape-enable-modal-heading" class="text-lg font-semibold text-slate-900">Enable Scraping</h3>
|
|
5
|
+
</div>
|
|
6
|
+
<div class="px-6 py-4">
|
|
7
|
+
<p class="text-sm text-slate-700">
|
|
8
|
+
This will enable scraping for the selected sources using the default scraper adapter.
|
|
9
|
+
Each source's items will be scraped on their next scheduled run.
|
|
10
|
+
</p>
|
|
11
|
+
<p class="mt-3 text-sm font-medium text-amber-700">
|
|
12
|
+
This action will modify the selected sources' configuration.
|
|
13
|
+
</p>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="flex justify-end gap-3 border-t border-slate-200 px-6 py-4">
|
|
16
|
+
<button type="button"
|
|
17
|
+
data-action="modal#close"
|
|
18
|
+
class="rounded-md border border-slate-200 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">
|
|
19
|
+
Cancel
|
|
20
|
+
</button>
|
|
21
|
+
<button type="submit"
|
|
22
|
+
class="rounded-md bg-violet-600 px-4 py-2 text-sm font-semibold text-white shadow hover:bg-violet-500">
|
|
23
|
+
Confirm Enable
|
|
24
|
+
</button>
|
|
27
25
|
</div>
|
|
28
26
|
</div>
|
|
29
27
|
</div>
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
selected_bucket: @selected_fetch_interval_bucket,
|
|
20
20
|
search_params: @search_params %>
|
|
21
21
|
|
|
22
|
-
<%= search_form_for @q, url: source_monitor.sources_path, method: :get, html: { class: "flex flex-wrap items-end gap-3", data: { turbo_frame: "source_monitor_sources_table" } } do |form| %>
|
|
22
|
+
<%= search_form_for @q, url: source_monitor.sources_path, method: :get, html: { class: "flex flex-wrap items-end gap-3", data: { turbo_frame: "source_monitor_sources_table", controller: "filter-submit" } } do |form| %>
|
|
23
23
|
<div class="flex-1 min-w-[12rem]">
|
|
24
24
|
<%= form.label @search_field, "Search sources", class: "sr-only" %>
|
|
25
25
|
<div class="flex rounded-md shadow-sm">
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
<% end %>
|
|
94
94
|
</div>
|
|
95
95
|
<% end %>
|
|
96
|
-
<%= form_with url: source_monitor.bulk_scrape_enablements_path, data: { controller: "select-all" } do |form| %>
|
|
96
|
+
<%= form_with url: source_monitor.bulk_scrape_enablements_path, data: { controller: "select-all modal", "select-all-total-candidates-value": @total_scrape_candidate_count } do |form| %>
|
|
97
97
|
<table class="min-w-full divide-y divide-slate-200 text-left text-sm">
|
|
98
98
|
<thead class="bg-slate-50 text-xs font-semibold uppercase tracking-wide text-slate-500">
|
|
99
99
|
<tr>
|
|
@@ -241,6 +241,25 @@
|
|
|
241
241
|
</tbody>
|
|
242
242
|
</table>
|
|
243
243
|
|
|
244
|
+
<input type="hidden" name="bulk_scrape_enablement[select_all_pages]" value="true" disabled data-select-all-target="selectAllPagesInput">
|
|
245
|
+
|
|
246
|
+
<% if @total_scrape_candidate_count > @scrape_candidate_ids.size %>
|
|
247
|
+
<div data-select-all-target="crossPageBanner" data-selected="false" class="hidden border-t border-violet-100 bg-violet-50 px-4 py-2 text-center text-sm text-violet-700">
|
|
248
|
+
<span data-role="select">
|
|
249
|
+
All <%= @scrape_candidate_ids.size %> candidates on this page are selected.
|
|
250
|
+
<button type="button" data-action="select-all#selectAllPages" class="font-semibold text-violet-900 underline hover:text-violet-600">
|
|
251
|
+
Select all <%= @total_scrape_candidate_count %> candidates across all pages
|
|
252
|
+
</button>
|
|
253
|
+
</span>
|
|
254
|
+
<span data-role="deselect" class="hidden">
|
|
255
|
+
All <%= @total_scrape_candidate_count %> candidates across all pages are selected.
|
|
256
|
+
<button type="button" data-action="select-all#deselectAllPages" class="font-semibold text-violet-900 underline hover:text-violet-600">
|
|
257
|
+
Clear selection
|
|
258
|
+
</button>
|
|
259
|
+
</span>
|
|
260
|
+
</div>
|
|
261
|
+
<% end %>
|
|
262
|
+
|
|
244
263
|
<div data-select-all-target="actionBar" class="hidden sticky bottom-0 border-t border-slate-200 bg-white px-4 py-3 shadow-md">
|
|
245
264
|
<div class="flex items-center justify-between">
|
|
246
265
|
<span class="text-sm text-slate-700">
|
data/docs/setup.md
CHANGED
|
@@ -18,8 +18,8 @@ This guide consolidates the new guided installer, verification commands, and rol
|
|
|
18
18
|
Run these commands inside your host Rails application before invoking the guided workflow:
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
-
bundle add source_monitor --version "~> 0.12.
|
|
22
|
-
# or add gem "source_monitor", "~> 0.12.
|
|
21
|
+
bundle add source_monitor --version "~> 0.12.4"
|
|
22
|
+
# or add gem "source_monitor", "~> 0.12.4" to Gemfile manually
|
|
23
23
|
bundle install
|
|
24
24
|
```
|
|
25
25
|
|
data/docs/upgrade.md
CHANGED
|
@@ -69,6 +69,38 @@ bin/rails db:migrate
|
|
|
69
69
|
- New ViewComponents and presenters are available for custom view integration but are not required by default templates.
|
|
70
70
|
- `Item#restore!` is the symmetric counterpart to `soft_delete!` — it clears `deleted_at` and increments the source `items_count` counter cache.
|
|
71
71
|
|
|
72
|
+
### Upgrading to 0.12.4
|
|
73
|
+
|
|
74
|
+
**What changed:**
|
|
75
|
+
- Bug fix: ScrapeItemJob Rails 8.1.2 with_lock compatibility (assign_attributes → reload)
|
|
76
|
+
- Bug fix: FetchFeedJob exponential backoff for advisory lock contention
|
|
77
|
+
- Bug fix: OPML import dismissal now dismisses all undismissed histories
|
|
78
|
+
- Bug fix: Dashboard pagination regex for schedule group keys
|
|
79
|
+
- Bug fix: Source/logs filter dropdowns auto-submit via Stimulus controller
|
|
80
|
+
|
|
81
|
+
**Upgrade steps:**
|
|
82
|
+
```bash
|
|
83
|
+
bundle update source_monitor
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Notes:**
|
|
87
|
+
- No breaking changes, migrations, or configuration changes required.
|
|
88
|
+
- Patch fix release.
|
|
89
|
+
|
|
90
|
+
### Upgrading to 0.12.3
|
|
91
|
+
|
|
92
|
+
**What changed:**
|
|
93
|
+
- UI fixes: menu icon (gear -> ellipsis), modal controller scope, cross-page select-all for bulk scraping recommendations
|
|
94
|
+
|
|
95
|
+
**Upgrade steps:**
|
|
96
|
+
```bash
|
|
97
|
+
bundle update source_monitor
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Notes:**
|
|
101
|
+
- No breaking changes, migrations, or configuration changes required.
|
|
102
|
+
- Patch fix release.
|
|
103
|
+
|
|
72
104
|
### Upgrading to 0.12.2
|
|
73
105
|
|
|
74
106
|
**What changed:**
|
|
@@ -32,7 +32,7 @@ module SourceMonitor
|
|
|
32
32
|
next unless in_flight?(record.scrape_status)
|
|
33
33
|
|
|
34
34
|
record.update_columns(scrape_status: nil)
|
|
35
|
-
record.
|
|
35
|
+
record.reload
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
broadcast_item(item) if broadcast
|
|
@@ -48,7 +48,7 @@ module SourceMonitor
|
|
|
48
48
|
with_item(item, lock:) do |record|
|
|
49
49
|
attributes = { scrape_status: status }.merge(extra.compact)
|
|
50
50
|
record.update_columns(attributes)
|
|
51
|
-
record.
|
|
51
|
+
record.reload
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
broadcast_item(item) if broadcast
|