diff --git a/CHANGELOG.md b/CHANGELOG.md index fd3c062..f345f41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Change Log All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). +## [2-beta28] - unreleased +- added a `snippets` API namespace for easier editor inntegration - by @timothyprately +- added a `kindly-compute` endpoint - for requesting server computations - with @RamNarayan-27 + ## [2-beta27] - 2024-12-24 - added a `:browse` option (default `true`) to determine whether to open a browser tab when the server is started diff --git a/notebooks/compute_examples.clj b/notebooks/compute_examples.clj new file mode 100644 index 0000000..e2d452a --- /dev/null +++ b/notebooks/compute_examples.clj @@ -0,0 +1,96 @@ +(ns compute-examples + (:require [scicloj.kindly.v4.kind :as kind] + [scicloj.kindly.v4.kind :as kind] + [scicloj.kindly.v4.kind :as kind])) + +(defn ^:kindly/servable + add [a b] + (+ a b)) + +(defn ^:kindly/servable + calc-click-and-open-rate [data] + (let [total-emails (count data) + opened-emails (count (filter #(nth % 2) data)) + clicked-emails (count (filter #(nth % 3) data)) + open-rate (if (pos? total-emails) (double (* 100 (/ opened-emails total-emails))) 0.0) + click-rate (if (pos? total-emails) (double (* 100 (/ clicked-emails total-emails))) 0.0)] + {:open-rate open-rate + :click-rate click-rate})) + +(kind/reagent + '(defn kindly-compute [input callback] + (ajax.core/POST + "/kindly-compute" + {:headers {"Accept" "application/json"} + :params (pr-str input) + :handler (fn [response] + (-> response + read-string + callback)) + :error-handler (fn [e] + (.log + js/console + (str "error on compute: " e)))}))) + +(kind/reagent + ['(fn [] + (let [*a1 (reagent.core/atom 10)] + (fn [] + [:div + [:p @*a1] + [:input {:type "button" :value "Click me!" + :on-click (fn [] + (kindly-compute + {:func 'dummy/add + :args [@*a1 20]} + (fn [response] + (reset! *a1 response))))}]])))]) + + +(kind/md + "### New example of a function that will calculate the open and click rates of the email data below") + +(def email-data + [["email1@example.com" "19-12-2024 11:46:05" "19-12-2024 12:00:00" "19-12-2024 12:05:00"] + ["email2@example.com" "19-12-2024 11:46:06" "20-12-2024 12:00:00" nil] + ["email3@example.com" "19-12-2024 11:46:07" "21-12-2024 12:00:00" "21-12-2024 12:05:00"] + ["email4@example.com" "19-12-2024 11:46:08" "22-12-2024 12:00:00" "22-12-2024 12:05:00"] + ["email5@example.com" "19-12-2024 11:46:09" nil nil] + ["email6@example.com" "19-12-2024 11:46:10" "24-12-2024 12:00:00" "24-12-2024 12:05:00"] + ["email7@example.com" "19-12-2024 11:46:11" nil nil] + ["email8@example.com" "19-12-2024 11:46:12" "26-12-2024 12:00:00" "26-12-2024 12:05:00"] + ["email9@example.com" "19-12-2024 11:46:13" "27-12-2024 12:00:00" nil] + ["email10@example.com" "19-12-2024 11:46:14" "28-12-2024 12:00:00" nil]]) + +(kind/reagent + '(def email-data + [["email1@example.com" "19-12-2024 11:46:05" "19-12-2024 12:00:00" "19-12-2024 12:05:00"] + ["email2@example.com" "19-12-2024 11:46:06" "20-12-2024 12:00:00" nil] + ["email3@example.com" "19-12-2024 11:46:07" "21-12-2024 12:00:00" "21-12-2024 12:05:00"] + ["email4@example.com" "19-12-2024 11:46:08" "22-12-2024 12:00:00" "22-12-2024 12:05:00"] + ["email5@example.com" "19-12-2024 11:46:09" nil nil] + ["email6@example.com" "19-12-2024 11:46:10" "24-12-2024 12:00:00" "24-12-2024 12:05:00"] + ["email7@example.com" "19-12-2024 11:46:11" nil nil] + ["email8@example.com" "19-12-2024 11:46:12" "26-12-2024 12:00:00" "26-12-2024 12:05:00"] + ["email9@example.com" "19-12-2024 11:46:13" "27-12-2024 12:00:00" nil] + ["email10@example.com" "19-12-2024 11:46:14" "28-12-2024 12:00:00" nil]])) + +(kind/table + {:column-names [:email :sent-at :opened-at :clicked-at] + :row-vectors email-data}) + +(kind/reagent + ['(fn [] + (let [*rates (reagent.core/atom {})] + (fn [] + [:div + [:p (str "Open rate " (:open-rate @*rates))] + [:p (str "Click rate " (:click-rate @*rates))] + [:input {:type "button" :value "Click to calculate click and open rate" + :on-click (fn [] + (kindly-compute + {:func 'dummy/calc-click-and-open-rate + :args [email-data]} + (fn [response] + (reset! *rates response))))}]])))]) + diff --git a/src/scicloj/clay/v2/server.clj b/src/scicloj/clay/v2/server.clj index 2bd0c95..47c6e17 100644 --- a/src/scicloj/clay/v2/server.clj +++ b/src/scicloj/clay/v2/server.clj @@ -9,6 +9,7 @@ [scicloj.clay.v2.util.time :as time] [scicloj.clay.v2.item :as item] [clojure.string :as str] + [cognitect.transit :as transit] [hiccup.core :as hiccup]) (:import (java.net ServerSocket))) @@ -40,7 +41,6 @@ clay_server_counter = '%d'; clay_refresh = function() {location.assign('http://localhost:'+clay_port);} - const clay_socket = new WebSocket('ws://localhost:'+clay_port); clay_socket.addEventListener('open', (event) => { clay_socket.send('Hello Server!')}); @@ -107,12 +107,23 @@ :last-rendered-spec :hide-ui-header) (hiccup/html - #_[:style "* {margin: 0; padding: 0; top: 0;}"] - [:div {:style {:height "70px" - :background-color "#eee"}} - (header state)])) + #_[:style "* {margin: 0; padding: 0; top: 0;}"] + [:div {:style {:height "70px" + :background-color "#eee"}} + (header state)])) (communication-script state))))) +(defn compute + [input] + (let [{:keys [func args]} input] + (if-let [func-var (resolve func)] + (if (-> func-var meta :kindly/servable) + (apply func-var args) + (throw (Exception. (str "Function is not safe to serve: " + func)))) + (throw (Exception. (str "Symbol not found: " + func)))))) + (defn routes "Web server routes." [{:keys [:body :request-method :uri] @@ -132,7 +143,13 @@ :counter str) :status 200} - + [:post "/kindly-compute"] (let [input (-> body + (transit/reader :json) + transit/read + read-string) + output (compute input)] + {:body (pr-str output) + :status 200}) ;; else (let [f (io/file (str (:base-target-path state) uri))] (if (.exists f) @@ -211,3 +228,5 @@ (when-let [s @*stop-server!] (s)) (reset! *stop-server! nil)) + +