From de85fd8ba695ef8ba62e9a4b7ae46a66f2408ad3 Mon Sep 17 00:00:00 2001 From: John Wregglesworth Date: Thu, 14 May 2026 13:56:09 -0700 Subject: [PATCH 1/3] Build interactive analysis URLs from the per-operator base URL VICE analyses can run on different clusters, each fronted by its own operator with its own landing domain. The job base queries now left-join operators to carry operator_base_url on every job, and interactive-urls builds the URL from it, falling back to the static interactive-apps base URL for jobs with no operator_id. send-interactive-job-status-update reuses the formatted job's :interactive_urls instead of rebuilding the URL, so notification/email links match the listing. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/apps/clients/notifications.clj | 13 ++++++++----- src/apps/persistence/jobs.clj | 17 ++++++++++++----- src/apps/service/apps/job_listings.clj | 11 ++++++++--- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/apps/clients/notifications.clj b/src/apps/clients/notifications.clj index 80373614..e67cb472 100644 --- a/src/apps/clients/notifications.clj +++ b/src/apps/clients/notifications.clj @@ -106,11 +106,14 @@ (send-job-status-update username email-address job-info))) (defn send-interactive-job-status-update - "Sends notification of an interactive job status update to the user." - ([username email-address {status :status user-id :user_id :as job-info} {external-id :external_id}] - (if-not (jp/completed? status) - (let [access-url (str (interapps-url (curl/url (config/interapps-base)) user-id external-id))] - (send-job-status-update username email-address (assoc job-info :access_url access-url))) + "Sends notification of an interactive job status update to the user. The + access URL is taken from the already-formatted job's :interactive_urls, + which point at the operator/cluster the analysis was launched on; this + avoids rebuilding the URL from the static interactive-apps base." + ([username email-address {status :status :as job-info} _job-step-info] + (if-let [access-url (and (not (jp/completed? status)) + (first (:interactive_urls job-info)))] + (send-job-status-update username email-address (assoc job-info :access_url access-url)) (send-job-status-update username email-address job-info))) ([{username :shortUsername email-address :email} job-info job-step-info] (send-interactive-job-status-update username email-address job-info job-step-info))) diff --git a/src/apps/persistence/jobs.clj b/src/apps/persistence/jobs.clj index a52e1c36..775c0e33 100644 --- a/src/apps/persistence/jobs.clj +++ b/src/apps/persistence/jobs.clj @@ -335,9 +335,12 @@ (cxu/bad-request (str "unrecognized sort field: " (name field))))) (defn- job-base-query - "The base query used for retrieving job information from the database." + "The base query used for retrieving job information from the database. The + operators table is left-joined so each job carries the base URL of the + operator it was launched on (nil for jobs with no operator_id)." [] (-> (sql/select* [:job_listings :j]) + (sql/join :left [:operators :o] {:o.id :j.operator_id}) (sql/fields :j.app_description :j.system_id :j.app_id @@ -356,7 +359,8 @@ :j.job_type :j.parent_id :j.is_batch - :j.notify))) + :j.notify + [:o.base_url :operator_base_url]))) (defn- hsql-job-base-query "The HoneySQL version of the base query used for retrieving job information from the database." @@ -379,8 +383,10 @@ :j.job_type :j.parent_id :j.is_batch - :j.notify) - (h/from [:job_listings :j]))) + :j.notify + [:o.base_url :operator_base_url]) + (h/from [:job_listings :j]) + (h/left-join [:operators :o] [:= :o.id :j.operator_id]))) (defn- job-step-base-query "The base query used for retrieving job step information from the database." @@ -474,7 +480,8 @@ :j.job_type :j.parent_id :j.is_batch - :j.notify) + :j.notify + :o.base_url) (sql/where (exists (sql/subselect :job_steps (sql/where {:job_id :j.id :external_id [:in external-ids]})))) (sql/select))) diff --git a/src/apps/service/apps/job_listings.clj b/src/apps/service/apps/job_listings.clj index 681a1ebc..dd94923f 100644 --- a/src/apps/service/apps/job_listings.clj +++ b/src/apps/service/apps/job_listings.clj @@ -66,9 +66,14 @@ :batch_status (when (:is_batch job) (format-batch-status id))})) (defn- interactive-urls - [{user-id :user_id :as job} rep-steps] - (let [interactive? (fn [step] (= (:job_type step) jp/interactive-job-type)) - get-url (fn [step] (str (interapps-url (url (config/interapps-base)) user-id (:external_id step))))] + [{user-id :user_id operator-base-url :operator_base_url :as job} rep-steps] + ;; Use the base URL of the operator the job was launched on so the URL + ;; points at the cluster actually running the analysis. Jobs with no + ;; operator_id (legacy / pre-operator launches) fall back to the static + ;; interactive-apps base URL. + (let [base (url (or operator-base-url (config/interapps-base))) + interactive? (fn [step] (= (:job_type step) jp/interactive-job-type)) + get-url (fn [step] (str (interapps-url base user-id (:external_id step))))] (when-not (:is_batch job) (seq (map get-url (filter interactive? (rep-steps (:id job)))))))) From 1b42e39ceb66fa2e1f0be1569713139af2668467 Mon Sep 17 00:00:00 2001 From: John Wregglesworth Date: Thu, 14 May 2026 14:23:56 -0700 Subject: [PATCH 2/3] Address review feedback on per-operator URL change Updates the hsql-job-base-query docstring to mention the operators left-join (matching job-base-query), and rewrites the send-interactive-job-status-update doc/comment to lead with intent and spell out when no access URL is attached. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/apps/clients/notifications.clj | 12 ++++++++---- src/apps/persistence/jobs.clj | 4 +++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/apps/clients/notifications.clj b/src/apps/clients/notifications.clj index e67cb472..b53a59fc 100644 --- a/src/apps/clients/notifications.clj +++ b/src/apps/clients/notifications.clj @@ -106,11 +106,15 @@ (send-job-status-update username email-address job-info))) (defn send-interactive-job-status-update - "Sends notification of an interactive job status update to the user. The - access URL is taken from the already-formatted job's :interactive_urls, - which point at the operator/cluster the analysis was launched on; this - avoids rebuilding the URL from the static interactive-apps base." + "Sends notification of an interactive job status update to the user, attaching + the analysis access URL so the cluster running it is reachable from the + notification. job-info is the formatted job, whose :interactive_urls were + already built against the correct per-operator base URL." ([username email-address {status :status :as job-info} _job-step-info] + ;; Attach :access_url only for a still-running job that actually has an + ;; interactive URL. Completed jobs get no URL (the analysis is gone), and a + ;; job with no :interactive_urls (no interactive steps) simply has none to + ;; attach -- in both cases the plain status update is sent. (if-let [access-url (and (not (jp/completed? status)) (first (:interactive_urls job-info)))] (send-job-status-update username email-address (assoc job-info :access_url access-url)) diff --git a/src/apps/persistence/jobs.clj b/src/apps/persistence/jobs.clj index 775c0e33..29a40da3 100644 --- a/src/apps/persistence/jobs.clj +++ b/src/apps/persistence/jobs.clj @@ -363,7 +363,9 @@ [:o.base_url :operator_base_url]))) (defn- hsql-job-base-query - "The HoneySQL version of the base query used for retrieving job information from the database." + "The HoneySQL version of the base query used for retrieving job information from the database. + Like job-base-query, it left-joins operators so each job carries the base URL of the operator + it was launched on (nil for jobs with no operator_id)." [] (-> (h/select :j.app_description :j.system_id From 750712c235ca309d8767454786ffb23fe0d8ba81 Mon Sep 17 00:00:00 2001 From: John Wregglesworth Date: Thu, 14 May 2026 15:01:37 -0700 Subject: [PATCH 3/3] Read operator_base_url straight from the job_listings view The job_listings view now left-joins operators and exposes operator_base_url itself, so the job base queries no longer need their own operators join -- they select the view column directly. interactive-urls and the notification path are unchanged; they still read :operator_base_url off the job map. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/apps/persistence/jobs.clj | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/apps/persistence/jobs.clj b/src/apps/persistence/jobs.clj index 29a40da3..dbea8ff7 100644 --- a/src/apps/persistence/jobs.clj +++ b/src/apps/persistence/jobs.clj @@ -335,12 +335,9 @@ (cxu/bad-request (str "unrecognized sort field: " (name field))))) (defn- job-base-query - "The base query used for retrieving job information from the database. The - operators table is left-joined so each job carries the base URL of the - operator it was launched on (nil for jobs with no operator_id)." + "The base query used for retrieving job information from the database." [] (-> (sql/select* [:job_listings :j]) - (sql/join :left [:operators :o] {:o.id :j.operator_id}) (sql/fields :j.app_description :j.system_id :j.app_id @@ -360,12 +357,10 @@ :j.parent_id :j.is_batch :j.notify - [:o.base_url :operator_base_url]))) + :j.operator_base_url))) (defn- hsql-job-base-query - "The HoneySQL version of the base query used for retrieving job information from the database. - Like job-base-query, it left-joins operators so each job carries the base URL of the operator - it was launched on (nil for jobs with no operator_id)." + "The HoneySQL version of the base query used for retrieving job information from the database." [] (-> (h/select :j.app_description :j.system_id @@ -386,9 +381,8 @@ :j.parent_id :j.is_batch :j.notify - [:o.base_url :operator_base_url]) - (h/from [:job_listings :j]) - (h/left-join [:operators :o] [:= :o.id :j.operator_id]))) + :j.operator_base_url) + (h/from [:job_listings :j]))) (defn- job-step-base-query "The base query used for retrieving job step information from the database." @@ -483,7 +477,7 @@ :j.parent_id :j.is_batch :j.notify - :o.base_url) + :j.operator_base_url) (sql/where (exists (sql/subselect :job_steps (sql/where {:job_id :j.id :external_id [:in external-ids]})))) (sql/select)))