From f90899ab71cf221a892f4136ee406326a0792245 Mon Sep 17 00:00:00 2001 From: Cam Saul <cammsaul@gmail.com> Date: Tue, 7 May 2019 15:21:56 -0700 Subject: [PATCH] Move index.html template code -> new metabase.routes.index ns :twisted_rightwards_arrows: --- src/metabase/public_settings.clj | 2 +- src/metabase/routes.clj | 94 ++++---------------------------- src/metabase/routes/index.clj | 85 +++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 85 deletions(-) create mode 100644 src/metabase/routes/index.clj diff --git a/src/metabase/public_settings.clj b/src/metabase/public_settings.clj index 66b06c528d5..066dbd30d2f 100644 --- a/src/metabase/public_settings.clj +++ b/src/metabase/public_settings.clj @@ -175,7 +175,7 @@ (def ^:private short-timezone-name (memoize short-timezone-name*)) - +;; TODO - it seems like it would be a nice performance win to cache this a little bit (defn public-settings "Return a simple map of key/value pairs which represent the public settings (`MetabaseBootstrap`) for the front-end application." diff --git a/src/metabase/routes.clj b/src/metabase/routes.clj index c9d31f68b71..8b61fa85cfc 100644 --- a/src/metabase/routes.clj +++ b/src/metabase/routes.clj @@ -1,14 +1,9 @@ (ns metabase.routes "Main Compojure routes tables. See https://github.com/weavejester/compojure/wiki/Routes-In-Detail for details about how these work. `/api/` routes are in `metabase.api.routes`." - (:require [cheshire.core :as json] - [clojure.java.io :as io] - [clojure.string :as str] - [clojure.tools.logging :as log] - [compojure + (:require [compojure [core :refer [context defroutes GET]] [route :as route]] - [hiccup.util :as h.util] [metabase [public-settings :as public-settings] [util :as u]] @@ -16,80 +11,8 @@ [dataset :as dataset-api] [routes :as api]] [metabase.core.initialization-status :as init-status] - [metabase.util - [embed :as embed] - [i18n :refer [trs]]] - [puppetlabs.i18n.core :refer [*locale*]] - [ring.util.response :as resp] - [stencil.core :as stencil])) - -(defn- base-href [] - (let [path (some-> (public-settings/site-url) io/as-url .getPath)] - (str path "/"))) - -(defn- escape-script [s] - ;; Escapes text to be included in an inline <script> tag, in particular the string '</script' - ;; https://stackoverflow.com/questions/14780858/escape-in-script-tag-contents/23983448#23983448 - (str/replace s #"(?i)</script" "</scr\\\\ipt")) - -(defn- load-template [path variables] - (try - (stencil/render-file path variables) - (catch IllegalArgumentException e - (let [message (str (trs "Failed to load template ''{0}''. Did you remember to build the Metabase frontend?" path))] - (log/error e message) - (throw (Exception. message e)))))) - -(defn- fallback-localization [locale] - (json/generate-string - {"headers" - {"language" locale - "plural-forms" "nplurals=2; plural=(n != 1);"} - - "translations" - {"" {"Metabase" {"msgid" "Metabase" - "msgstr" ["Metabase"]}}}})) - -(defn- load-localization* [locale] - (if (or (not locale) - (= (str locale) "en")) - (fallback-localization locale) - (try - (load-file-at-path (str "frontend_client/app/locales/" locale ".json")) - (catch Throwable e - (log/warn (trs "Locale ''{0}'' not found." locale)) - (fallback-localization locale))))) - -(def ^:private ^{:arglists '([])} load-localization - (let [memoized-load-localization (memoize load-localization*)] - (fn [] - (memoized-load-localization *locale*)))) - -(defn- load-entrypoint-template [entrypoint-name embeddable? uri] - (load-template - (str "frontend_client/" entrypoint-name ".html") - (let [{:keys [anon_tracking_enabled google_auth_client_id], :as public-settings} (public-settings/public-settings)] - {:bootstrapJSON (escape-script (json/generate-string public-settings)) - :localizationJSON (escape-script (load-localization)) - :uri (h.util/escape-html uri) - :baseHref (h.util/escape-html (base-href)) - :embedCode (when embeddable? (embed/head uri)) - :enableGoogleAuth (boolean google_auth_client_id) - :enableAnonTracking (boolean anon_tracking_enabled)}))) - -(defn- entrypoint - "Repsonse that serves up an entrypoint into the Metabase application, e.g. `index.html`." - [entrypoint-name embeddable? {:keys [uri]} respond raise] - (respond - (-> (if (init-status/complete?) - (load-entrypoint-template entrypoint-name embeddable? uri) - (load-file-at-path "frontend_client/init.html")) - resp/response - (resp/content-type "text/html; charset=utf-8")))) - -(def ^:private index (partial entrypoint "index" (not :embeddable))) -(def ^:private public (partial entrypoint "public" :embeddable)) -(def ^:private embed (partial entrypoint "embed" :embeddable)) + [metabase.routes.index :as index] + [ring.util.response :as resp])) (defn- redirect-including-query-string "Like `resp/redirect`, but passes along query string URL params as well. This is important because the public and @@ -98,24 +21,27 @@ (fn [{:keys [query-string]} respond _] (respond (resp/redirect (str url "?" query-string))))) + ;; /public routes. /public/question/:uuid.:export-format redirects to /api/public/card/:uuid/query/:export-format (defroutes ^:private public-routes (GET ["/question/:uuid.:export-format", :uuid u/uuid-regex, :export-format dataset-api/export-format-regex] [uuid export-format] (redirect-including-query-string (format "%s/api/public/card/%s/query/%s" (public-settings/site-url) uuid export-format))) - (GET "*" [] public)) + (GET "*" [] index/public)) + ;; /embed routes. /embed/question/:token.:export-format redirects to /api/public/card/:token/query/:export-format (defroutes ^:private embed-routes (GET ["/question/:token.:export-format", :export-format dataset-api/export-format-regex] [token export-format] (redirect-including-query-string (format "%s/api/embed/card/%s/query/%s" (public-settings/site-url) token export-format))) - (GET "*" [] embed)) + (GET "*" [] index/embed)) + ;; Redirect naughty users who try to visit a page other than setup if setup is not yet complete (defroutes ^{:doc "Top-level ring routes for Metabase."} routes ;; ^/$ -> index.html - (GET "/" [] index) + (GET "/" [] index/index) (GET "/favicon.ico" [] (resp/resource-response "frontend_client/favicon.ico")) ;; ^/api/health -> Health Check Endpoint (GET "/api/health" [] (if (init-status/complete?) @@ -138,4 +64,4 @@ ;; ^/emebed/ -> Embed frontend and download routes (context "/embed" [] embed-routes) ;; Anything else (e.g. /user/edit_current) should serve up index.html; React app will handle the rest - (GET "*" [] index)) + (GET "*" [] index/index)) diff --git a/src/metabase/routes/index.clj b/src/metabase/routes/index.clj new file mode 100644 index 00000000000..528b11f4286 --- /dev/null +++ b/src/metabase/routes/index.clj @@ -0,0 +1,85 @@ +(ns metabase.routes.index + "Logic related to loading various versions of the index.html template. The actual template lives in + `resources/frontend_client/index_template.html`; when the frontend is built (e.g. via `./bin/build frontend`) + different versions that include the FE app are created as `index.html`, `public.html`, and `embed.html`." + (:require [cheshire.core :as json] + [clojure.java.io :as io] + [clojure.string :as str] + [clojure.tools.logging :as log] + [hiccup.util :as h.util] + [metabase.core.initialization-status :as init-status] + [metabase.public-settings :as public-settings] + [metabase.util + [embed :as embed] + [i18n :refer [trs]]] + [puppetlabs.i18n.core :refer [*locale*]] + [ring.util.response :as resp] + [stencil.core :as stencil])) + +(defn- base-href [] + (let [path (some-> (public-settings/site-url) io/as-url .getPath)] + (str path "/"))) + +(defn- escape-script [s] + ;; Escapes text to be included in an inline <script> tag, in particular the string '</script' + ;; https://stackoverflow.com/questions/14780858/escape-in-script-tag-contents/23983448#23983448 + (str/replace s #"(?i)</script" "</scr\\\\ipt")) + + +(defn- fallback-localization [locale] + (json/generate-string + {"headers" + {"language" locale + "plural-forms" "nplurals=2; plural=(n != 1);"} + + "translations" + {"" {"Metabase" {"msgid" "Metabase" + "msgstr" ["Metabase"]}}}})) + +(defn- load-localization* [locale] + (or + (when (and locale (not= locale "en")) + (try + (slurp (str "frontend_client/app/locales/" locale ".json")) + (catch Throwable e + (log/warn (trs "Locale ''{0}'' not found." locale))))) + (fallback-localization locale))) + +(def ^:private ^{:arglists '([])} load-localization + (let [memoized-load-localization (memoize load-localization*)] + (fn [] + (memoized-load-localization *locale*)))) + + +(defn- load-template [path variables] + (try + (stencil/render-file path variables) + (catch IllegalArgumentException e + (let [message (str (trs "Failed to load template ''{0}''. Did you remember to build the Metabase frontend?" path))] + (log/error e message) + (throw (Exception. message e)))))) + +(defn- load-entrypoint-template [entrypoint-name embeddable? uri] + (load-template + (str "frontend_client/" entrypoint-name ".html") + (let [{:keys [anon_tracking_enabled google_auth_client_id], :as public-settings} (public-settings/public-settings)] + {:bootstrapJSON (escape-script (json/generate-string public-settings)) + :localizationJSON (escape-script (load-localization)) + :uri (h.util/escape-html uri) + :baseHref (h.util/escape-html (base-href)) + :embedCode (when embeddable? (embed/head uri)) + :enableGoogleAuth (boolean google_auth_client_id) + :enableAnonTracking (boolean anon_tracking_enabled)}))) + +(defn- entrypoint + "Repsonse that serves up an entrypoint into the Metabase application, e.g. `index.html`." + [entrypoint-name embeddable? {:keys [uri]} respond raise] + (respond + (-> (if (init-status/complete?) + (resp/response (load-entrypoint-template entrypoint-name embeddable? uri)) + (resp/resource-response "frontend_client/init.html")) + (resp/content-type "text/html; charset=utf-8")))) + +(def index "main index.html entrypoint." (partial entrypoint "index" (not :embeddable))) +(def public "/public index.html entrypoint." (partial entrypoint "public" :embeddable)) +(def embed "/embed index.html entrypoint." (partial entrypoint "embed" :embeddable)) -- GitLab