diff --git a/Makefile b/Makefile index cdb7816a..143dae84 100644 --- a/Makefile +++ b/Makefile @@ -25,3 +25,9 @@ server-repl: up: docker compose --profile local up -d + +down: + docker compose --profile local down + +down-v: + docker compose --profile local down -v diff --git a/package-lock.json b/package-lock.json index b214bd24..6d2ce569 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "@monaco-editor/react": "^4.5.0", "@sooro-io/react-gtm-module": "^3.0.0", "antd": "^5.18.3", + "comment-json": "^4.2.5", "fhirpath-autocomplete-ts": "git@github.com:Aidbox/fhirpath-autocomplete-ts.git", "react": "^18.2.0", "react-dom": "^18.3.1", @@ -445,6 +446,12 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, + "node_modules/array-timsort": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", + "license": "MIT" + }, "node_modules/array-tree-filter": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-tree-filter/-/array-tree-filter-2.1.0.tgz", @@ -800,6 +807,22 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, + "node_modules/comment-json": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.2.5.tgz", + "integrity": "sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==", + "license": "MIT", + "dependencies": { + "array-timsort": "^1.0.3", + "core-util-is": "^1.0.3", + "esprima": "^4.0.1", + "has-own-prop": "^2.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/compute-scroll-into-view": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.0.tgz", @@ -1277,6 +1300,19 @@ "node": ">=6" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -1464,6 +1500,15 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, + "node_modules/has-own-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz", + "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -3339,6 +3384,15 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", diff --git a/package.json b/package.json index eece8c6a..1f014146 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "@monaco-editor/react": "^4.5.0", "@sooro-io/react-gtm-module": "^3.0.0", "antd": "^5.18.3", + "comment-json": "^4.2.5", "fhirpath-autocomplete-ts": "git@github.com:Aidbox/fhirpath-autocomplete-ts.git", "react": "^18.2.0", "react-dom": "^18.3.1", diff --git a/src/clj/vd_designer/aidbox.clj b/src/clj/vd_designer/aidbox.clj index cf2da766..b9e1aebb 100644 --- a/src/clj/vd_designer/aidbox.clj +++ b/src/clj/vd_designer/aidbox.clj @@ -60,7 +60,17 @@ :view-definition-run (merge {:body {:resourceType "Parameters" :parameter [{:name "_format" :valueCode "json"} - {:name "viewResource" :resource vd}]}} + {:name "viewResource" :resource (assoc vd :resourceType "ViewDefinition")}]}} + fhir-server-headers)))) + +(defn get-view-definition-sql + [{:keys [box-url request fhir-server-headers]}] + (let [{:keys [vd]} (:body-params request)] + @(martian/response-for (aidbox-client/aidbox-client box-url) + :get-view-definition-sql + (merge {:body {:resourceType "Parameters" + :parameter [{:name "_format" :valueCode "json"} + {:name "viewResource" :resource (assoc vd :resourceType "ViewDefinition")}]}} fhir-server-headers)))) (defn save-view-definition @@ -68,7 +78,7 @@ (let [{:keys [vd vd-id]} (:body-params request)] @(martian/response-for (aidbox-client/aidbox-client box-url) (if vd-id :update-view-definition :create-view-definition) - (merge {:body vd + (merge {:body (assoc vd :resourceType "ViewDefinition") :vd-id vd-id} fhir-server-headers)))) diff --git a/src/clj/vd_designer/clients/aidbox.clj b/src/clj/vd_designer/clients/aidbox.clj index 9cf87ee3..98376c1a 100644 --- a/src/clj/vd_designer/clients/aidbox.clj +++ b/src/clj/vd_designer/clients/aidbox.clj @@ -61,6 +61,15 @@ :method :post :body-schema {:body s/Any}} + {:route-name :get-view-definition-sql + :path-parts ["/fhir/ViewDefinition/$sql"] + :headers-schema {(s/optional-key :Cookie) s/Str + (s/optional-key :Authorization) s/Str} + :produces ["application/json"] + :consumes ["application/json"] + :method :post + :body-schema {:body s/Any}} + {:route-name :rpc :path-parts ["/rpc"] :method :post diff --git a/src/clj/vd_designer/web/routes/router.clj b/src/clj/vd_designer/web/routes/router.clj index 46b6d6f6..eabfbfe9 100644 --- a/src/clj/vd_designer/web/routes/router.clj +++ b/src/clj/vd_designer/web/routes/router.clj @@ -81,7 +81,12 @@ {:post {:parameters {:body {:box-url string? :vd string?}} - :handler #'aidbox/eval-view-definition}}]] + :handler #'aidbox/eval-view-definition}}] + ["/sql" + {:post + {:parameters {:body {:box-url string? + :vd string?}} + :handler #'aidbox/get-view-definition-sql}}]] ["/Resource" {:get {:parameters {:query {:vd-id string? @@ -117,4 +122,4 @@ coercion/coerce-request-middleware coercion/coerce-response-middleware (app-context-middleware ctx) - (observability-middleware)]}})) \ No newline at end of file + (observability-middleware)]}})) diff --git a/src/cljs/vd_designer/http/fhir_server.cljs b/src/cljs/vd_designer/http/fhir_server.cljs index 71655d81..ff9603e2 100644 --- a/src/cljs/vd_designer/http/fhir_server.cljs +++ b/src/cljs/vd_designer/http/fhir_server.cljs @@ -39,14 +39,25 @@ :params {:box-url box-url :vd view-definition} :headers (authorization-header authentication-token)}) +(defn get-view-definition-sql-user-server [authentication-token {:keys [box-url]} view-definition] + {:uri "/api/aidbox/ViewDefinition/sql" + :timeout 8000 + :format (ajax/json-request-format) + :response-format (ajax/json-response-format + {:keywords? true}) + :with-credentials true + :method :post + :params {:box-url box-url :vd view-definition} + :headers (authorization-header authentication-token)}) + (defn get-metadata [{:keys [box-url]}] - {:uri "/api/metadata" - :timeout 8000 - :format (ajax/json-request-format) - :response-format (ajax/json-response-format {:keywords? true}) - :with-credentials false - :method :get - :params {:box-url box-url}}) + {:uri "/api/metadata" + :timeout 8000 + :format (ajax/json-request-format) + :response-format (ajax/json-response-format {:keywords? true}) + :with-credentials false + :method :get + :params {:box-url box-url}}) (defn delete-view-definition [authentication-token {:keys [box-url]} vd-id] {:uri "/api/aidbox/ViewDefinition" diff --git a/src/cljs/vd_designer/index.cljs b/src/cljs/vd_designer/index.cljs index 81544c33..3deb3a47 100644 --- a/src/cljs/vd_designer/index.cljs +++ b/src/cljs/vd_designer/index.cljs @@ -44,11 +44,16 @@ (fn [{:keys [db authentication-token chosen-server]} _] (if (seq db) {:db db} - {:db {:view-definitions [] - :onboarding {:sandbox 0 - :aidbox 0} - :authorized? (boolean authentication-token) - :cfg/fhir-servers {:chosen-server chosen-server}}}))) + {:db {:view-definitions [] + :onboarding {:sandbox 0 + :aidbox 0} + :authorized? (boolean authentication-token) + :cfg/fhir-servers {:chosen-server (or chosen-server + {:server-name "Aidbox Sandbox" + :box-url "https://dfiudgkdea.edge.aidbox.app" + :type :public-servers + :sandbox true + :headers {:Authorization "Basic YmFzaWM6c2VjcmV0"}})}}}))) (defn wrap-view-layout [route view] (let [breadcrumbs {:breadcrumbs (breadcrumbs route)}] diff --git a/src/cljs/vd_designer/pages/form/controller.cljs b/src/cljs/vd_designer/pages/form/controller.cljs index 37bbbdb5..8762166e 100644 --- a/src/cljs/vd_designer/pages/form/controller.cljs +++ b/src/cljs/vd_designer/pages/form/controller.cljs @@ -67,7 +67,7 @@ {:uri "/viewdefinition_jsonschema.json" :fileMatch ["*"] :schema vd-jsonschema/schema})) - :fx (cond-> (if (-> db :cfg/fhir-servers empty?) + :fx (cond-> (if (-> db :cfg/fhir-servers (dissoc :chosen-server) empty?) [[:dispatch [::fetch-user-servers vd-id]]] (ready-server-event-fx vd-id)) @@ -89,11 +89,7 @@ ::got-server-list ;; TODO: decide what if expected server is not in the list? (fn [{:keys [db]} [_ vd-id user-server-list]] - ;; TODO: remove code duplication - {:db (->> user-server-list - (group-by :server-name) - (medley/map-vals first) - (assoc-in db [:cfg/fhir-servers ])) + {:db (update db :cfg/fhir-servers merge user-server-list) :fx (ready-server-event-fx vd-id)})) (reg-event-fx @@ -422,14 +418,15 @@ {:db (-> (assoc db ::m/eval-loading true) (dissoc ::m/empty-inputs?)) - :dispatch [::auth/with-authentication - (fn [authentication-token] - (-> (http.fhir-server/eval-view-definition-user-server - authentication-token - (http.fhir-server/active-server db) - view-definition) - (assoc :on-success [::on-eval-view-definition-success] - :on-failure [::on-eval-view-definition-error])))]})))) + :dispatch-n [[::auth/with-authentication + (fn [authentication-token] + (-> (http.fhir-server/eval-view-definition-user-server + authentication-token + (http.fhir-server/active-server db) + view-definition) + (assoc :on-success [::on-eval-view-definition-success] + :on-failure [::on-eval-view-definition-error])))] + [::on-sql-tab-clicked]]})))) (reg-event-fx ::eval-view-definition-code @@ -438,8 +435,11 @@ {:dataLayer {:event "vd_run" :resource-type (get (:current-vd db) :resource "")}}) (let [sandbox? (settings-model/in-sandbox? db) + parse (if (= :language/json (::m/language db)) + yaml/json-parse + yaml/try-parse) view-definition (-> (::m/view-definition-code db) - yaml/try-parse + parse (js->clj :keywordize-keys true) strip-empty-select-nodes strip-empty-where-nodes) @@ -485,7 +485,10 @@ ::on-eval-view-definition-error (fn [{:keys [db]} [_ result]] {:db (assoc db ::m/eval-loading false) - :notification-error (str "Error on run: " (u/response->error result))})) + :notification-error (str (u/response->error result) ": " + (->> (get-in result [:response :issue]) + (keep :diagnostics) + (str/join ", ")))})) (reg-event-db ::change-input-value @@ -701,7 +704,7 @@ (defn format-vd-code [code lang] (case lang - :language/yaml (-> code js/JSON.parse yaml/stringify) + :language/yaml (-> code yaml/json-parse yaml/stringify) :language/json (-> code yaml/str->yaml (js/JSON.stringify nil 2)) "")) @@ -936,10 +939,40 @@ (input-references/replace-inputs-with-values (::m/tree-inputs db)) (format-code language)))))))) -(reg-event-db +(reg-event-fx + ::get-vd-sql-success + (fn [{:keys [db]} [_ response]] + {:db (assoc-in db + [::m/vd-sql :sql] + (->> response + :parameter + (filter #(= (:name %) "sql")) + first + :valueString))})) + +(reg-event-fx + ::get-vd-sql-failure + (fn [{:keys [_db]} [_ _response]] + nil)) + +(reg-event-fx ::on-sql-tab-clicked - (fn [db _] - (assoc db ::m/left-panel-active-tab :left-panel-tab/sql))) + (fn [{:keys [db]} _] + {:fx [[:dispatch [::auth/with-authentication + (fn [authentication-token] + (assoc (http.fhir-server/get-view-definition-sql-user-server + authentication-token + (http.fhir-server/active-server db) + (-> (:current-vd db) + decoration/remove-decoration + (input-references/replace-inputs-with-values (::m/tree-inputs db)) + strip-empty-collections + remove-meta + strip-empty-select-nodes + strip-empty-where-nodes)) + :on-success [::get-vd-sql-success] + :on-failure [::get-vd-sql-failure]))]]] + :db (assoc db ::m/left-panel-active-tab :left-panel-tab/sql)})) (reg-event-db ::set-code-dirty diff --git a/src/cljs/vd_designer/pages/form/model.cljs b/src/cljs/vd_designer/pages/form/model.cljs index fc27494f..91f418e7 100644 --- a/src/cljs/vd_designer/pages/form/model.cljs +++ b/src/cljs/vd_designer/pages/form/model.cljs @@ -49,7 +49,7 @@ (reg-sub ::sql (fn [db _] - (-> db ::resource-data :sql))) + (-> db ::vd-sql :sql))) (reg-sub ::eval-loading @@ -131,7 +131,7 @@ (get input-id) (get :type)))) -(reg-sub +(reg-sub ::view-definition-code :-> ::view-definition-code) @@ -179,4 +179,4 @@ (reg-sub ::resource-loading? - :-> ::resource-loading?) \ No newline at end of file + :-> ::resource-loading?) diff --git a/src/cljs/vd_designer/pages/form/sql.cljs b/src/cljs/vd_designer/pages/form/sql.cljs index e3cba14a..0f6399b4 100644 --- a/src/cljs/vd_designer/pages/form/sql.cljs +++ b/src/cljs/vd_designer/pages/form/sql.cljs @@ -8,7 +8,7 @@ (defn sql [] (let [sql @(subscribe [::m/sql]) - formatted-sql (sqlf/format sql (clj->js {:language "postgresql"}))] + formatted-sql (if sql (sqlf/format sql (clj->js {:language "postgresql"})) "")] [:div {:style {:height "100%" :padding-right "8px"}} [monaco {:id "vd-sql" diff --git a/src/cljs/vd_designer/pages/lists/settings/controller.cljs b/src/cljs/vd_designer/pages/lists/settings/controller.cljs index 5043d6d6..c33a5746 100644 --- a/src/cljs/vd_designer/pages/lists/settings/controller.cljs +++ b/src/cljs/vd_designer/pages/lists/settings/controller.cljs @@ -1,5 +1,6 @@ (ns vd-designer.pages.lists.settings.controller - (:require [medley.core :as medley] + (:require [cljs.reader :as reader] + [medley.core :as medley] [re-frame.core :refer [reg-cofx reg-event-fx reg-fx reg-event-db inject-cofx]] [vd-designer.auth.controller :as auth] [vd-designer.http.backend :as backend] @@ -118,7 +119,7 @@ (fn [coeffects] (assoc coeffects chosen-server-kv - (js->clj (.getItem js/localStorage chosen-server-kv))))) + (reader/read-string (.getItem js/localStorage chosen-server-kv))))) (reg-event-fx ::use-sandbox-if-not-selected diff --git a/src/cljs/vd_designer/pages/lists/vds/controller.cljs b/src/cljs/vd_designer/pages/lists/vds/controller.cljs index c181ab8e..99dae636 100644 --- a/src/cljs/vd_designer/pages/lists/vds/controller.cljs +++ b/src/cljs/vd_designer/pages/lists/vds/controller.cljs @@ -12,7 +12,7 @@ (reg-event-fx ::start (fn [{db :db} [_]] - {:fx (if (-> db :cfg/fhir-servers ) + {:fx (if (-> db :cfg/fhir-servers (dissoc :chosen-server) not-empty) [[:dispatch [::get-view-definitions]]] [[:dispatch [::settings-controller/fetch-user-servers]] diff --git a/src/cljs/vd_designer/pages/lists/vds/import.cljs b/src/cljs/vd_designer/pages/lists/vds/import.cljs index 93e3ac23..92fb9690 100644 --- a/src/cljs/vd_designer/pages/lists/vds/import.cljs +++ b/src/cljs/vd_designer/pages/lists/vds/import.cljs @@ -8,7 +8,7 @@ [vd-designer.components.tabs :as tabs] [vd-designer.pages.lists.vds.controller :as c] [vd-designer.pages.lists.vds.model :as m] - [vd-designer.utils.yaml :refer [str->yaml]])) + [vd-designer.utils.yaml :refer [try-parse json-parse]])) (def supported-extensions [".json" ".yaml" ".yml"]) @@ -31,10 +31,12 @@ false)))) (defn try-parse-vd [^String content] - (try - (str->yaml content) - (catch js/Error _ - (js/JSON.parse content)))) + (let [yaml-res (try-parse content) + json-res (json-parse content)] + (cond (and yaml-res (not (string? yaml-res))) + yaml-res + (and json-res (not (string? json-res))) + json-res))) (defn parse-vd [^String content] (js->clj (try-parse-vd content) :keywordize-keys true)) diff --git a/src/cljs/vd_designer/utils/yaml.cljs b/src/cljs/vd_designer/utils/yaml.cljs index 6afedf8a..44670463 100644 --- a/src/cljs/vd_designer/utils/yaml.cljs +++ b/src/cljs/vd_designer/utils/yaml.cljs @@ -1,6 +1,6 @@ (ns vd-designer.utils.yaml (:require ["yaml" :as y] - [clojure.string :as str])) + ["comment-json" :as json])) (defn edn->yaml [edn] (y/stringify (clj->js edn))) @@ -8,9 +8,8 @@ (defn stringify [js] (y/stringify js)) -(defn str->yaml [^String str] - (let [wo-comments (str/replace str #"//.*|#.*|/\*[\s\S]*?\*/" "")] - (y/parse wo-comments))) +(defn str->yaml [^String s] + (y/parse s)) (defn yaml->edn [yaml] (y/parse yaml)) @@ -19,4 +18,10 @@ (try (str->yaml content) (catch js/Error _ - (js/JSON.parse content)))) \ No newline at end of file + nil))) + +(defn json-parse [^String content] + (try + (json/parse content nil true) + (catch js/Error _ + nil)))