From d135971fd4d09bb2a315cb534764064b15674428 Mon Sep 17 00:00:00 2001 From: daslu Date: Sun, 10 Nov 2024 17:55:09 +0200 Subject: [PATCH 01/12] server experiments - WIP --- notebooks/dummy.clj | 43 ++++++++++++++++++++ src/scicloj/clay/v2/server.clj | 72 +++++++++++++++++++++------------- 2 files changed, 87 insertions(+), 28 deletions(-) create mode 100644 notebooks/dummy.clj diff --git a/notebooks/dummy.clj b/notebooks/dummy.clj new file mode 100644 index 0000000..e0dfcc7 --- /dev/null +++ b/notebooks/dummy.clj @@ -0,0 +1,43 @@ +(ns dummy + (:require [scicloj.kindly.v4.kind :as kind])) + + + +;; Interpreted by the Scittle interpreter +;; in the browser. +(kind/reagent + '(def *a1 (reagent.core/atom 0))) + +;; Just JVM Clojure code. +(def *a2 (atom 0)) + + +(kind/reagent + '(defn compute [input callback] + (ajax.core/POST + "/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 reset: " e)))}))) + + +(kind/reagent + ['(fn [] + [:div + [:p @*a1] + [:input {:type "button" :value "Click me!" + :on-click (fn [] + (compute + @*a1 + (fn [response] + (reset! *a1 response))))}]])]) + + + diff --git a/src/scicloj/clay/v2/server.clj b/src/scicloj/clay/v2/server.clj index b4eed31..42ac615 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!')}); @@ -113,6 +113,10 @@ (header state)])) (communication-script state))))) +(defn abcd [x] + (+ x 9)) + + (defn routes "Web server routes." [{:keys [:body :request-method :uri] @@ -122,37 +126,47 @@ (httpkit/as-channel req {:on-open (fn [ch] (swap! *clients conj ch)) :on-close (fn [ch _reason] (swap! *clients disj ch)) :on-receive (fn [_ch msg])}) - (case [request-method uri] - [:get "/"] {:body (-> state - page - (wrap-html state)) - :headers {"Content-Type" "text/html"} - :status 200} - [:get "/counter"] {:body (-> state - :counter - str) - :status 200} + (case [request-method uri] + [:get "/"] {:body (-> state + page + (wrap-html state)) + :headers {"Content-Type" "text/html"} + :status 200} + [:get "/counter"] {:body (-> state + :counter + str) + :status 200} + + [:post "/compute"] (let [input (-> body + (transit/reader :json) + transit/read + read-string) + _ (prn [:input input]) + output (abcd input)] + (prn [:output output]) + {:body (pr-str output) + :status 200}) ;; else - (let [f (io/file (str (:base-target-path state) uri))] - (if (.exists f) - {:body (if (re-matches #".*\.html$" uri) - (-> f - slurp - (wrap-html state)) - f) - :headers (when (str/ends-with? uri ".js") - {"Content-Type" "text/javascript"}) - :status 200} - (case [request-method uri] + (let [f (io/file (str (:base-target-path state) uri))] + (if (.exists f) + {:body (if (re-matches #".*\.html$" uri) + (-> f + slurp + (wrap-html state)) + f) + :headers (when (str/ends-with? uri ".js") + {"Content-Type" "text/javascript"}) + :status 200} + (case [request-method uri] ;; user files have priority, otherwise serve the default from resources - [:get "/favicon.ico"] {:body (io/input-stream (io/resource "favicon.ico")) - :status 200} + [:get "/favicon.ico"] {:body (io/input-stream (io/resource "favicon.ico")) + :status 200} ;; this image is for the header above the page during interactive mode - [:get "/Clay.svg.png"] {:body (io/input-stream (io/resource "Clay.svg.png")) - :status 200} - {:body "not found" - :status 404}))))))) + [:get "/Clay.svg.png"] {:body (io/input-stream (io/resource "Clay.svg.png")) + :status 200} + {:body "not found" + :status 404}))))))) (defonce *stop-server! (atom nil)) @@ -210,3 +224,5 @@ (when-let [s @*stop-server!] (s)) (reset! *stop-server! nil)) + + From b50469468f55c9c00882cee4f5efa4b37a413994 Mon Sep 17 00:00:00 2001 From: Ram Narayan <104751383+RamNarayan-27@users.noreply.github.com> Date: Tue, 10 Dec 2024 00:15:03 +0530 Subject: [PATCH 02/12] add compute endpoint in the server to update an atom in the client --- notebooks/dummy.clj | 12 +++++------- src/scicloj/clay/v2/server.clj | 11 ++++++++--- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/notebooks/dummy.clj b/notebooks/dummy.clj index e0dfcc7..ac89eb8 100644 --- a/notebooks/dummy.clj +++ b/notebooks/dummy.clj @@ -6,7 +6,7 @@ ;; Interpreted by the Scittle interpreter ;; in the browser. (kind/reagent - '(def *a1 (reagent.core/atom 0))) + '(def *a1 (reagent.core/atom 10))) ;; Just JVM Clojure code. (def *a2 (atom 0)) @@ -35,9 +35,7 @@ [:input {:type "button" :value "Click me!" :on-click (fn [] (compute - @*a1 - (fn [response] - (reset! *a1 response))))}]])]) - - - + {:func :add + :args [@*a1 20]} + (fn [response] + (reset! *a1 response))))}]])]) diff --git a/src/scicloj/clay/v2/server.clj b/src/scicloj/clay/v2/server.clj index 42ac615..e876400 100644 --- a/src/scicloj/clay/v2/server.clj +++ b/src/scicloj/clay/v2/server.clj @@ -113,9 +113,14 @@ (header state)])) (communication-script state))))) -(defn abcd [x] - (+ x 9)) +(def *registered-functions (atom {})) +(swap! *registered-functions assoc :add (fn [a b] (+ a b))) + +(defn compute + [input] + (let [{:keys [func args]} input] + (apply (func @*registered-functions) args))) (defn routes "Web server routes." @@ -142,7 +147,7 @@ transit/read read-string) _ (prn [:input input]) - output (abcd input)] + output (compute input)] (prn [:output output]) {:body (pr-str output) :status 200}) From 785ad24ec5979df46fa59a6c6ac945488651e7fe Mon Sep 17 00:00:00 2001 From: Ram Narayan Kappagantula <57584367+SpacePirate27@users.noreply.github.com> Date: Mon, 30 Dec 2024 10:29:37 +0530 Subject: [PATCH 03/12] add table to calculate click and open rate --- notebooks/dummy.clj | 86 +++++++++++++++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 27 deletions(-) diff --git a/notebooks/dummy.clj b/notebooks/dummy.clj index ac89eb8..e92131e 100644 --- a/notebooks/dummy.clj +++ b/notebooks/dummy.clj @@ -1,41 +1,73 @@ (ns dummy - (:require [scicloj.kindly.v4.kind :as kind])) - - + (:require [scicloj.kindly.v4.kind :as kind] + [scicloj.kindly.v4.kind :as kind] + [scicloj.kindly.v4.kind :as kind])) ;; Interpreted by the Scittle interpreter ;; in the browser. (kind/reagent - '(def *a1 (reagent.core/atom 10))) + '(def *a1 (reagent.core/atom 10))) ;; Just JVM Clojure code. (def *a2 (atom 0)) +(kind/reagent + '(def *rates (reagent.core/atom {}))) + +(kind/reagent + '(defn compute [input callback] + (ajax.core/POST + "/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 reset: " e)))}))) + +(kind/reagent + ['(fn [] + [:div + [:p @*a1] + [:input {:type "button" :value "Click me!" + :on-click (fn [] + (compute + {:func :add + :args [@*a1 20]} + (fn [response] + (reset! *a1 response))))}]])]) +;; New example of a function that will calculate the open and click rates of the email data below (kind/reagent - '(defn compute [input callback] - (ajax.core/POST - "/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 reset: " e)))}))) + '(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 [] - [:div - [:p @*a1] - [:input {:type "button" :value "Click me!" - :on-click (fn [] - (compute - {:func :add - :args [@*a1 20]} - (fn [response] - (reset! *a1 response))))}]])]) + ['(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 [] + (compute + {:func :calc-click-and-open-rate + :args [email-data]} + (fn [response] + (reset! *rates response))))}]])]) From c896c49495f04d6ba3510c09aaeb5a069aee20b2 Mon Sep 17 00:00:00 2001 From: Ram Narayan Kappagantula <57584367+SpacePirate27@users.noreply.github.com> Date: Mon, 30 Dec 2024 10:31:03 +0530 Subject: [PATCH 04/12] register new function to calculate open and click rates --- src/scicloj/clay/v2/server.clj | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/scicloj/clay/v2/server.clj b/src/scicloj/clay/v2/server.clj index e876400..897993e 100644 --- a/src/scicloj/clay/v2/server.clj +++ b/src/scicloj/clay/v2/server.clj @@ -117,6 +117,16 @@ (swap! *registered-functions assoc :add (fn [a b] (+ a b))) +(swap! *registered-functions assoc :calc-click-and-open-rate + (fn [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}))) + (defn compute [input] (let [{:keys [func args]} input] From 7cc1f77d42c0b814d631bea323a59718d6a815bc Mon Sep 17 00:00:00 2001 From: Ram Narayan Kappagantula <57584367+SpacePirate27@users.noreply.github.com> Date: Tue, 31 Dec 2024 12:14:39 +0530 Subject: [PATCH 05/12] Add heading --- notebooks/dummy.clj | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/notebooks/dummy.clj b/notebooks/dummy.clj index e92131e..c20505a 100644 --- a/notebooks/dummy.clj +++ b/notebooks/dummy.clj @@ -41,7 +41,21 @@ (fn [response] (reset! *a1 response))))}]])]) -;; New example of a function that will calculate the open and click rates of the email data below +(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"] From 7fe7b876df04e87e7accfb1ce0bca734a4dc0977 Mon Sep 17 00:00:00 2001 From: daslu Date: Sun, 12 Jan 2025 14:29:54 +0200 Subject: [PATCH 06/12] experimental `compute` - marking safe functions with `:kindly/servable` --- notebooks/dummy.clj | 62 ++++++++++++-------- src/scicloj/clay/v2/server.clj | 104 +++++++++++++++------------------ 2 files changed, 87 insertions(+), 79 deletions(-) diff --git a/notebooks/dummy.clj b/notebooks/dummy.clj index c20505a..e0330f9 100644 --- a/notebooks/dummy.clj +++ b/notebooks/dummy.clj @@ -3,10 +3,24 @@ [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})) + ;; Interpreted by the Scittle interpreter ;; in the browser. (kind/reagent - '(def *a1 (reagent.core/atom 10))) + '(def *a1 (reagent.core/atom 10))) ;; Just JVM Clojure code. (def *a2 (atom 0)) @@ -30,19 +44,20 @@ (str "error on reset: " e)))}))) (kind/reagent - ['(fn [] - [:div - [:p @*a1] - [:input {:type "button" :value "Click me!" - :on-click (fn [] - (compute - {:func :add - :args [@*a1 20]} - (fn [response] - (reset! *a1 response))))}]])]) + ['(fn [] + [:div + [:p @*a1] + [:input {:type "button" :value "Click me!" + :on-click (fn [] + (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") + "### 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"] @@ -74,14 +89,15 @@ :row-vectors email-data}) (kind/reagent - ['(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 [] - (compute - {:func :calc-click-and-open-rate - :args [email-data]} - (fn [response] - (reset! *rates response))))}]])]) + ['(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 [] + (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 897993e..6f3a888 100644 --- a/src/scicloj/clay/v2/server.clj +++ b/src/scicloj/clay/v2/server.clj @@ -107,30 +107,22 @@ :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))))) -(def *registered-functions (atom {})) - -(swap! *registered-functions assoc :add (fn [a b] (+ a b))) - -(swap! *registered-functions assoc :calc-click-and-open-rate - (fn [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}))) - (defn compute [input] (let [{:keys [func args]} input] - (apply (func @*registered-functions) args))) + (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." @@ -141,47 +133,47 @@ (httpkit/as-channel req {:on-open (fn [ch] (swap! *clients conj ch)) :on-close (fn [ch _reason] (swap! *clients disj ch)) :on-receive (fn [_ch msg])}) - (case [request-method uri] - [:get "/"] {:body (-> state - page - (wrap-html state)) - :headers {"Content-Type" "text/html"} - :status 200} - [:get "/counter"] {:body (-> state - :counter - str) - :status 200} - - [:post "/compute"] (let [input (-> body - (transit/reader :json) - transit/read - read-string) - _ (prn [:input input]) - output (compute input)] - (prn [:output output]) - {:body (pr-str output) - :status 200}) + (case [request-method uri] + [:get "/"] {:body (-> state + page + (wrap-html state)) + :headers {"Content-Type" "text/html"} + :status 200} + [:get "/counter"] {:body (-> state + :counter + str) + :status 200} + + [:post "/compute"] (let [input (-> body + (transit/reader :json) + transit/read + read-string) + _ (prn [:input input]) + output (compute input)] + (prn [:output output]) + {:body (pr-str output) + :status 200}) ;; else - (let [f (io/file (str (:base-target-path state) uri))] - (if (.exists f) - {:body (if (re-matches #".*\.html$" uri) - (-> f - slurp - (wrap-html state)) - f) - :headers (when (str/ends-with? uri ".js") - {"Content-Type" "text/javascript"}) - :status 200} - (case [request-method uri] + (let [f (io/file (str (:base-target-path state) uri))] + (if (.exists f) + {:body (if (re-matches #".*\.html$" uri) + (-> f + slurp + (wrap-html state)) + f) + :headers (when (str/ends-with? uri ".js") + {"Content-Type" "text/javascript"}) + :status 200} + (case [request-method uri] ;; user files have priority, otherwise serve the default from resources - [:get "/favicon.ico"] {:body (io/input-stream (io/resource "favicon.ico")) - :status 200} + [:get "/favicon.ico"] {:body (io/input-stream (io/resource "favicon.ico")) + :status 200} ;; this image is for the header above the page during interactive mode - [:get "/Clay.svg.png"] {:body (io/input-stream (io/resource "Clay.svg.png")) - :status 200} - {:body "not found" - :status 404}))))))) + [:get "/Clay.svg.png"] {:body (io/input-stream (io/resource "Clay.svg.png")) + :status 200} + {:body "not found" + :status 404}))))))) (defonce *stop-server! (atom nil)) From d8c5f760755224b468f477bd45d4615c88462f48 Mon Sep 17 00:00:00 2001 From: daslu Date: Sat, 18 Jan 2025 23:04:09 +0200 Subject: [PATCH 07/12] CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd3c062..90c00f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # 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 +- new `snippets` API namespace for easier editor inntegration + ## [2-beta27] - 2024-12-24 - added a `:browse` option (default `true`) to determine whether to open a browser tab when the server is started From 0793f2fc9a6dfd207e140cc87d72515ae87023b4 Mon Sep 17 00:00:00 2001 From: daslu Date: Sat, 18 Jan 2025 23:13:31 +0200 Subject: [PATCH 08/12] compute API - renamed to kindly-compute --- src/scicloj/clay/v2/server.clj | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/scicloj/clay/v2/server.clj b/src/scicloj/clay/v2/server.clj index 62dd30f..47c6e17 100644 --- a/src/scicloj/clay/v2/server.clj +++ b/src/scicloj/clay/v2/server.clj @@ -143,17 +143,13 @@ :counter str) :status 200} - - [:post "/compute"] (let [input (-> body - (transit/reader :json) - transit/read - read-string) - _ (prn [:input input]) - output (compute input)] - (prn [:output output]) - {:body (pr-str output) - :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) From d045369716d84e5c6c2943ef8d2f33a1a951587a Mon Sep 17 00:00:00 2001 From: daslu Date: Sat, 18 Jan 2025 23:13:41 +0200 Subject: [PATCH 09/12] compute API - example code cleanup and adaptations --- notebooks/dummy.clj | 58 ++++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/notebooks/dummy.clj b/notebooks/dummy.clj index e0330f9..7c97633 100644 --- a/notebooks/dummy.clj +++ b/notebooks/dummy.clj @@ -17,43 +17,37 @@ {:open-rate open-rate :click-rate click-rate})) -;; Interpreted by the Scittle interpreter -;; in the browser. (kind/reagent - '(def *a1 (reagent.core/atom 10))) - -;; Just JVM Clojure code. -(def *a2 (atom 0)) - -(kind/reagent - '(def *rates (reagent.core/atom {}))) + '(def *rates (reagent.core/atom {}))) (kind/reagent - '(defn compute [input callback] - (ajax.core/POST - "/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 reset: " e)))}))) + '(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 reset: " e)))}))) (kind/reagent ['(fn [] - [:div - [:p @*a1] - [:input {:type "button" :value "Click me!" - :on-click (fn [] - (compute - {:func 'dummy/add - :args [@*a1 20]} - (fn [response] - (reset! *a1 response))))}]])]) + (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 @@ -95,7 +89,7 @@ [:p (str "Click rate " (:click-rate @*rates))] [:input {:type "button" :value "Click to calculate click and open rate" :on-click (fn [] - (compute + (kindly-compute {:func 'dummy/calc-click-and-open-rate :args [email-data]} (fn [response] From 84ec7d2eb9969e201d2e95e9f18274369ba6bc09 Mon Sep 17 00:00:00 2001 From: daslu Date: Sat, 18 Jan 2025 23:14:24 +0200 Subject: [PATCH 10/12] renamed compute API examples --- notebooks/dummy.clj | 97 --------------------------------------------- 1 file changed, 97 deletions(-) delete mode 100644 notebooks/dummy.clj diff --git a/notebooks/dummy.clj b/notebooks/dummy.clj deleted file mode 100644 index 7c97633..0000000 --- a/notebooks/dummy.clj +++ /dev/null @@ -1,97 +0,0 @@ -(ns dummy - (: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 - '(def *rates (reagent.core/atom {}))) - -(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 reset: " 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 [] - [: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))))}]])]) - From 3bba65dddb21ad0d2a168ac266ebd979034afe47 Mon Sep 17 00:00:00 2001 From: daslu Date: Sat, 18 Jan 2025 23:17:37 +0200 Subject: [PATCH 11/12] renamed and cleaned example code --- notebooks/compute_examples.clj | 96 ++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 notebooks/compute_examples.clj 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))))}]])))]) + From b360f0d13b888925ca70d3fb62734d3b21ad251e Mon Sep 17 00:00:00 2001 From: daslu Date: Sat, 18 Jan 2025 23:19:22 +0200 Subject: [PATCH 12/12] CHANGELOG --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90c00f8..f345f41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,8 @@ 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 -- new `snippets` API namespace for easier editor inntegration +- 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