Compare commits

...

2 Commits

Author SHA1 Message Date
Ngoc Khuat
bbb9879325 revert debugging code 2025-05-30 11:34:23 +07:00
Ngoc Khuat
150218536d Remove redundant json encode/deocde when render static viz 2025-05-30 11:33:13 +07:00
3 changed files with 115 additions and 53 deletions

View File

@@ -19,10 +19,10 @@ function toJSMap(m) {
*/
function row_chart(settings, data, colors, tokenFeatures) {
return StaticViz.LegacyRenderChart("row", {
settings: JSON.parse(settings),
data: JSON.parse(data),
colors: JSON.parse(colors),
tokenFeatures: JSON.parse(tokenFeatures),
settings: settings,
data: data,
colors: colors,
tokenFeatures: tokenFeatures,
});
}
@@ -31,9 +31,9 @@ function row_chart(settings, data, colors, tokenFeatures) {
*/
function gauge(card, data, tokenFeatures) {
return StaticViz.LegacyRenderChart("gauge", {
card: JSON.parse(card),
data: JSON.parse(data),
tokenFeatures: JSON.parse(tokenFeatures),
card: card,
data: data,
tokenFeatures: tokenFeatures,
});
}
@@ -42,9 +42,9 @@ function gauge(card, data, tokenFeatures) {
*/
function funnel(data, settings, tokenFeatures) {
return StaticViz.LegacyRenderChart("funnel", {
data: JSON.parse(data),
settings: JSON.parse(settings),
tokenFeatures: JSON.parse(tokenFeatures),
data: data,
settings: settings,
tokenFeatures: tokenFeatures,
});
}
@@ -53,18 +53,18 @@ function funnel(data, settings, tokenFeatures) {
*/
function progress(data, settings, instanceColors, tokenFeatures) {
return StaticViz.LegacyRenderChart("progress", {
data: JSON.parse(data),
settings: JSON.parse(settings),
colors: JSON.parse(instanceColors),
tokenFeatures: JSON.parse(tokenFeatures),
data: data,
settings: settings,
colors: instanceColors,
tokenFeatures: tokenFeatures,
});
}
function javascript_visualization(rawSeries, dashcardSettings, options) {
const content = StaticViz.RenderChart(
JSON.parse(rawSeries),
JSON.parse(dashcardSettings),
JSON.parse(options),
rawSeries,
dashcardSettings,
options,
);
const type = content.startsWith("<svg") ? "svg" : "html";

View File

@@ -11,7 +11,8 @@
[clojure.java.io :as io]
[metabase.util.i18n :refer [trs]])
(:import
(org.graalvm.polyglot Context HostAccess Source Value)))
(org.graalvm.polyglot Context HostAccess Source Value)
(org.graalvm.polyglot.proxy ProxyArray ProxyObject)))
(set! *warn-on-reflection* true)
@@ -52,6 +53,71 @@
{:source source})))
(.eval context (.build (Source/newBuilder "js" resource)))))
(defn- deserialize-number
[^Value result]
(cond
(.fitsInShort result)
(.asShort result)
(.fitsInLong result)
(.asLong result)
(.fitsInInt result)
(.asInt result)
(.fitsInDouble result)
(.asDouble result)
(.fitsInFloat result)
(.asFloat result)))
(defn- deserialize
[^Value result]
(cond
(.isNumber result)
(deserialize-number result)
(.isString result)
(.asString result)
(.hasArrayElements result)
(let [n (.getArraySize result)]
(into [] (map (fn [idx]
(deserialize (.getArrayElement result idx)))
(range 0 n))))
(.isNull result)
nil
(.isBoolean result)
(.asBoolean result)
:else
result))
(defn- serialize-arg [arg]
(cond
(keyword? arg)
(name arg)
(symbol? arg)
(name arg)
(map? arg)
(ProxyObject/fromMap (into {} (map (fn [[k v]]
[(serialize-arg k) (serialize-arg v)])
arg)))
(coll? arg)
(ProxyArray/fromArray (into-array Object (map serialize-arg arg)))
:else
arg))
(defn- execute*
[fn-ref args]
(deserialize (.execute fn-ref (into-array Object (map serialize-arg args)))))
(defn execute-fn-name
"Executes `js-fn-name` in js context with args"
^Value [^Context context js-fn-name & args]
@@ -60,14 +126,15 @@
;; There is a couple of idea we can try:
;; - put a thread pool around context initialization
;; - init a new context for each thread
;; maybe we can remove the lock now as we have pooled static-viz-context
;; https://github.com/metabase/metabase/pull/56648
(locking context
(let [fn-ref (.eval context "js" js-fn-name)
args (into-array Object args)]
(let [fn-ref (.eval context "js" js-fn-name)]
(assert (.canExecute fn-ref) (str "cannot execute " js-fn-name))
(.execute fn-ref args))))
(execute* fn-ref args))))
(defn execute-fn
"fn-ref should be an executable org.graalvm.polyglot.Value return from a js engine. Invoke this function with args."
^Value [^Value fn-ref & args]
(assert (.canExecute fn-ref) "cannot execute function reference")
(.execute fn-ref (object-array args)))
(execute* fn-ref args))

View File

@@ -191,29 +191,29 @@
(defn funnel
"Clojure entrypoint to render a funnel chart. Data should be vec of [[Step Measure]] where Step is
{:name name :format format-options}
{:name name :format format-options}
and Measure is {:format format-options} and you go and look to
frontend/src/metabase/static-viz/components/FunnelChart/types.ts for the actual format options. Returns a byte array
of a png file."
[data settings]
(let [svg-string (with-static-viz-context context
(.asString (js.engine/execute-fn-name context "funnel" (json/encode data)
(json/encode settings)
(json/encode (premium-features/token-features)))))]
(.asString (js.engine/execute-fn-name context "funnel" data
settings
(premium-features/token-features))))]
(svg-string->bytes svg-string)))
(defn ^:dynamic *javascript-visualization*
"Clojure entrypoint to render javascript visualizations. This functions is dynanic only for testing purposes."
[cards-with-data dashcard-viz-settings]
(let [response (with-static-viz-context context
(.asString (js.engine/execute-fn-name context "javascript_visualization"
(json/encode cards-with-data)
(json/encode dashcard-viz-settings)
(json/encode {:applicationColors (appearance/application-colors)
:startOfWeek (lib-be/start-of-week)
:customFormatting (appearance/custom-formatting)
:tokenFeatures (premium-features/token-features)}))))]
(js.engine/execute-fn-name context "javascript_visualization"
cards-with-data
dashcard-viz-settings
{:applicationColors (appearance/application-colors)
:startOfWeek (lib-be/start-of-week)
:customFormatting (appearance/custom-formatting)
:tokenFeatures (premium-features/token-features)}))]
(-> response
json/decode+kw
(update :type (fnil keyword "unknown")))))
@@ -221,36 +221,31 @@
(defn row-chart
"Clojure entrypoint to render a row chart."
[settings data]
(let [svg-string (with-static-viz-context context
(.asString (js.engine/execute-fn-name context "row_chart"
(json/encode settings)
(json/encode data)
(json/encode (appearance/application-colors))
(json/encode (premium-features/token-features)))))]
(svg-string->bytes svg-string)))
(with-static-viz-context context
(svg-string->bytes (js.engine/execute-fn-name context "row_chart"
settings
data
(appearance/application-colors)
(premium-features/token-features)))))
(defn gauge
"Clojure entrypoint to render a gauge chart. Returns a byte array of a png file"
[card data]
(with-static-viz-context context
(let [js-res (js.engine/execute-fn-name context "gauge"
(json/encode card)
(json/encode data)
(json/encode (premium-features/token-features)))
svg-string (.asString js-res)]
(svg-string->bytes svg-string))))
(svg-string->bytes (js.engine/execute-fn-name context "gauge"
card
data
(premium-features/token-features)))))
(defn progress
"Clojure entrypoint to render a progress bar. Returns a byte array of a png file"
[value goal settings]
(with-static-viz-context context
(let [js-res (js.engine/execute-fn-name context "progress"
(json/encode {:value value :goal goal})
(json/encode settings)
(json/encode (appearance/application-colors))
(json/encode (premium-features/token-features)))
svg-string (.asString js-res)]
(svg-string->bytes svg-string))))
(svg-string->bytes (js.engine/execute-fn-name context "progress"
{:value value :goal goal}
settings
(appearance/application-colors)
(premium-features/token-features)))))
(def ^:private icon-paths
{:dashboard "M32 28a4 4 0 0 1-4 4H4a4.002 4.002 0 0 1-3.874-3H0V4a4 4 0 0 1 4-4h25a3 3 0 0 1 3 3v25zm-4 0V8H4v20h24zM7.273 18.91h10.182v4.363H7.273v-4.364zm0-6.82h17.454v4.365H7.273V12.09zm13.09 6.82h4.364v4.363h-4.363v-4.364z"