metabase pulse代码简单跟踪分析如下:
获取要执行的pulse
(s/defn retrieve-scheduled-channels
"Fetch all `PulseChannels` that are scheduled to run at a given time described by `hour`, `weekday`, `monthday`, and
`monthweek`.
Examples:
(retrieve-scheduled-channels 14 \"mon\" :first :first) - 2pm on the first Monday of the month
(retrieve-scheduled-channels 8 \"wed\" :other :last) - 8am on Wednesday of the last week of the month
Based on the given input the appropriate `PulseChannels` are returned:
* `hourly` scheduled channels are always included.
* `daily` scheduled channels are included if the `hour` matches.
* `weekly` scheduled channels are included if the `weekday` & `hour` match.
* `monthly` scheduled channels are included if the `monthday`, `monthweek`, `weekday`, & `hour` all match."
[hour :- (s/maybe s/Int)
weekday :- (s/maybe (s/pred day-of-week?))
monthday :- (s/enum :first :last :mid :other)
monthweek :- (s/enum :first :last :other)]
(let [schedule-frame (cond
(= :mid monthday) "mid"
(= :first monthweek) "first"
(= :last monthweek) "last"
:else "invalid")
monthly-schedule-day-or-nil (when (= :other monthday)
weekday)]
(db/select [PulseChannel :id :pulse_id :schedule_type :channel_type]
{
:where [:and [:= :enabled true]
[:or [:= :schedule_type "hourly"]
[:and [:= :schedule_type "daily"]
[:= :schedule_hour hour]]
[:and [:= :schedule_type "weekly"]
[:= :schedule_hour hour]
[:= :schedule_day weekday]]
[:and [:= :schedule_type "monthly"]
[:= :schedule_hour hour]
[:= :schedule_frame schedule-frame]
[:or [:= :schedule_day weekday]
;; this is here specifically to allow for cases where day doesn't have to match
[:= :schedule_day monthly-schedule-day-or-nil]]]]]})))
quartz trigger初始化
(defmethod task/init! ::SendPulses [_]
(let [job (jobs/build
(jobs/of-type SendPulses)
(jobs/with-identity (jobs/key send-pulses-job-key)))
trigger (triggers/build
(triggers/with-identity (triggers/key send-pulses-trigger-key))
(triggers/start-now)
(triggers/with-schedule
(cron/schedule
;; run at the top of every hour
(cron/cron-schedule "0 0 * * * ? *")
;; If send-pulses! misfires, don't try to re-send all the misfired Pulses. Retry only the most
;; recent misfire, discarding all others. This should hopefully cover cases where a misfire
;; happens while the system is still running; if the system goes down for an extended period of
;; time we don't want to re-send tons of (possibly duplicate) Pulses.
;;
;; See https://www.nurkiewicz.com/2012/04/quartz-scheduler-misfire-instructions.html
(cron/with-misfire-handling-instruction-fire-and-proceed))))]
(task/schedule-task! job trigger)))
结合以上代码,可以发现最小单位就是hourly ,要去修改支持一小时以下是很麻烦。
各种channel支持的时间间隔
(def channel-types
"Map which contains the definitions for each type of pulse channel we allow. Each key is a channel type with a map
which contains any other relevant information for defining the channel. E.g.
{
:email {
:name \"Email\", :recipients? true}
:slack {
:name \"Slack\", :recipients? false}}"
{
:email {
:type "email"
:name "Email"
:allows_recipients true
:recipients ["user", "email"]
:schedules [:daily :weekly :monthly]}
:slack {
:type "slack"
:name "Slack"
:allows_recipients false
:schedules [:hourly :daily :weekly :monthly]
:fields [{
:name "channel"
:type "select"
:displayName "Post to"
:options []
:required true}]}})
如果要email也支持每小时发送,修改以上代码就可以
pulse发送记录日志
注意下,card(即question)运行失败并不会被以下代码记录
(s/defn do-with-task-history
"Impl for `with-task-history` macro; see documentation below."
[info :- TaskHistoryInfo, f]
(let [start-time-ms (System/currentTimeMillis)]
(try
(u/prog1 (f)
(save-task-history! start-time-ms info))
(catch Throwable e
(let [info (assoc info :task_details {
:status :failed
:exception (class e)
:message (.getMessage e)
:stacktrace (u/filtered-stacktrace e)
:ex-data (ex-data e)
:original-info (:task_details info)})]
(save-task-history! start-time-ms info))
(throw e)))))
Card error 同catch模式直接写日志了
具体代码如下:
(s/defn ^:private render-pulse-card-body :- common/RenderedPulseCard
[render-type timezone-id :- (s/maybe s/Str) card {
:keys [data error], :as results}]
(try
(when error
(throw (ex-info (tru "Card has errors: {0}" error) results)))
(let [chart-type (or (detect-pulse-chart-type card data)
(when (is-attached? card)
:attached)
:unknown)]
(log/debug (trs "Rendering pulse card with chart-type {0} and render-type {1}" chart-type render-type))
(body/render chart-type render-type timezone-id card data))
(catch Throwable e
(log/error e (trs "Pulse card render error"))
(body/render :error nil nil nil nil))))
...
card失败时候的调用栈如下:
```powershell
2020-11-09 09:00:00,290 ERROR pulse.render :: Pulse card render error
clojure.lang.ExceptionInfo: Card has errors: Column "WID" not found; SQL statement:
-- Metabase:: userID: 1 queryType: native queryHash: 133b68379d1830f9dadcb5a9e2bb89dafe98457d6818adbd8ff0d82396c3e009
select * from ORDERS where wid=1 [42122-197]
at metabase.pulse.render$eval43637$render_pulse_card_body__43642$fn__43646.invoke(render.clj:99) [?:?]
at metabase.pulse.render$eval43637$render_pulse_card_body__43642.invoke(render.clj:95) [?:?]
at metabase.pulse.render$eval43673$render_pulse_card__43678$fn__43679.invoke(render.clj:118) [?:?]
at metabase.pulse.render$eval43673$render_pulse_card__43678.invoke(render.clj:114) [?:?]
at metabase.pulse.render$eval43700$render_pulse_section__43705$fn__43711$fn__43715.invoke(render.clj:141) [?:?]
at metabase.pulse.render$eval43700$render_pulse_section__43705$fn__43711.invoke(render.clj:140) [?:?]
at metabase.pulse.render$eval43700$render_pulse_section__43705.invoke(render.clj:137) [?:?]
at metabase.email.messages$render_message_body$fn__45290$fn__45291.invoke(messages.clj:339) [?:?]
at clojure.core$mapv$fn__8445.invoke(core.clj:6912) [clojure-1.10.1.jar:?]
at clojure.core.protocols$fn__8159.invokeStatic(protocols.clj:168) [clojure-1.10.1.jar:?]
at clojure.core.protocols$fn__8159.invoke(protocols.clj:124) [clojure-1.10.1.jar:?]
at clojure.core.protocols$fn__8114$G__8109__8123.invoke(protocols.clj:19) [clojure-1.10.1.jar:?]
at clojure.core.protocols$seq_reduce.invokeStatic(protocols.clj:31) [clojure-1.10.1.jar:?]
at clojure.core.protocols$fn__8146.invokeStatic(protocols.clj:75) [clojure-1.10.1.jar:?]
at clojure.core.protocols$fn__8146.invoke(protocols.clj:75) [clojure-1.10.1.jar:?]
at clojure.core.protocols$fn__8088$G__8083__8101.invoke(protocols.clj:13) [clojure-1.10.1.jar:?]
at clojure.core$reduce.invokeStatic(core.clj:6828) [clojure-1.10.1.jar:?]
at clojure.core$mapv.invokeStatic(core.clj:6903) [clojure-1.10.1.jar:?]
at clojure.core$mapv.invoke(core.clj:6903) [clojure-1.10.1.jar:?]
at metabase.email.messages$render_message_body$fn__45290.invoke(messages.clj:339) [?:?]
at metabase.email.messages$render_message_body.invokeStatic(messages.clj:337) [?:?]
at metabase.email.messages$render_message_body.invoke(messages.clj:336) [?:?]
at metabase.email.messages$render_pulse_email.invokeStatic(messages.clj:354) [?:?]
at metabase.email.messages$render_pulse_email.invoke(messages.clj:351) [?:?]
at metabase.pulse$eval84834$fn__84837.invoke(pulse.clj:177) [?:?]
at clojure.lang.MultiFn.invoke(MultiFn.java:239) [clojure-1.10.1.jar:?]
at metabase.pulse$results__GT_notifications$iter__84878__84882$fn__84883.invoke(pulse.clj:221) [?:?]
at clojure.lang.LazySeq.sval(LazySeq.java:42) [clojure-1.10.1.jar:?]
at clojure.lang.LazySeq.seq(LazySeq.java:51) [clojure-1.10.1.jar:?]
at clojure.lang.RT.seq(RT.java:535) [clojure-1.10.1.jar:?]
at clojure.core$seq__5402.invokeStatic(core.clj:137) [clojure-1.10.1.jar:?]
at clojure.core$seq__5402.invoke(core.clj:137) [clojure-1.10.1.jar:?]
at metabase.pulse$send_notifications_BANG_.invokeStatic(pulse.clj:257) [?:?]
at metabase.pulse$send_notifications_BANG_.invoke(pulse.clj:256) [?:?]
at metabase.pulse$send_pulse_BANG_.invokeStatic(pulse.clj:283) [?:?]
at metabase.pulse$send_pulse_BANG_.doInvoke(pulse.clj:265) [?:?]
at clojure.lang.RestFn.invoke(RestFn.java:439) [clojure-1.10.1.jar:?]
at metabase.task.send_pulses$eval91579$send_pulses_BANG___91588$fn__91591$fn__91609$fn__91610.invoke(send_pulses.clj:59) [?:?]
at metabase.models.task_history$eval52837$do_with_task_history__52842$fn__52843.invoke(task_history.clj:77) [?:?]
at metabase.models.task_history$eval52837$do_with_task_history__52842.invoke(task_history.clj:72) [?:?]
at metabase.task.send_pulses$eval91579$send_pulses_BANG___91588$fn__91591$fn__91609.invoke(send_pulses.clj:56) [?:?]
at metabase.task.send_pulses$eval91579$send_pulses_BANG___91588$fn__91591.invoke(send_pulses.clj:55) [?:?]
at metabase.task.send_pulses$eval91579$send_pulses_BANG___91588.invoke(send_pulses.clj:42) [?:?]
at metabase.task.send_pulses$eval91579$send_pulses_BANG___91588$fn__91589.invoke(send_pulses.clj:49) [?:?]
at metabase.task.send_pulses$eval91579$send_pulses_BANG___91588.invoke(send_pulses.clj:42) [?:?]
at metabase.task.send_pulses.SendPulses$fn__91645.invoke(send_pulses.clj:100) [?:?]
at metabase.models.task_history$eval52837$do_with_task_history__52842$fn__52843.invoke(task_history.clj:77) [?:?]
at metabase.models.task_history$eval52837$do_with_task_history__52842.invoke(task_history.clj:72) [?:?]
at metabase.task.send_pulses.SendPulses.execute(send_pulses.clj:86) [?:?]
at org.quartz.core.JobRunShell.run(JobRunShell.java:213) [quartz-2.1.7.jar:?]
at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:557) [quartz-2.1.7.jar:?]