From c984656d5ea044f7fda0cf9c6baf10a3a40f6190 Mon Sep 17 00:00:00 2001 From: uosl Date: Thu, 6 Feb 2020 18:04:22 +0000 Subject: [PATCH] QB: Support importing of PathQuery XML --- project.clj | 1 + .../bluegenes/pages/querybuilder/events.cljs | 3 +- .../bluegenes/pages/querybuilder/views.cljs | 19 ++- src/cljs/bluegenes/utils.cljs | 48 +++++++- test/cljs/bluegenes/utils_test.cljs | 112 ++++++++++++++++++ tests.edn | 2 +- 6 files changed, 175 insertions(+), 10 deletions(-) create mode 100644 test/cljs/bluegenes/utils_test.cljs diff --git a/project.clj b/project.clj index 76e6c9858..35afc53ea 100644 --- a/project.clj +++ b/project.clj @@ -49,6 +49,7 @@ [cljsjs/google-analytics "2015.04.13-0"] [day8.re-frame/test "0.1.5"] [cljs-bean "1.4.0"] + [org.clojure/data.xml "0.2.0-alpha6"] ; Logging [com.taoensso/timbre "4.10.0"] diff --git a/src/cljs/bluegenes/pages/querybuilder/events.cljs b/src/cljs/bluegenes/pages/querybuilder/events.cljs index 56dd6b028..a467d3531 100644 --- a/src/cljs/bluegenes/pages/querybuilder/events.cljs +++ b/src/cljs/bluegenes/pages/querybuilder/events.cljs @@ -581,6 +581,7 @@ (comp enhance-constraint-logic read-logic-string)) (update :where #(or % [])) (dissoc :title :model) - ;; Sterilizing *might* not be necessary. + ;; Sterilizing *might* not be necessary since + ;; it's coming straight from the InterMine. (im-query/sterilize-query))))) {} queries))))) diff --git a/src/cljs/bluegenes/pages/querybuilder/views.cljs b/src/cljs/bluegenes/pages/querybuilder/views.cljs index 22bacd8c3..ed1d7d98b 100644 --- a/src/cljs/bluegenes/pages/querybuilder/views.cljs +++ b/src/cljs/bluegenes/pages/querybuilder/views.cljs @@ -9,7 +9,8 @@ [imcljs.path :as im-path] [imcljs.query :refer [->xml]] [bluegenes.components.loader :refer [mini-loader loader]] - [bluegenes.components.ui.results_preview :refer [preview-table]])) + [bluegenes.components.ui.results_preview :refer [preview-table]] + [bluegenes.utils :refer [read-xml-query]])) (defn one-of? [haystack needle] (some? (some #{needle} haystack))) @@ -566,7 +567,21 @@ :active? (= @active-query query) :any-renaming* any-renaming?]))])))) -(defn import-from-xml []) +(defn import-from-xml [] + (let [query-input (reagent/atom "")] + (fn [] + [:div + [:p "Paste your InterMine PathQuery XML here."] + [:textarea.form-control + {:rows 10 + :autoFocus true + :value @query-input + :on-change #(reset! query-input (oget % :target :value))}] + [:button.btn.btn-raised + {:on-click #(when-let [query (not-empty @query-input)] + (reset! query-input "") + (dispatch [:qb/load-query (read-xml-query query)]))} + "Load query"]]))) (defn create-template []) diff --git a/src/cljs/bluegenes/utils.cljs b/src/cljs/bluegenes/utils.cljs index 7f9f8f64c..19869fd11 100644 --- a/src/cljs/bluegenes/utils.cljs +++ b/src/cljs/bluegenes/utils.cljs @@ -1,14 +1,16 @@ (ns bluegenes.utils - (:require [clojure.string :refer [blank? join split capitalize split]])) + (:require [clojure.string :as string] + [clojure.data.xml :as xml] + [imcljs.query :as im-query])) (defn uncamel "Uncamel case a string. Example: thisIsAString -> This is a string" [s] - (if-not (blank? s) + (if-not (string/blank? s) (as-> s $ - (split $ #"(?=[A-Z][^A-Z])") - (join " " $) - (capitalize $)) + (string/split $ #"(?=[A-Z][^A-Z])") + (string/join " " $) + (string/capitalize $)) s)) (defn read-origin @@ -16,7 +18,7 @@ [query] (if-let [origin (:from query)] origin - (first (split (first (:select query)) #"\.")))) + (first (string/split (first (:select query)) #"\.")))) (defn kw->str [kw] @@ -26,3 +28,37 @@ (name kw)) (do (assert (string? kw) "This function takes only a keyword or string.") kw))) + +(defn read-xml-query + "Read an InterMine PathQuery in XML into an EDN Clojure map." + [xml-query] + (let [xml-map (xml/parse-str xml-query) + select (string/split (get-in xml-map [:attrs :view]) #" ") + from (second (re-find #"^(.*)\." (first select))) + constraintLogic (get-in xml-map [:attrs :constraintLogic]) + orderBy (let [{:keys [sortOrder orderBy]} (:attrs xml-map) + pairs (partition 2 (string/split (or sortOrder orderBy) #" "))] + (mapv (fn [[path dir]] + {(keyword path) (string/upper-case dir)}) pairs)) + joins (into [] + (comp (filter (comp #{:join} :tag)) + (filter (comp #{"OUTER"} :style :attrs)) + (map (comp :path :attrs))) + (:content xml-map)) + where (into [] + (comp (filter (comp #{:constraint} :tag)) + (map (fn [{:keys [attrs content]}] + (cond-> attrs + (not-empty content) + ;; Handle ONE OF constraints. + (assoc :values + (->> content + (filter (comp #{:value} :tag)) + (mapcat :content))))))) + (:content xml-map))] + {:from from + :select select + :orderBy orderBy + :constraintLogic constraintLogic + :joins joins + :where where})) diff --git a/test/cljs/bluegenes/utils_test.cljs b/test/cljs/bluegenes/utils_test.cljs new file mode 100644 index 000000000..13abb77b6 --- /dev/null +++ b/test/cljs/bluegenes/utils_test.cljs @@ -0,0 +1,112 @@ +(ns bluegenes.utils-test + (:require [cljs.test :refer-macros [deftest is are testing]] + [bluegenes.utils :as utils])) + +(deftest read-xml-query + (testing "Missing fields are correctly nulled" + (are [xml m] (= (utils/read-xml-query xml) m) + "" + {:from "Gene", + :select + ["Gene.secondaryIdentifier" + "Gene.symbol" + "Gene.organism.name" + "Gene.primaryIdentifier"], + :orderBy [], + :constraintLogic nil, + :joins [], + :where []} + "" + {:from "Gene", + :select + ["Gene.secondaryIdentifier" + "Gene.symbol" + "Gene.organism.name" + "Gene.primaryIdentifier"], + :orderBy [{:Gene.symbol "DESC"}], + :constraintLogic nil, + :joins [], + :where []} + " + + +" + {:from "Gene", + :select + ["Gene.secondaryIdentifier" + "Gene.symbol" + "Gene.organism.name" + "Gene.primaryIdentifier"], + :orderBy [{:Gene.symbol "DESC"}], + :constraintLogic nil, + :joins [], + :where + [{:path "Gene.symbol", :code "A", :op "CONTAINS", :value "ab"} + {:path "Gene", :code "B", :op "IN", :value "PL FlyAtlas_brain_top"}]} + " + + +" + {:from "Gene", + :select + ["Gene.secondaryIdentifier" + "Gene.symbol" + "Gene.organism.name" + "Gene.primaryIdentifier"], + :orderBy [{:Gene.symbol "DESC"}], + :constraintLogic "A or B", + :joins [], + :where + [{:path "Gene.symbol", :code "A", :op "CONTAINS", :value "ab"} + {:path "Gene", :code "B", :op "IN", :value "PL FlyAtlas_brain_top"}]})) + (testing "Can handle ONE OF constraints" + (is (= (utils/read-xml-query "))) + CDPK1CK1ENOCDPK4ABRAERD2CRK2)) +") + {:from "Gene" + :select + ["Gene.primaryIdentifier" + "Gene.secondaryIdentifier" + "Gene.symbol" + "Gene.name" + "Gene.length" + "Gene.organism.shortName"], + :orderBy [] + :constraintLogic "(A and B)", + :joins [], + :where + [{:path "Gene.symbol", + :values '("CDPK1" "CK1" "ENO" "CDPK4" "ABRA" "ERD2" "CRK2"), + :op "ONE OF", + :code "B"}]}))) + (testing "Can handle OUTER JOIN" + (is (= (utils/read-xml-query ")))) + ) +") + {:from "Gene", + :select ["Gene.symbol" "Gene.pathways.identifier"], + :orderBy [], + :constraintLogic nil, + :joins ["Gene.pathways"], + :where []}))) + (testing "Can handle OUTER JOIN mixed with constraints" + (is (= (utils/read-xml-query ")))) + + + ) +") + {:from "Gene", + :select + ["Gene.secondaryIdentifier" + "Gene.symbol" + "Gene.organism.name" + "Gene.primaryIdentifier" + "Gene.proteins.primaryIdentifier" + "Gene.proteins.primaryAccession" + "Gene.proteins.organism.name"], + :orderBy [{:Gene.symbol "DESC"}], + :constraintLogic "A and B", + :joins ["Gene.proteins"], + :where + [{:path "Gene.symbol", :code "A", :op "CONTAINS", :value "ab"} + {:path "Gene", :code "B", :op "IN", :value "PL FlyAtlas_brain_top"}]})))) diff --git a/tests.edn b/tests.edn index 7ab25b79c..7b1c2e17f 100644 --- a/tests.edn +++ b/tests.edn @@ -1,5 +1,5 @@ #kaocha/v1 {:tests [{:id :unit-cljs :type :kaocha.type/cljs - :cljs/repl-env cljs.repl.node/repl-env + :cljs/repl-env cljs.repl.browser/repl-env :cljs/timeout 60000}]}