metabase pulse代码分析

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:?]

猜你喜欢

转载自blog.csdn.net/weixin_40455124/article/details/109588445