diff --git a/backend/src/app/http/websocket.clj b/backend/src/app/http/websocket.clj index 3351566ca..bb29839a1 100644 --- a/backend/src/app/http/websocket.clj +++ b/backend/src/app/http/websocket.clj @@ -11,6 +11,7 @@ [app.common.logging :as l] [app.common.pprint :as pp] [app.common.spec :as us] + [app.common.uuid :as uuid] [app.db :as db] [app.http.session :as session] [app.metrics :as mtx] @@ -99,7 +100,10 @@ (sp/pipe ch output-ch false) ;; Subscribe to the profile topic on msgbus/redis - (mbus/sub! msgbus :topic profile-id :chan ch))) + (mbus/sub! msgbus :topic profile-id :chan ch) + + ;; Subscribe to the system topic on msgbus/redis + (mbus/sub! msgbus :topic (str uuid/zero) :chan ch))) (defmethod handle-message :close [{:keys [::mbus/msgbus]} {:keys [::ws/id ::ws/state ::profile-id ::session-id]} _] diff --git a/backend/src/app/srepl/main.clj b/backend/src/app/srepl/main.clj index 77413995b..4977829c4 100644 --- a/backend/src/app/srepl/main.clj +++ b/backend/src/app/srepl/main.clj @@ -8,10 +8,15 @@ "A collection of adhoc fixes scripts." #_:clj-kondo/ignore (:require + [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.logging :as l] [app.common.pprint :as p] [app.common.spec :as us] + [app.common.uuid :as uuid] + [app.config :as cf] [app.db :as db] + [app.msgbus :as mbus] [app.rpc.commands.auth :as auth] [app.rpc.commands.profile :as profile] [app.srepl.fixes :as f] @@ -164,3 +169,106 @@ (alter-var-root var (fn [f] (or (::original (meta f)) f)))) +(defn notify! + [{:keys [::mbus/msgbus ::db/pool]} & {:keys [dest code message level] + :or {code :generic level :info} + :as params}] + (dm/verify! + ["invalid level %" level] + (contains? #{:success :error :info :warning} level)) + + (dm/verify! + ["invalid code: %" code] + (contains? #{:generic :upgrade-version} code)) + + (letfn [(send [dest] + (l/inf :hint "sending notification" :dest (str dest)) + (let [message {:type :notification + :code code + :level level + :version (:full cf/version) + :subs-id dest + :message message} + message (->> (dissoc params :dest :code :message :level) + (merge message))] + (mbus/pub! msgbus + :topic (str dest) + :message message))) + + (resolve-profile [email] + (some-> (db/get* pool :profile {:email (str/lower email)} {:columns [:id]}) :id vector)) + + (resolve-team [team-id] + (->> (db/query pool :team-profile-rel + {:team-id team-id} + {:columns [:profile-id]}) + (map :profile-id))) + + (parse-uuid [v] + (if (uuid? v) + v + (d/parse-uuid v))) + + (resolve-dest [dest] + (cond + (uuid? dest) + [dest] + + (string? dest) + (some-> dest parse-uuid resolve-dest) + + (nil? dest) + (resolve-dest uuid/zero) + + (map? dest) + (sequence (comp + (map vec) + (mapcat resolve-dest)) + dest) + + (and (coll? dest) + (every? coll? dest)) + (sequence (comp + (map vec) + (mapcat resolve-dest)) + dest) + + (vector? dest) + (let [[op param] dest] + (cond + (= op :email) + (cond + (and (coll? param) + (every? string? param)) + (sequence (comp + (keep resolve-profile) + (mapcat identity)) + param) + + (string? param) + (resolve-profile param)) + + (= op :team-id) + (cond + (coll? param) + (sequence (comp + (mapcat resolve-team) + (keep parse-uuid)) + param) + + (uuid? param) + (resolve-team param) + + (string? param) + (some-> param parse-uuid resolve-team)) + + (= op :profile-id) + (if (coll? param) + (sequence (keep parse-uuid) param) + (resolve-dest param)))))) + ] + + (->> (resolve-dest dest) + (filter some?) + (into #{}) + (run! send)))) diff --git a/frontend/resources/styles/common/framework.scss b/frontend/resources/styles/common/framework.scss index 257ea2e31..660f29c4a 100644 --- a/frontend/resources/styles/common/framework.scss +++ b/frontend/resources/styles/common/framework.scss @@ -1157,6 +1157,7 @@ input[type="range"]:focus::-ms-fill-upper { .wrapper { display: flex; + align-items: center; .icon { padding: $size-2; @@ -1173,6 +1174,9 @@ input[type="range"]:focus::-ms-fill-upper { padding: $size-2; width: 100%; align-items: center; + + padding: 10px 15px; + min-height: 48px; } } diff --git a/frontend/src/app/main/data/common.cljs b/frontend/src/app/main/data/common.cljs index 684f2747c..b8106252a 100644 --- a/frontend/src/app/main/data/common.cljs +++ b/frontend/src/app/main/data/common.cljs @@ -7,7 +7,10 @@ (ns app.main.data.common "A general purpose events." (:require + [app.config :as cf] + [app.main.data.messages :as msg] [app.main.repo :as rp] + [app.util.i18n :refer [tr]] [beicon.core :as rx] [potok.core :as ptk])) @@ -43,3 +46,33 @@ (watch [_ _ _] (->> (rp/cmd! :delete-share-link {:id id}) (rx/ignore))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; NOTIFICATIONS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn force-reload! + [] + (.reload js/location)) + +(defn handle-notification + [{:keys [message code level] :as params}] + (ptk/reify ::show-notification + ptk/WatchEvent + (watch [_ _ _] + (case code + :upgrade-version + (when (or (not= (:version params) (:full cf/version)) + (true? (:force params))) + (rx/of (msg/dialog + :content (tr "notifications.by-code.upgrade-version") + :controls :inline-actions + :type level + :actions [{:label "Refresh" :callback force-reload!}] + :tag :notification))) + + (rx/of (msg/dialog + :content message + :controls :close + :type level + :tag :notification)))))) diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index 725e014a4..7d0e4cce8 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -13,10 +13,12 @@ [app.common.uri :as u] [app.common.uuid :as uuid] [app.config :as cf] + [app.main.data.common :refer [handle-notification]] [app.main.data.events :as ev] [app.main.data.fonts :as df] [app.main.data.media :as di] [app.main.data.users :as du] + [app.main.data.websocket :as dws] [app.main.features :as features] [app.main.repo :as rp] [app.util.dom :as dom] @@ -61,7 +63,23 @@ (ptk/watch (fetch-projects) state stream) (ptk/watch (fetch-team-members) state stream) (ptk/watch (du/fetch-teams) state stream) - (ptk/watch (du/fetch-users {:team-id id}) state stream))))) + (ptk/watch (du/fetch-users {:team-id id}) state stream) + + (let [stoper (rx/filter (ptk/type? ::finalize) stream) + profile-id (:profile-id state)] + (->> stream + (rx/filter (ptk/type? ::dws/message)) + (rx/map deref) + (rx/filter (fn [{:keys [subs-id type] :as msg}] + (and (or (= subs-id uuid/zero) + (= subs-id profile-id)) + (= :notification type)))) + (rx/map handle-notification) + (rx/take-until stoper))))))) + +(defn finalize + [params] + (ptk/data-event ::finalize params)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Data Fetching (context aware: current team) diff --git a/frontend/src/app/main/data/messages.cljs b/frontend/src/app/main/data/messages.cljs index bc1c7c7ad..8ace43228 100644 --- a/frontend/src/app/main/data/messages.cljs +++ b/frontend/src/app/main/data/messages.cljs @@ -120,6 +120,17 @@ :position :fixed :timeout timeout}))) +(defn dialog + [& {:keys [content controls actions position tag type] + :or {controls :none position :floating type :info}}] + (show (d/without-nils + {:content content + :type type + :position position + :controls controls + :actions actions + :tag tag}))) + (defn info-dialog ([content controls actions] (info-dialog content controls actions nil)) diff --git a/frontend/src/app/main/data/workspace/notifications.cljs b/frontend/src/app/main/data/workspace/notifications.cljs index 840d1aec9..e7ab37432 100644 --- a/frontend/src/app/main/data/workspace/notifications.cljs +++ b/frontend/src/app/main/data/workspace/notifications.cljs @@ -10,6 +10,8 @@ [app.common.data.macros :as dm] [app.common.pages.changes :as cpc] [app.common.schema :as sm] + [app.common.uuid :as uuid] + [app.main.data.common :refer [handle-notification]] [app.main.data.websocket :as dws] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.libraries :as dwl] @@ -57,8 +59,9 @@ (rx/filter (ptk/type? ::dws/message)) (rx/map deref) (rx/filter (fn [{:keys [subs-id] :as msg}] - (or (= subs-id team-id) + (or (= subs-id uuid/zero) (= subs-id profile-id) + (= subs-id team-id) (= subs-id file-id)))) (rx/map process-message)) @@ -96,6 +99,7 @@ :pointer-update (handle-pointer-update msg) :file-change (handle-file-change msg) :library-change (handle-library-change msg) + :notification (handle-notification msg) nil)) (defn- handle-pointer-send diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 13b41ee30..27fc40870 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -4960,3 +4960,7 @@ msgstr "Marketing" #: src/app/main/ui/onboarding/questions.cljs msgid "questions.student-teacher" msgstr "Student or teacher" + +#: src/app/main/data/common.cljs +msgid "notifications.by-code.upgrade-version" +msgstr "A new version is available, please refresh the page"