Merge remote-tracking branch 'origin/staging' into develop
Some checks failed
_DEVELOP / build-bundle (push) Has been cancelled
_DEVELOP / build-docker (push) Has been cancelled
_STAGING / build-bundle (push) Has been cancelled
_STAGING / build-docker (push) Has been cancelled
Commit Message Check / Check Commit Message (push) Has been cancelled

This commit is contained in:
Andrey Antukh
2025-11-07 10:43:27 +01:00
9 changed files with 151 additions and 76 deletions

View File

@@ -102,6 +102,7 @@
- Fix options button does not work for comments created in the lower part of the screen [Taiga #12422](https://tree.taiga.io/project/penpot/issue/12422) - Fix options button does not work for comments created in the lower part of the screen [Taiga #12422](https://tree.taiga.io/project/penpot/issue/12422)
- Fix problem when checking usage with removed teams [Taiga #12442](https://tree.taiga.io/project/penpot/issue/12442) - Fix problem when checking usage with removed teams [Taiga #12442](https://tree.taiga.io/project/penpot/issue/12442)
- Fix focus mode persisting across page/file navigation [Taiga #12469](https://tree.taiga.io/project/penpot/issue/12469) - Fix focus mode persisting across page/file navigation [Taiga #12469](https://tree.taiga.io/project/penpot/issue/12469)
- Fix shadow color validation [Github #7705](https://github.com/penpot/penpot/pull/7705)
## 2.10.1 ## 2.10.1

View File

@@ -218,6 +218,9 @@
(when (or (nil? revn) (= revn (:revn file))) (when (or (nil? revn) (= revn (:revn file)))
file))) file)))
;; FIXME: we should skip files that does not match the revn on the
;; props and add proper schema for this task props
(defn- process-file! (defn- process-file!
[cfg {:keys [file-id] :as props}] [cfg {:keys [file-id] :as props}]
(if-let [file (get-file cfg props)] (if-let [file (get-file cfg props)]

View File

@@ -8,6 +8,7 @@
"A maintenance task that is responsible of properly scheduling the "A maintenance task that is responsible of properly scheduling the
file-gc task for all files that matches the eligibility threshold." file-gc task for all files that matches the eligibility threshold."
(:require (:require
[app.common.logging :as l]
[app.common.time :as ct] [app.common.time :as ct]
[app.config :as cf] [app.config :as cf]
[app.db :as db] [app.db :as db]
@@ -21,25 +22,24 @@
f.modified_at f.modified_at
FROM file AS f FROM file AS f
WHERE f.has_media_trimmed IS false WHERE f.has_media_trimmed IS false
AND f.modified_at < now() - ?::interval AND f.modified_at < ?
AND f.deleted_at IS NULL AND f.deleted_at IS NULL
ORDER BY f.modified_at DESC ORDER BY f.modified_at DESC
FOR UPDATE OF f FOR UPDATE OF f
SKIP LOCKED") SKIP LOCKED")
(defn- get-candidates
[{:keys [::db/conn ::min-age] :as cfg}]
(let [min-age (db/interval min-age)]
(db/plan conn [sql:get-candidates min-age] {:fetch-size 10})))
(defn- schedule! (defn- schedule!
[cfg] [{:keys [::db/conn] :as cfg} threshold]
(let [total (reduce (fn [total {:keys [id modified-at revn]}] (let [total (reduce (fn [total {:keys [id modified-at revn]}]
(let [params {:file-id id :modified-at modified-at :revn revn}] (let [params {:file-id id :revn revn}]
(l/trc :hint "schedule"
:file-id (str id)
:revn revn
:modified-at (ct/format-inst modified-at))
(wrk/submit! (assoc cfg ::wrk/params params)) (wrk/submit! (assoc cfg ::wrk/params params))
(inc total))) (inc total)))
0 0
(get-candidates cfg))] (db/plan conn [sql:get-candidates threshold] {:fetch-size 10}))]
{:processed total})) {:processed total}))
(defmethod ig/assert-key ::handler (defmethod ig/assert-key ::handler
@@ -53,12 +53,12 @@
(defmethod ig/init-key ::handler (defmethod ig/init-key ::handler
[_ cfg] [_ cfg]
(fn [{:keys [props] :as task}] (fn [{:keys [props] :as task}]
(let [min-age (ct/duration (or (:min-age props) (::min-age cfg)))] (let [threshold (-> (ct/duration (or (:min-age props) (::min-age cfg)))
(ct/in-past))]
(-> cfg (-> cfg
(assoc ::db/rollback (:rollback? props)) (assoc ::db/rollback (:rollback? props))
(assoc ::min-age min-age)
(assoc ::wrk/task :file-gc) (assoc ::wrk/task :file-gc)
(assoc ::wrk/priority 10) (assoc ::wrk/priority 10)
(assoc ::wrk/mark-retries 0) (assoc ::wrk/mark-retries 0)
(assoc ::wrk/delay 1000) (assoc ::wrk/delay 10000)
(db/tx-run! schedule!))))) (db/tx-run! schedule! threshold)))))

View File

@@ -77,8 +77,8 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def ^:private sql:insert-new-task (def ^:private sql:insert-new-task
"insert into task (id, name, props, queue, label, priority, max_retries, scheduled_at) "insert into task (id, name, props, queue, label, priority, max_retries, created_at, modified_at, scheduled_at)
values (?, ?, ?, ?, ?, ?, ?, now() + ?) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
returning id") returning id")
(def ^:private (def ^:private
@@ -88,7 +88,7 @@
AND queue=? AND queue=?
AND label=? AND label=?
AND status = 'new' AND status = 'new'
AND scheduled_at > now()") AND scheduled_at > ?")
(def ^:private schema:options (def ^:private schema:options
[:map {:title "submit-options"} [:map {:title "submit-options"}
@@ -111,17 +111,19 @@
(check-options! options) (check-options! options)
(let [duration (ct/duration delay) (let [delay (ct/duration delay)
interval (db/interval duration) now (ct/now)
props (db/tjson params) scheduled-at (-> (ct/plus now delay)
id (uuid/next) (ct/truncate :millisecond))
tenant (cf/get :tenant) props (db/tjson params)
task (d/name task) id (uuid/next)
queue (str/ffmt "%:%" tenant (d/name queue)) tenant (cf/get :tenant)
conn (db/get-connectable options) task (d/name task)
deleted (when dedupe queue (str/ffmt "%:%" tenant (d/name queue))
(-> (db/exec-one! conn [sql:remove-not-started-tasks task queue label]) conn (db/get-connectable options)
:next.jdbc/update-count))] deleted (when dedupe
(-> (db/exec-one! conn [sql:remove-not-started-tasks task queue label now])
(db/get-update-count)))]
(l/trc :hint "submit task" (l/trc :hint "submit task"
:name task :name task
@@ -129,11 +131,13 @@
:queue queue :queue queue
:label label :label label
:dedupe (boolean dedupe) :dedupe (boolean dedupe)
:delay (ct/format-duration duration) :delay (ct/format-duration delay)
:replace (or deleted 0)) :replace (or deleted 0))
(db/exec-one! conn [sql:insert-new-task id task props queue (db/exec-one! conn [sql:insert-new-task id task props queue
label priority max-retries interval]) label priority max-retries
now now scheduled-at])
id)) id))
(defn invoke! (defn invoke!

View File

@@ -158,7 +158,9 @@
(inst-ms (:scheduled-at task))) (inst-ms (:scheduled-at task)))
(l/wrn :hint "skiping task, rescheduled" (l/wrn :hint "skiping task, rescheduled"
:task-id task-id :task-id task-id
:runner-id id) :runner-id id
:scheduled-at (ct/format-inst (:scheduled-at task))
:expected-scheduled-at (ct/format-inst scheduled-at))
:else :else
(let [result (run-task cfg task)] (let [result (run-task cfg task)]
@@ -179,7 +181,8 @@
{:error explain {:error explain
:status "retry" :status "retry"
:modified-at now :modified-at now
:scheduled-at (ct/plus now delay) :scheduled-at (-> (ct/plus now delay)
(ct/truncate :millisecond))
:retry-num nretry} :retry-num nretry}
{:id (:id task)}) {:id (:id task)})
nil)) nil))

View File

@@ -549,6 +549,44 @@
(io/copy r sw) (io/copy r sw)
(.toString sw)))) (.toString sw))))
(defn parse-sse
[content]
(let [state
(reduce (fn [{:keys [events data event id] :as state} line]
(cond
;; empty line → dispatch event if we have data
(str/blank? line)
(if (seq data)
(-> state
(update :events conj {:event (or event "message")
:data (-> (str/join "\n" data))})
(assoc :data [] :event nil))
state)
;; comment line (starts with :)
(str/starts-with? line ":")
state
:else
(let [[field raw-value] (str/split line #":" 2)
value (some-> raw-value (str/replace #"^ " ""))]
(case field
"data" (update state :data conj (or value ""))
"event" (assoc state :event value)
;; ignore retry and unknown fields
state))))
{:events [] :data [] :event nil}
(str/split content #"\r?\n"))
;; handle unterminated last event (no trailing blank line)
state (if (seq (:data state))
(update state :events conj
{:event (or (:event state) "message")
:data (str/join "\n" (:data state))})
state)]
(:events state)))
(defn consume-sse (defn consume-sse
[callback] [callback]
(let [{:keys [::yres/status ::yres/body ::yres/headers] :as response} (callback {}) (let [{:keys [::yres/status ::yres/body ::yres/headers] :as response} (callback {})
@@ -558,12 +596,9 @@
(try (try
(px/exec! :virtual #(rcp/write-body-to-stream body nil output)) (px/exec! :virtual #(rcp/write-body-to-stream body nil output))
(into [] (into []
(map (fn [event] (map (fn [{:keys [event data]}]
(let [[item1 item2] (re-seq #"(.*): (.*)\n?" event)] [(keyword event)
(tr/decode-str data)]))
[(keyword (nth item1 2)) (parse-sse (slurp' input)))
(tr/decode-str (nth item2 2))])))
(-> (slurp' input)
(str/split "\n\n")))
(finally (finally
(.close input))))) (.close input)))))

View File

@@ -1357,38 +1357,6 @@
(update :pages-index d/update-vals update-container) (update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container)))) (d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "0004-clean-shadow-color"
[data _]
(let [decode-color (sm/decoder types.color/schema:color sm/json-transformer)
clean-shadow-color
(fn [color]
(let [ref-id (get color :id)
ref-file (get color :file-id)]
(-> (d/without-qualified color)
(select-keys [:opacity :color :gradient :image :ref-id :ref-file])
(cond-> ref-id
(assoc :ref-id ref-id))
(cond-> ref-file
(assoc :ref-file ref-file))
(decode-color))))
clean-shadow
(fn [shadow]
(update shadow :color clean-shadow-color))
update-object
(fn [object]
(d/update-when object :shadow #(mapv clean-shadow %)))
update-container
(fn [container]
(d/update-when container :objects d/update-vals update-object))]
(-> data
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "0005-deprecate-image-type" (defmethod migrate-data "0005-deprecate-image-type"
[data _] [data _]
(letfn [(update-object [object] (letfn [(update-object [object]
@@ -1697,6 +1665,45 @@
(update :pages-index d/update-vals update-container) (update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container)))) (d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "0015-clean-shadow-color"
[data _]
(let [decode-shadow-color
(sm/decoder ctss/schema:color sm/json-transformer)
clean-shadow-color
(fn [color]
(let [ref-id (get color :id)
ref-file (get color :file-id)]
(-> (d/without-qualified color)
(select-keys ctss/color-attrs)
(cond-> ref-id
(assoc :ref-id ref-id))
(cond-> ref-file
(assoc :ref-file ref-file))
(decode-shadow-color)
(d/without-nils))))
clean-shadow
(fn [shadow]
(update shadow :color clean-shadow-color))
clean-xform
(comp
(keep clean-shadow)
(filter ctss/valid-shadow?))
update-object
(fn [object]
(d/update-when object :shadow #(into [] clean-xform %)))
update-container
(fn [container]
(d/update-when container :objects d/update-vals update-object))]
(-> data
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
;; Copy fills from position-data to text nodes when all text nodes lack fills, ;; Copy fills from position-data to text nodes when all text nodes lack fills,
;; all position-data have fills, and the counts match ;; all position-data have fills, and the counts match
(defmethod migrate-data "0016-copy-fills-from-position-data-to-text-node" (defmethod migrate-data "0016-copy-fills-from-position-data-to-text-node"
@@ -1818,7 +1825,6 @@
"0002-clean-shape-interactions" "0002-clean-shape-interactions"
"0003-fix-root-shape" "0003-fix-root-shape"
"0003-convert-path-content-v2" "0003-convert-path-content-v2"
"0004-clean-shadow-color"
"0005-deprecate-image-type" "0005-deprecate-image-type"
"0006-fix-old-texts-fills" "0006-fix-old-texts-fills"
"0008-fix-library-colors-v4" "0008-fix-library-colors-v4"
@@ -1832,4 +1838,5 @@
"0014-fix-tokens-lib-duplicate-ids" "0014-fix-tokens-lib-duplicate-ids"
"0014-clear-components-nil-objects" "0014-clear-components-nil-objects"
"0015-fix-text-attrs-blank-strings" "0015-fix-text-attrs-blank-strings"
"0015-clean-shadow-color"
"0016-copy-fills-from-position-data-to-text-node"])) "0016-copy-fills-from-position-data-to-text-node"]))

View File

@@ -36,7 +36,7 @@
(defn type (defn type
[s] [s]
(m/-type s)) (m/type s default-options))
(defn properties (defn properties
[s] [s]
@@ -46,6 +46,10 @@
[s] [s]
(m/type-properties s)) (m/type-properties s))
(defn children
[s]
(m/children s default-options))
(defn schema (defn schema
[s] [s]
(if (schema? s) (if (schema? s)
@@ -127,9 +131,19 @@
(defn keys (defn keys
"Given a map schema, return all keys as set" "Given a map schema, return all keys as set"
[schema] [schema']
(->> (entries schema) (let [schema' (m/schema schema' default-options)]
(into #{} xf:map-key))) (case (m/type schema')
:map
(->> (entries schema')
(into #{} xf:map-key))
:merge
(->> (m/children schema')
(mapcat m/entries)
(into #{} xf:map-key))
(throw (ex-info "not supported schema type" {:type (m/type schema')})))))
(defn update-properties (defn update-properties
[s f & args] [s f & args]

View File

@@ -11,6 +11,14 @@
(def styles #{:drop-shadow :inner-shadow}) (def styles #{:drop-shadow :inner-shadow})
(def schema:color
[:merge {:title "ShadowColor"}
ctc/schema:color-attrs
ctc/schema:plain-color])
(def color-attrs
(sm/keys schema:color))
(def schema:shadow (def schema:shadow
[:map {:title "Shadow"} [:map {:title "Shadow"}
[:id [:maybe ::sm/uuid]] [:id [:maybe ::sm/uuid]]
@@ -20,7 +28,7 @@
[:blur ::sm/safe-number] [:blur ::sm/safe-number]
[:spread ::sm/safe-number] [:spread ::sm/safe-number]
[:hidden :boolean] [:hidden :boolean]
[:color ctc/schema:color]]) [:color schema:color]])
(def check-shadow (def check-shadow
(sm/check-fn schema:shadow)) (sm/check-fn schema:shadow))