             (map #(assoc % :job job) (vals builds)))
           @builds))
 
-(defn- comma-separated-fail-phase [{start :start end :end culprits :culprits}]
-  (join [(csv/export [(csv/format-timestamp start)
-                      (csv/format-timestamp end)
-                      (join "|" culprits)])
-         "\n"]))
-
 (defn- get-fail-phases [accept]
   (let [annotated-builds-in-order (sort-by :end (all-builds-in-order))
         fail-phases (pipelineinfo/pipeline-fail-phases annotated-builds-in-order)]
     (if (= (:mime accept) :json)
       {:body fail-phases}
-      (join (cons "start,end,culprits\n"
-                  (map comma-separated-fail-phase fail-phases))))))
+      {:body (csv/export-table ["start" "end" "culprits"]
+                               (map (fn [{start :start end :end culprits :culprits}]
+                                      [(csv/format-timestamp start)
+                                       (csv/format-timestamp end)
+                                       (join "|" culprits)])
+                                    fail-phases))})))
 
 ;; failures
 

  (let [job-names (filter has-testsuites? (keys @builds))]
    (if (= (:mime accept) :json)
      {:body (zipmap job-names (map testsuites-for job-names))}
      {:body (join (cons "averageRuntime,job,testsuite,classname,name\n"
                         (mapcat comma-separated-test-runtimes job-names)))})))

;; app


(defn- serialize-nested-testsuites [testsuite-id]
  (join ": " testsuite-id))

(defn- flat-test-runtimes [job]
  (->> (testsuites/average-testsuite-runtime-as-list (test-runs job))
       (map (fn [{testsuite :testsuite classname :classname name :name average-runtime :averageRuntime}]
              [average-runtime
               job
               (serialize-nested-testsuites testsuite)
               classname
               name]))))

(defn- has-testsuites? [job]
  (some? (@test-results job)))
