|
87 | 87 | (.getMessage e))))) |
88 | 88 |
|
89 | 89 | (defn- failures-for-subscriber |
90 | | - "Return failures on `source` routed to `email-addr`, posted after |
91 | | - `since-date` (or all of them if since-date is nil). |
| 90 | + "Return failures on `:source` routed to `:email`, posted after `:since` |
| 91 | + (or all of them if `:since` is nil). |
92 | 92 |
|
93 | 93 | Routing is driven by the `:audience` field on each failure entry: |
94 | 94 | - `:author` -- shown only to the address that triggered the |
95 | 95 | failure (someone seeing their own typo); default |
96 | 96 | for legacy entries that predate the field. |
97 | 97 | - `:maintainers` -- shown to every subscriber on the source." |
98 | | - [all-failures email-addr source since-date] |
99 | | - (let [addr (str/lower-case email-addr)] |
| 98 | + [all-failures {:keys [email source since]}] |
| 99 | + (let [addr (str/lower-case email)] |
100 | 100 | (->> all-failures |
101 | 101 | (filter (fn [{:keys [from audience date] src :source}] |
102 | 102 | (and (= source src) |
103 | | - (or (nil? since-date) |
104 | | - (and date (.after ^java.util.Date date since-date))) |
| 103 | + (or (nil? since) |
| 104 | + (and date (.after ^java.util.Date date since))) |
105 | 105 | (case (or audience :author) |
106 | 106 | :author (= addr from) |
107 | 107 | :maintainers true |
|
183 | 183 | (defn- filter-relevant-reports |
184 | 184 | "Filter the full report set against scope filters: actionable type, |
185 | 185 | open, on-source, plus optional :subject-match / :topic substrings." |
186 | | - [reports {:keys [source subj-match topic]}] |
187 | | - (let [subj-lc (some-> subj-match str/lower-case) |
188 | | - topic-lc (some-> topic str/lower-case)] |
189 | | - (cond->> reports |
190 | | - true (filter #(contains? actionable-types (:report/type %))) |
191 | | - true (filter open?) |
192 | | - true (filter #(= source (get-in % [:report/email :email/source]))) |
193 | | - subj-lc (filter #(some-> (get-in % [:report/email :email/subject]) |
| 186 | + [reports {:keys [source subject-match topic]}] |
| 187 | + (let [subject-lc (some-> subject-match str/lower-case) |
| 188 | + topic-lc (some-> topic str/lower-case)] |
| 189 | + (cond->> (->> reports |
| 190 | + (filter #(contains? actionable-types (:report/type %))) |
| 191 | + (filter open?) |
| 192 | + (filter #(= source (get-in % [:report/email :email/source])))) |
| 193 | + subject-lc (filter #(some-> (get-in % [:report/email :email/subject]) |
194 | 194 | str/lower-case |
195 | | - (str/includes? subj-lc))) |
| 195 | + (str/includes? subject-lc))) |
196 | 196 | topic-lc (filter #(some-> (:report/topic-value %) |
197 | 197 | str/lower-case |
198 | 198 | (str/includes? topic-lc)))))) |
|
202 | 202 | and 2 (owned by you, with or without deadline) always show what you |
203 | 203 | own. Section 3 (unacked & unowned) is the noisy one and is the |
204 | 204 | only one filtered by min-priority and min-status." |
205 | | - [relevant email {:keys [min-pri min-sts]}] |
| 205 | + [relevant email {:keys [min-priority min-status]}] |
206 | 206 | (let [owned (filter #(owned-by? % email) relevant)] |
207 | 207 | {:dl (->> owned |
208 | 208 | (filter :report/deadline-value) |
|
213 | 213 | :unacked (->> relevant |
214 | 214 | (filter unacked?) |
215 | 215 | (filter unowned?) |
216 | | - (filter #(>= (report-priority %) min-pri)) |
217 | | - (filter #(>= (report-status %) min-sts)))})) |
| 216 | + (filter #(>= (report-priority %) min-priority)) |
| 217 | + (filter #(>= (report-status %) min-status)))})) |
218 | 218 |
|
219 | 219 | (defn- failure-subjects-map |
220 | 220 | "Build {message-id -> subject} for the message-ids referenced by `failures`." |
|
250 | 250 | "Build the notification email body for one (email, subscription) pair. |
251 | 251 | `failures` is a seq of cmd-failure entities to include." |
252 | 252 | [db reports email subscription failures] |
253 | | - (let [source (:source subscription) |
254 | | - prefs {:source source |
255 | | - :min-pri (:min-priority subscription 1) |
256 | | - :min-sts (:min-status subscription 0) |
257 | | - :subj-match (:subject-match subscription) |
258 | | - :topic (:topic subscription)} |
259 | | - relevant (->> (filter-relevant-reports reports prefs) |
260 | | - (sort-by (juxt report-priority report-descendant-count) |
261 | | - #(compare %2 %1))) |
262 | | - {:keys [dl owned unacked]} (build-sections relevant email prefs) |
263 | | - sec-fail (failures-section db source failures) |
264 | | - sec-dl (section |
265 | | - (str "== Upcoming deadlines -- owned by you (" source ") ==") |
266 | | - dl) |
267 | | - sec-owned (section |
268 | | - (str "== Open bugs/patches/requests owned by you (" source ") ==") |
269 | | - owned) |
270 | | - sec-unack (section |
271 | | - (str "== Unacked & unowned bugs/patches/requests (" source ") ==") |
272 | | - unacked)] |
| 253 | + (let [source (:source subscription) |
| 254 | + prefs (merge {:min-priority 1 :min-status 0} subscription) |
| 255 | + relevant (->> (filter-relevant-reports reports prefs) |
| 256 | + (sort-by (juxt report-priority report-descendant-count) |
| 257 | + #(compare %2 %1))) |
| 258 | + {:keys [dl owned unacked]} (build-sections relevant email prefs)] |
273 | 259 | (log/debug "build-email-body for" email (str "(source: " source ")")) |
274 | 260 | (log/debug " total reports:" (count reports) "-- relevant:" (count relevant)) |
275 | | - (join-sections [sec-fail sec-dl sec-owned sec-unack]))) |
| 261 | + (join-sections |
| 262 | + [(failures-section db source failures) |
| 263 | + (section (str "== Upcoming deadlines -- owned by you (" source ") ==") dl) |
| 264 | + (section (str "== Open bugs/patches/requests owned by you (" source ") ==") owned) |
| 265 | + (section (str "== Unacked & unowned bugs/patches/requests (" source ") ==") unacked)]))) |
276 | 266 |
|
277 | 267 | ;; --------------------------------------------------------------------------- |
278 | 268 | ;; Per-source kill switch |
|
306 | 296 | ;; Main |
307 | 297 | ;; --------------------------------------------------------------------------- |
308 | 298 |
|
309 | | -(defn- expand-subscribers |
310 | | - "Expand the :subscribers map into a flat seq of [email subscription] pairs." |
311 | | - [subscribers] |
312 | | - (mapcat (fn [[email subs]] |
313 | | - (map (fn [s] [email s]) subs)) |
314 | | - subscribers)) |
| 299 | +(defn- log-skipped-pairs! |
| 300 | + "Debug-log subscribers skipped because their source has |
| 301 | + :notifications {:enabled false}." |
| 302 | + [src-map pairs] |
| 303 | + (doseq [[email s] pairs |
| 304 | + :when (not (source-notify-enabled? src-map (:source s)))] |
| 305 | + (log/debug "SKIPPED" email |
| 306 | + "-- notifications disabled for source" (:source s)))) |
315 | 307 |
|
316 | 308 | ;; Guard ensures this block only runs when the script is invoked directly, |
317 | 309 | ;; not when loaded via load-file (e.g. from tests or other scripts). |
318 | 310 | (when (= (System/getProperty "babashka.file") *file*) |
319 | | - (let [flags (set *command-line-args*) |
320 | | - dry-run? (flags "--dry-run") |
321 | | - debug? (flags "--debug") |
322 | | - _ (when debug? (log/merge-config! {:min-level :debug})) |
323 | | - config (load-config) |
324 | | - dbp (db-path config) |
325 | | - notif (:notifications config)] |
326 | | - (when-not (and notif (:enabled notif)) |
327 | | - (log/info "Notifications disabled in config.") |
328 | | - (System/exit 0)) |
329 | | - (let [smtp (or (:smtp notif) |
330 | | - (do (log/error "No :smtp config under :notifications.") |
331 | | - (System/exit 1))) |
332 | | - subscribers (:subscribers notif) |
333 | | - conn (when (seq subscribers) |
334 | | - (d/get-conn dbp bark-schema {:wal? false}))] |
335 | | - (when (empty? subscribers) |
336 | | - (log/info "No :subscribers configured.") |
337 | | - (System/exit 0)) |
338 | | - (try |
339 | | - (let [db (d/db conn) |
340 | | - src-map (build-source-map config) |
341 | | - reports (all-reports db) |
342 | | - all-failures (load-failures) |
343 | | - last-shown (load-last-failures-shown) |
344 | | - pairs (expand-subscribers subscribers) |
345 | | - _ (log/debug (count pairs) "subscription(s) configured") |
346 | | - live-pairs (filter (fn [[_ s]] (source-notify-enabled? src-map (:source s))) pairs) |
347 | | - _ (do (when (< (count live-pairs) (count pairs)) |
348 | | - (doseq [[email s] pairs |
349 | | - :when (not (source-notify-enabled? src-map (:source s)))] |
350 | | - (log/debug "SKIPPED" email |
351 | | - "-- notifications disabled for source" |
352 | | - (:source s)))) |
353 | | - (log/debug (count live-pairs) "after per-source filter")) |
354 | | - sent (atom 0) |
355 | | - updated-shown (atom last-shown)] |
356 | | - (try |
| 311 | + (let [flags (set *command-line-args*) |
| 312 | + dry-run? (flags "--dry-run") |
| 313 | + debug? (flags "--debug") |
| 314 | + _ (when debug? (log/merge-config! {:min-level :debug})) |
| 315 | + config (load-config) |
| 316 | + dbp (db-path config) |
| 317 | + notif (:notifications config) |
| 318 | + smtp (:smtp notif) |
| 319 | + subscribers (:subscribers notif)] |
| 320 | + (cond |
| 321 | + (not (and notif (:enabled notif))) |
| 322 | + (do (log/info "Notifications disabled in config.") |
| 323 | + (System/exit 0)) |
| 324 | + |
| 325 | + (nil? smtp) |
| 326 | + (do (log/error "No :smtp config under :notifications.") |
| 327 | + (System/exit 1)) |
| 328 | + |
| 329 | + (empty? subscribers) |
| 330 | + (do (log/info "No :subscribers configured.") |
| 331 | + (System/exit 0)) |
| 332 | + |
| 333 | + :else |
| 334 | + (let [conn (d/get-conn dbp bark-schema {:wal? false}) |
| 335 | + last-shown (load-last-failures-shown) |
| 336 | + updated-shown (atom last-shown) |
| 337 | + sent (atom 0)] |
| 338 | + (try |
| 339 | + (let [db (d/db conn) |
| 340 | + src-map (build-source-map config) |
| 341 | + reports (all-reports db) |
| 342 | + all-failures (load-failures) |
| 343 | + pairs (for [[email subs] subscribers |
| 344 | + s subs] |
| 345 | + [email s]) |
| 346 | + live-pairs (filter (fn [[_ s]] (source-notify-enabled? src-map (:source s))) pairs)] |
| 347 | + (log/debug (count pairs) "subscription(s) configured") |
| 348 | + (log-skipped-pairs! src-map pairs) |
| 349 | + (log/debug (count live-pairs) "after per-source filter") |
357 | 350 | (if (empty? live-pairs) |
358 | 351 | (log/info "No active subscriptions.") |
359 | 352 | (doseq [[email subscription] live-pairs] |
360 | 353 | (let [source (:source subscription) |
361 | 354 | k (failures-key email source) |
362 | 355 | since-ms (get last-shown k) |
363 | 356 | since (when since-ms (java.util.Date. (long since-ms))) |
364 | | - failures (failures-for-subscriber all-failures email source since) |
| 357 | + failures (failures-for-subscriber |
| 358 | + all-failures |
| 359 | + {:email email :source source :since since}) |
365 | 360 | body (build-email-body db reports email subscription failures)] |
366 | 361 | (if body |
367 | 362 | (do (log/info (if dry-run? "[dry-run]" "") |
|
383 | 378 | (println "---"))) |
384 | 379 | (log/info "No open items for" email |
385 | 380 | (str "(source: " source "),") "skipping."))))) |
386 | | - (log/info "Done." (if dry-run? "Dry run, no emails sent." (str @sent " email(s) sent."))) |
387 | | - (finally |
388 | | - (when-not dry-run? |
389 | | - (try (save-last-failures-shown! @updated-shown) |
390 | | - (catch Exception e |
391 | | - (log/error "Failed to persist last-notify-failures:" (.getMessage e)))))))) |
392 | | - (finally |
393 | | - (d/close conn)))))) |
| 381 | + (log/info "Done." (if dry-run? "Dry run, no emails sent." (str @sent " email(s) sent.")))) |
| 382 | + (finally |
| 383 | + (when-not dry-run? |
| 384 | + (try (save-last-failures-shown! @updated-shown) |
| 385 | + (catch Exception e |
| 386 | + (log/error "Failed to persist last-notify-failures:" |
| 387 | + (.getMessage e))))) |
| 388 | + (d/close conn))))))) |
0 commit comments