diff --git a/src/clj/job_streamer/control_bus/component/jobs.clj b/src/clj/job_streamer/control_bus/component/jobs.clj index cbb723e..172efb7 100644 --- a/src/clj/job_streamer/control_bus/component/jobs.clj +++ b/src/clj/job_streamer/control_bus/component/jobs.clj @@ -2,10 +2,11 @@ (:require [clojure.tools.logging :as log] [clojure.edn :as edn] [com.stuartsierra.component :as component] + [bouncer.core :as b] [bouncer.validators :as v] [liberator.core :as liberator] [clojure.string :as str] - + [clj-time.format :as f] (job-streamer.control-bus [notification :as notification] [validation :refer [validate]] [util :refer [parse-body edn->datoms to-int]]) @@ -70,56 +71,70 @@ [?job :job/name ?job-name]]} app-name job-name)) +(defn- parse-query-since [q] + (let [since (.substring q (count "since:"))] + (when (b/valid? {:since since} + :since [[v/datetime (:date f/formatters)]]) + {:since (f/parse (:date f/formatters) since)}))) + +(defn- parse-query-until [q] + (let [until (.substring q (count "until:"))] + (when (b/valid? {:until until} + :until [[v/datetime (:date f/formatters)]]) + {:until (f/parse (:date f/formatters) until)}))) + +(defn- parse-query-exit-status [q] + (let [exit-status (.substring q (count "exit-status:"))] + (when (b/valid? {:exit-status exit-status} + :exit-status v/required) + {:exit-status exit-status}))) + (defn parse-query [query] - (loop [query-vector (str/split query #"\s") - query-map {:job-names []}] - (if-let [query-unit (first query-vector)] - (if (.startsWith query-unit "since:") - (recur (rest query-vector) (assoc query-map :since (java.sql.Date/valueOf (.substring query-unit 6)))) - (if (.startsWith query-unit "until:") - (recur (rest query-vector) (assoc query-map :until (java.sql.Date/valueOf (.substring query-unit 6)))) - (if (.startsWith query-unit "exit-status:") - (recur (rest query-vector) (assoc query-map :exit-status (.substring query-unit 12))) - (recur (rest query-vector) (update-in query-map [:job-names] conj query-unit))))) - query-map))) + (when (not-empty query) + (->> (str/split query #"\s") + (map #(cond + (.startsWith % "since:") (parse-query-since %) + (.startsWith % "until:") (parse-query-until %) + (.startsWith % "exit-status:") (parse-query-exit-status %) + :default {:job-name [%]})) + (apply merge-with concat {:job-name nil})))) (defn find-all [{:keys [datomic]} app-name query & [offset limit]] - (let [query-map (if query (parse-query query) nil) + (let [qmap (parse-query query) base-query '{:find [?job] :in [$ ?app-name [?job-name-condition ...] ?since-condition ?until-condition ?exit-status-condition] :where [[?app :application/name ?app-name] [?app :application/jobs ?job]]} - job-name-condition (:job-names query-map) - since-condition (:since query-map) - until-condition (:until query-map) - exit-status-condition (:exit-status query-map) jobs (d/query datomic (cond-> base-query - (not-empty job-name-condition) - (update-in [:where] conj - '[?job :job/name ?job-name] - '[(.contains ^String ?job-name ?job-name-condition)]) - (or since-condition until-condition exit-status-condition) - (update-in [:where] conj - '[?job :job/executions ?job-executions] - '[?job-execution :job-execution/create-time ?create-time] - '[(max ?create-time)] - '[?job-executions :job-execution/exit-status ?exit-status] - '[?job-executions :job-execution/end-time ?end-time]) - since-condition - (update-in [:where] conj - '[(>= ?end-time ?since-condition)]) - until-condition - (update-in [:where] conj - '[(<= ?end-time ?until-condition)]) - exit-status-condition - (update-in [:where] conj - '[(.contains ^String ?exit-status ?exit-status-condition)])) + (not-empty (:job-name qmap)) + (update-in [:where] conj + '[?job :job/name ?job-name] + '[(.contains ^String ?job-name ?job-name-condition)]) + (or (:since qmap) (:until qmap) (:exit-status qmap)) + (update-in [:where] conj + '[?job :job/executions ?job-executions] + '[?job-execution :job-execution/create-time ?create-time] + '[(max ?create-time)] + '[?job-executions :job-execution/exit-status ?exit-status] + '[?job-executions :job-execution/end-time ?end-time]) + + (:since qmap) + (update-in [:where] conj + '[(>= ?end-time ?since-condition)]) + + (:until qmap) + (update-in [:where] conj + '[(<= ?end-time ?until-condition)]) + + (:exit-status qmap) + (update-in [:where] conj + '[(.contains ^String ?exit-status ?exit-status-condition)])) app-name - (or (not-empty job-name-condition) [""]) - (or since-condition "") - (or until-condition "") - (or exit-status-condition ""))] + (:job-name qmap []) + (:since qmap "") + (:until qmap "") + (:exit-status qmap ""))] {:results (->> jobs (drop (dec (or offset 0))) diff --git a/src/clj/job_streamer/control_bus/system.clj b/src/clj/job_streamer/control_bus/system.clj index c9b2691..ecea67d 100644 --- a/src/clj/job_streamer/control_bus/system.clj +++ b/src/clj/job_streamer/control_bus/system.clj @@ -21,9 +21,7 @@ [socketapp :refer [socketapp-component]]) (job-streamer.control-bus.endpoint [api :refer [api-endpoint]]) - [job-streamer.control-bus.endpoint.api :refer [api-endpoint]]) - (:use ring.middleware.stacktrace)) - + [job-streamer.control-bus.endpoint.api :refer [api-endpoint]])) (defn wrap-same-origin-policy [handler alias] (fn [req] @@ -39,8 +37,7 @@ (def base-config {:app {:middleware [[wrap-not-found :not-found] [wrap-same-origin-policy :same-origin] - [wrap-defaults :defaults] - [ring.middleware.stacktrace/wrap-stacktrace :stacktrace]] + [wrap-defaults :defaults]] :not-found "Resource Not Found" :defaults (meta-merge api-defaults {})}}) diff --git a/test/clj/job_streamer/control_bus/component/jobs_test.clj b/test/clj/job_streamer/control_bus/component/jobs_test.clj index 81101ab..b25f1b3 100644 --- a/test/clj/job_streamer/control_bus/component/jobs_test.clj +++ b/test/clj/job_streamer/control_bus/component/jobs_test.clj @@ -10,7 +10,8 @@ [meta-merge.core :refer [meta-merge]] [clojure.test :refer :all] [clojure.pprint :refer :all] - [clojure.edn :as edn])) + [clojure.edn :as edn] + [clj-time.format :as f])) @@ -75,7 +76,25 @@ (deftest parse-query (testing "parse-query" (let [result (jobs/parse-query "a b since:2016-09-01 until:2016-09-02 exit-status:COMPLETED")] - (is (= "a" (first (:job-names result)))) - (is (= (java.sql.Date/valueOf "2016-09-01") (:since result))) - (is (= (java.sql.Date/valueOf "2016-09-02") (:until result))) - (is (= "COMPLETED" (:exit-status result)))))) + (is (= "a" (first (:job-name result)))) + (is (= "2016-09-01" (f/unparse (:date f/formatters) (:since result)))) + (is (= "2016-09-02" (f/unparse (:date f/formatters) (:until result)))) + (is (= "COMPLETED" (:exit-status result))))) + (testing "nil query returns nil" + (let [result (jobs/parse-query nil)] + (is (nil? result)))) + (testing "empty query returns nil" + (let [result (jobs/parse-query "")] + (is (nil? result)))) + + (testing "single simple query" + (let [result (jobs/parse-query "a")] + (is (= {:job-name '("a")} result)))) + + (testing "single simple query" + (let [result (jobs/parse-query "a")] + (is (= {:job-name '("a")} result)))) + + (testing "ignore breaking tokens in a query" + (let [result (jobs/parse-query "a since: until: since:xxx until:yyy")] + (is (= {:job-name '("a")} result)))))