(ns metabase.channel.impl.http (:require [clj-http.client :as http] [java-time.api :as t] [metabase.channel.core :as channel] [metabase.channel.render.core :as channel.render] [metabase.channel.shared :as channel.shared] [metabase.util.i18n :refer [tru]] [metabase.util.json :as json] [metabase.util.malli :as mu] [metabase.util.malli.schema :as ms] [metabase.util.urls :as urls])) | |
Maximum width of the rendered PNG of HTML to be sent to HTTP Content that exceeds this width (e.g. a table with many columns) is truncated. | (def ^:private image-width 1200) |
(def ^:private string-or-keyword [:or :string :keyword]) | |
(def ^:private HTTPDetails [:map {:closed true} [:url ms/Url] [:auth-method [:enum "none" "header" "query-param" "request-body"]] [:auth-info {:optional true} [:map-of string-or-keyword :any]] ;; used by the frontend to display the auth info properly [:fe-form-type {:optional true} [:enum "api-key" "bearer" "basic" "none"]] ;; request method [:method {:optional true} [:enum "get" "post" "put"]]]) | |
(def ^:private HTTPChannel [:map [:type [:= :channel/http]] [:details HTTPDetails]]) | |
(mu/defmethod channel/send! :channel/http [{{:keys [url method auth-method auth-info]} :details} :- HTTPChannel request] (let [req (merge {:accept :json :content-type :json :method :post :url url} (when method {:method (keyword method)}) (cond-> request (= "request-body" auth-method) (update :body merge auth-info) (= "header" auth-method) (update :headers merge auth-info) (= "query-param" auth-method) (update :query-params merge auth-info)))] (http/request (cond-> req (or (map? (:body req)) (sequential? (:body req))) (update :body json/encode))))) | |
(defn- maybe-parse-json [x] (if (string? x) (try (json/decode x) (catch Exception _e x)) x)) | |
(defmethod channel/can-connect? :channel/http [_channel-type details] (channel.shared/validate-channel-details HTTPDetails details) (try (channel/send! {:type :channel/http :details details} {}) true (catch Exception e (let [data (ex-data e)] ;; throw an appriopriate error if it's a connection error (if (= ::http/unexceptional-status (:type data)) (throw (ex-info (tru "Failed to connect to channel") {:request-status (:status data) :request-body (maybe-parse-json (:body data))})) (throw e)))))) | |
------------------------------------------------------------------------------------------------;; Alerts ;; ------------------------------------------------------------------------------------------------;; | |
(defn- qp-result->raw-data [qp-result] (let [data (:data qp-result)] {:cols (map :name (:cols data)) :rows (:rows data)})) | |
(mu/defmethod channel/render-notification [:channel/http :notification/card] [_channel-type {:keys [payload creator]} _template _recipients] (let [{:keys [card alert card_part]} payload request-body {:type "alert" :alert_id (:id alert) :alert_creator_id (:id creator) :alert_creator_name (:common_name creator) :data {:type "question" :question_id (:id card) :question_name (:name card) :question_url (urls/card-url (:id card)) :visualization (let [{:keys [card dashcard result]} card_part] (channel.render/render-pulse-card-to-base64 (channel.render/defaulted-timezone card) card dashcard result image-width)) :raw_data (qp-result->raw-data (:result card_part))} :sent_at (t/offset-date-time)}] [{:body request-body}])) | |