@@ -9,3 +9,4 @@ pom.xml.asc | |||
/.nrepl-port | |||
.hgignore | |||
.hg/ | |||
.clj-kondo |
@@ -1,6 +1,6 @@ | |||
(defproject tservice-plugins/quartet-metqc-report "v0.1.3" | |||
(defproject quartet-metqc-report "0.1.3" | |||
:description "Visualizes Quality Control(QC) results for Quartet Project." | |||
:url "https://github.com/tservice-plugins/quartet-metqc-report" | |||
:url "https://github.com/chinese-quartet/quartet-metqc-report" | |||
:license {:name "Eclipse Public License" | |||
:url "http://www.eclipse.org/legal/epl-v10.html"} | |||
:min-lein-version "2.5.0" | |||
@@ -8,16 +8,28 @@ | |||
:dependencies | |||
[[org.clojure/data.csv "1.0.0"] | |||
[me.raynes/fs "1.4.6"] | |||
[com.github.yjcyxky/local-fs "0.1.5"] | |||
[org.clojure/tools.logging "1.1.0"] | |||
[org.clojure/core.async "0.4.500" | |||
:exclusions [org.clojure/tools.reader]]] | |||
[metosin/spec-tools "0.10.5"]] | |||
:test-paths ["test"] | |||
:plugins [[lein-cloverage "1.0.13"] | |||
[lein-shell "0.5.0"] | |||
[lein-changelog "0.3.2"]] | |||
:aliases {"update-version" ["shell" "sed" "-i" "" "s/version \"[0-9.]*\"/version \"${:version}\"/" "src/quartet_metqc_report/version.clj"]} | |||
:deploy-repositories [["releases" :clojars]] | |||
:release-tasks [["change" "version" "leiningen.release/bump-version"] | |||
["change" "version" "leiningen.release/bump-version" "release"] | |||
["changelog" "release"] | |||
["update-version"] | |||
["deploy"]] | |||
:profiles | |||
{:provided | |||
{:dependencies | |||
[[org.clojure/clojure "1.10.1"] | |||
[org.clojars.yjcyxky/tservice "0.6.0"]]} | |||
[com.github.yjcyxky/tservice-core "0.2.0"]]} | |||
:uberjar | |||
{:auto-clean true | |||
@@ -25,5 +37,4 @@ | |||
:omit-source true | |||
:javac-options ["-target" "1.8", "-source" "1.8"] | |||
:target-path "target/%s" | |||
:resource-paths ["resources"] | |||
:uberjar-name "quartet-metqc-report.tservice-plugin.jar"}}) | |||
:resource-paths ["resources"]}}) |
@@ -3,14 +3,20 @@ info: | |||
version: v0.1.3 | |||
description: Generate the QC Report for Quartet Metabolomics data. | |||
category: Report | |||
home: https://github.com/tservice-plugins/quartet-metqc-report | |||
source: PGx | |||
home: https://github.com/chinese-quartet/quartet-metqc-report | |||
source: Chinese Quartet | |||
short_name: quartet-metqc-report | |||
icons: | |||
- src: "" | |||
type: image/png | |||
sizes: 192x192 | |||
author: PGx | |||
author: Jingcheng Yang | |||
maintainers: | |||
- Jingcheng Yang | |||
tags: | |||
- R | |||
- Chart | |||
readme: https://github.com/chinese-quartet/quartet-metqc-report/blob/master/README.md | |||
plugin: | |||
name: quartet-metqc-report | |||
display-name: QC Report for Quartet Metabolomics | |||
@@ -18,11 +24,21 @@ plugin: | |||
init: | |||
# Unpack environment file to the directory, repository/envs/quartet-metqc-report | |||
- step: unpack-env | |||
envname: quartet-metqc-report | |||
postunpack: chmod a+x {{ENV_DEST_DIR}}/bin/metqc.sh | |||
envtype: environment | |||
envname: bin | |||
postunpack: chmod a+x {{ENV_DIR}}/bin/metqc.sh | |||
- step: unpack-env | |||
envtype: environment | |||
envname: renv | |||
- step: unpack-env | |||
envtype: environment | |||
envname: requirements.txt | |||
- step: unpack-env | |||
envtype: environment | |||
envname: renv.lock | |||
- step: load-namespace | |||
namespace: tservice.plugins.quartet-metqc-report | |||
namespace: quartet-metqc-report.core | |||
- step: register-plugin | |||
entrypoint: tservice.plugins.quartet-metqc-report/metadata | |||
entrypoint: quartet-metqc-report.core/metadata | |||
- step: init-event | |||
entrypoint: tservice.plugins.quartet-metqc-report/events-init | |||
entrypoint: quartet-metqc-report.core/events-init |
@@ -0,0 +1,15 @@ | |||
(ns quartet-metqc-report.core | |||
(:require [tservice-core.tasks.http :as http-task] | |||
[quartet-metqc-report.spec :as spec] | |||
[quartet-metqc-report.task :as task])) | |||
(def metadata | |||
(http-task/make-routes "quartet-metqc-report" :ReportPlugin | |||
{:method-type :post | |||
:endpoint "quartet-metqc-report" | |||
:summary "Generate the QC Report for Quartet Metabolomics data." | |||
:body-schema spec/quartet-metqc-report-params-body | |||
:response-schema any? | |||
:handler task/post-handler})) | |||
(def events-init task/events-init) |
@@ -0,0 +1,92 @@ | |||
(ns quartet-metqc-report.metqc | |||
"A wrapper for metqc tool." | |||
(:require [clojure.string :as clj-str] | |||
[local-fs.core :as fs-lib] | |||
[clojure.java.shell :as shell :refer [sh]] | |||
[tservice-core.plugins.util :refer [call-command!]] | |||
[tservice-core.plugins.env :refer [get-context-path add-env-to-path]] | |||
[clojure.tools.logging :as log] | |||
[quartet-metqc-report.version :as v])) | |||
(defn call-metqc! | |||
"Call metqc bash script. more details on https://github.com/chinese-quartet/MetQC | |||
exp-file: Proteomics profiled data. | |||
meta-file: proteomics metadata. | |||
result-dir: A directory for result files." | |||
[exp-file meta-file result-dir] | |||
(let [command ["bash" "-c" | |||
(format "metqc.sh -d %s -m %s -o %s" exp-file meta-file result-dir)] | |||
path-var (add-env-to-path v/plugin-name) | |||
rprofile (fs-lib/join-paths (get-context-path :env v/plugin-name) "Rprofile")] | |||
(log/info "PATH variable: " path-var) | |||
(log/info "Rprofile file is in " rprofile) | |||
(shell/with-sh-env {:PATH path-var | |||
:R_PROFILE_USER rprofile | |||
:LC_ALL "en_US.utf-8" | |||
:LANG "en_US.utf-8"} | |||
(let [result (apply sh command)] | |||
{:status (if (= (:exit result) 0) "Success" "Error") | |||
:msg (str (:out result) "\n" (:err result))})))) | |||
(defn multiqc | |||
"A multiqc wrapper for generating multiqc report: | |||
TODO: set the absolute path of multiqc binary instead of environment variable | |||
Required: | |||
analysis-dir: Analysis directory, e.g. data directory from project | |||
outdir: Create report in the specified output directory. | |||
Options: | |||
| key | description | | |||
| -------------------|-------------| | |||
| :dry-run? | Dry run mode | | |||
| :filename | Report filename. Use 'stdout' to print to standard out. | | |||
| :comment | Custom comment, will be printed at the top of the report. | | |||
| :title | Report title. Printed as page header, used for filename if not otherwise specified. | | |||
| :force? | Overwrite any existing reports | | |||
| :prepend-dirs? | Prepend directory to sample names | | |||
| :template | default, other custom template | | |||
| :config | Where is the config file | | |||
| :env | An environemnt map for running multiqc, such as {:PATH (get-path-variable)} | | |||
Example: | |||
(multiqc 'XXX' 'YYY' {:filename 'ZZZ' | |||
:comment '' | |||
:title '' | |||
:force? true | |||
:prepend-dirs? true})" | |||
[analysis-dir outdir {:keys [dry-run? filename comment title force? prepend-dirs? template config env] | |||
:or {dry-run? false | |||
force? true | |||
prepend-dirs? false | |||
filename "multiqc_report.html" | |||
comment "" | |||
template "default" | |||
title "iSEQ Analyzer Report"}}] | |||
(let [force-arg (if force? "--force" "") | |||
dirs-arg (if prepend-dirs? "--dirs" "") | |||
config-arg (if config (str "-c " config) "") | |||
multiqc-command (filter #(> (count %) 0) ["multiqc" | |||
force-arg dirs-arg config-arg | |||
"--title" (format "'%s'" title) | |||
"--comment" (format "'%s'" comment) | |||
"--filename" filename | |||
"--outdir" outdir | |||
"-t" template | |||
analysis-dir]) | |||
command (clj-str/join " " multiqc-command)] | |||
(if dry-run? | |||
(log/info command) | |||
(if env | |||
(call-command! command env) | |||
(call-command! command))))) | |||
(defn is-localpath? | |||
[filepath] | |||
(re-matches #"^file:\/\/.*" filepath)) | |||
(defn correct-filepath | |||
[filepath] | |||
(if (is-localpath? filepath) | |||
(clj-str/replace filepath #"^file:\/\/" "") | |||
filepath)) |
@@ -0,0 +1,41 @@ | |||
(ns quartet-metqc-report.spec | |||
(:require [clojure.spec.alpha :as s] | |||
[spec-tools.core :as st])) | |||
;;; ------------------------------------------------ Event Specs ------------------------------------------------ | |||
(s/def ::metadata_file | |||
(st/spec | |||
{:spec (s/and string? #(re-matches #"^[a-zA-Z0-9]+:\/\/(\/|\.\/)[a-zA-Z0-9_]+.*" %)) | |||
:type :string | |||
:description "File path for metadata file, such as file:///xxx/xxx/metadata.csv" | |||
:swagger/default nil | |||
:reason "The filepath must be string."})) | |||
(s/def ::data_file | |||
(st/spec | |||
{:spec (s/and string? #(re-matches #"^[a-zA-Z0-9]+:\/\/(\/|\.\/)[a-zA-Z0-9_]+.*" %)) | |||
:type :string | |||
:description "File path for metabolomics profiled data, such as file:///xxx/xxx/data.csv" | |||
:swagger/default nil | |||
:reason "The filepath must be string."})) | |||
(s/def ::name | |||
(st/spec | |||
{:spec string? | |||
:type :string | |||
:description "The name of the report" | |||
:swagger/default "" | |||
:reason "Not a valid name"})) | |||
(s/def ::description | |||
(st/spec | |||
{:spec string? | |||
:type :string | |||
:description "Description of the report" | |||
:swagger/default "" | |||
:reason "Not a valid description."})) | |||
(def quartet-metqc-report-params-body | |||
"A spec for the body parameters." | |||
(s/keys :req-un [::name ::data_file ::metadata_file] | |||
:opt-un [::description])) |
@@ -0,0 +1,105 @@ | |||
(ns quartet-metqc-report.task | |||
(:require [quartet-metqc-report.metqc :as metqc] | |||
[local-fs.core :as fs-lib] | |||
[tservice-core.plugins.env :refer [add-env-to-path create-task! update-task!]] | |||
[tservice-core.plugins.util :as util] | |||
[clojure.data.json :as json] | |||
[clojure.tools.logging :as log] | |||
[tservice-core.tasks.async :refer [publish-event! make-events-init]])) | |||
(defn date | |||
[] | |||
(.format (java.text.SimpleDateFormat. "yyyy-MM-dd") | |||
(new java.util.Date))) | |||
(defn update-process! | |||
[^String task-id ^Integer percentage] | |||
(let [record (cond | |||
(= percentage 100) {:status "Finished" | |||
:percentage 100 | |||
:finished_time (util/time->int (util/now))} | |||
(= percentage -1) {:status "Failed" | |||
:finished_time (util/time->int (util/now))} | |||
:else {:percentage percentage}) | |||
record (merge {:id task-id} record)] | |||
(update-task! record))) | |||
(defn update-log-process! | |||
"Update message into log file and process into database." | |||
[log-path coll task-id process] | |||
(spit log-path (json/write-str coll)) | |||
(update-process! task-id process)) | |||
(defn post-handler | |||
[{{:keys [name data_file metadata_file description owner plugin-context] | |||
:or {description (format "Quality control report for %s" name)} | |||
:as payload} :body}] | |||
(log/info (format "Create a report %s with %s" name payload)) | |||
(let [payload (merge {:description description} payload) | |||
data-file (metqc/correct-filepath data_file) | |||
metadata-file (metqc/correct-filepath metadata_file) | |||
workdir (fs-lib/dirname data-file) | |||
log-path (fs-lib/join-paths workdir "log") | |||
response {:report (format "%s/multiqc_report.html" workdir) | |||
:log log-path} | |||
task-id (create-task! {:name name | |||
:description description | |||
:payload payload | |||
:owner owner | |||
:plugin-name "quartet-metqc-report" | |||
:plugin-type "ReportPlugin" | |||
:plugin-version (:plugin-version plugin-context) | |||
:response response}) | |||
result-dir (fs-lib/join-paths workdir "results")] | |||
(fs-lib/create-directories! result-dir) | |||
(spit log-path (json/write-str {:status "Running" | |||
:msg ""})) | |||
(update-process! task-id 0) | |||
(publish-event! "quartet_metqc_report" | |||
{:data-file data-file | |||
:metadata-file metadata-file | |||
:dest-dir workdir | |||
:task-id task-id | |||
:metadata {:name name | |||
:description description | |||
:plugin-name "quartet-metqc-report" | |||
:plutin-type "ReportPlugin" | |||
:plugin-version (:plugin-version plugin-context)}}) | |||
response)) | |||
(defn- make-report! | |||
[{:keys [data-file metadata-file dest-dir metadata task-id]}] | |||
(let [log-path (fs-lib/join-paths dest-dir "log") | |||
result-dir (fs-lib/join-paths dest-dir "results") | |||
parameters-file (fs-lib/join-paths result-dir "general_information.json") | |||
results (util/chain-fn-coll [(fn [] | |||
(update-process! task-id 20) | |||
(metqc/call-metqc! data-file metadata-file result-dir)) | |||
(fn [] | |||
(update-process! task-id 50) | |||
(spit parameters-file (json/write-str {"Report Name" (:name metadata) | |||
"Description" (:description metadata) | |||
"Report Tool" (format "%s-%s" | |||
(:plugin-name metadata) | |||
(:plugin-version metadata)) | |||
"Team" "Quartet Team" | |||
"Date" (date)})) | |||
{:status "Success" :msg ""}) | |||
(fn [] | |||
(update-process! task-id 80) | |||
(metqc/multiqc result-dir dest-dir | |||
{:template "quartet_metabolite_report" | |||
:title "Quartet Report for Metabolomics" | |||
:env {:PATH (add-env-to-path "quartet-metqc-report")}}))] | |||
(fn [result] (= (:status result) "Success"))) | |||
status (:status (last results)) | |||
msg (apply str (map :msg results)) | |||
process (if (= status "Success") 100 -1)] | |||
(log/info (format "Running batch command: %s" (pr-str results))) | |||
(update-log-process! log-path {:status status | |||
:msg msg} | |||
task-id process))) | |||
(def events-init | |||
"Automatically called during startup; start event listener for quartet_metqc_report events." | |||
(make-events-init "quartet_metqc_report" make-report!)) |
@@ -0,0 +1,4 @@ | |||
(ns quartet-metqc-report.version) | |||
(def plugin-name "quartet-metqc-report") | |||
(def version "0.1.3") |
@@ -1,144 +0,0 @@ | |||
(ns tservice.plugins.quartet-metqc-report | |||
(:require [clojure.data.json :as json] | |||
[clojure.spec.alpha :as s] | |||
[spec-tools.core :as st] | |||
[clojure.tools.logging :as log] | |||
[tservice.lib.files :as ff] | |||
[tservice.api.config :refer [add-env-to-path]] | |||
[tservice.lib.fs :as fs-lib] | |||
[tservice.vendor.multiqc :as mq] | |||
[tservice.plugins.quartet-metqc-report.metqc :as metqc] | |||
[tservice.api.task :refer [make-events-init publish-event! make-plugin-metadata create-task! update-process!]])) | |||
;;; ------------------------------------------------ Event Specs ------------------------------------------------ | |||
(s/def ::metadata_file | |||
(st/spec | |||
{:spec (s/and string? #(re-matches #"^[a-zA-Z0-9]+:\/\/(\/|\.\/)[a-zA-Z0-9_]+.*" %)) | |||
:type :string | |||
:description "File path for metadata file, such as file:///xxx/xxx/metadata.csv" | |||
:swagger/default nil | |||
:reason "The filepath must be string."})) | |||
(s/def ::data_file | |||
(st/spec | |||
{:spec (s/and string? #(re-matches #"^[a-zA-Z0-9]+:\/\/(\/|\.\/)[a-zA-Z0-9_]+.*" %)) | |||
:type :string | |||
:description "File path for metabolomics profiled data, such as file:///xxx/xxx/data.csv" | |||
:swagger/default nil | |||
:reason "The filepath must be string."})) | |||
(s/def ::name | |||
(st/spec | |||
{:spec string? | |||
:type :string | |||
:description "The name of the report" | |||
:swagger/default "" | |||
:reason "Not a valid name"})) | |||
(s/def ::description | |||
(st/spec | |||
{:spec string? | |||
:type :string | |||
:description "Description of the report" | |||
:swagger/default "" | |||
:reason "Not a valid description."})) | |||
(def quartet-metqc-report-params-body | |||
"A spec for the body parameters." | |||
(s/keys :req-un [::name ::data_file ::metadata_file] | |||
:opt-un [::description])) | |||
;;; ------------------------------------------------ Event Metadata ------------------------------------------------ | |||
(def metadata | |||
(make-plugin-metadata | |||
{:name "quartet-metqc-report" | |||
:summary "Visualizes Quality Control(QC) results from metabolomics data for Quartet Project." | |||
:params-schema quartet-metqc-report-params-body | |||
:handler (fn [{:keys [name data_file metadata_file description owner plugin-context] | |||
:or {description (format "Quality control report for %s" name)} | |||
:as payload}] | |||
(let [payload (merge {:description description} payload) | |||
data-file (metqc/correct-filepath data_file) | |||
metadata-file (metqc/correct-filepath metadata_file) | |||
workdir (metqc/dirname data-file) | |||
log-path (fs-lib/join-paths workdir "log") | |||
response {:report (format "%s/multiqc_report.html" workdir) | |||
:log log-path | |||
:response-type :data2report} | |||
task-id (create-task! {:name name | |||
:description description | |||
:payload payload | |||
:owner owner | |||
:plugin-name "quartet-metqc-report" | |||
:plugin-type "ReportPlugin" | |||
:plugin-version (:plugin-version plugin-context) | |||
:response response}) | |||
result-dir (fs-lib/join-paths workdir "results")] | |||
(fs-lib/create-directories! result-dir) | |||
(log/info (format "Create a report %s with %s in %s" name payload workdir)) | |||
(spit log-path (json/write-str {:status "Running" | |||
:msg ""})) | |||
(update-process! task-id 0) | |||
(publish-event! "quartet_metqc_report" | |||
{:data-file data-file | |||
:metadata-file metadata-file | |||
:dest-dir workdir | |||
:task-id task-id | |||
:metadata {:name name | |||
:description description | |||
:plugin-name "quartet-metqc-report" | |||
:plutin-type "ReportPlugin" | |||
:plugin-version (:plugin-version plugin-context)}}) | |||
response)) | |||
:plugin-type :ReportPlugin | |||
:response-type :data2report})) | |||
(defn update-log-process! | |||
"Update message into log file and process into database." | |||
[log-path coll task-id process] | |||
(spit log-path (json/write-str coll)) | |||
(update-process! task-id process)) | |||
(defn date | |||
[] | |||
(.format (java.text.SimpleDateFormat. "yyyy-MM-dd") | |||
(new java.util.Date))) | |||
(defn- make-report! | |||
[{:keys [data-file metadata-file dest-dir metadata task-id]}] | |||
(let [log-path (fs-lib/join-paths dest-dir "log") | |||
result-dir (fs-lib/join-paths dest-dir "results") | |||
parameters-file (fs-lib/join-paths result-dir "general_information.json") | |||
results (ff/chain-fn-coll [(fn [] | |||
(update-process! task-id 20) | |||
(metqc/call-metqc! data-file metadata-file result-dir)) | |||
(fn [] | |||
(update-process! task-id 50) | |||
(spit parameters-file (json/write-str {"Report Name" (:name metadata) | |||
"Description" (:description metadata) | |||
"Report Tool" (format "%s-%s" | |||
(:plugin-name metadata) | |||
(:plugin-version metadata)) | |||
"Team" "Quartet Team" | |||
"Date" (date)})) | |||
{:status "Success" :msg ""}) | |||
(fn [] | |||
(update-process! task-id 80) | |||
(mq/multiqc result-dir dest-dir | |||
{:template "quartet_metabolite_report" | |||
:title "Quartet Report for Metabolomics" | |||
:env {:PATH (add-env-to-path "quartet-metqc-report")}}))] | |||
(fn [result] (= (:status result) "Success"))) | |||
status (:status (last results)) | |||
msg (apply str (map :msg results)) | |||
process (if (= status "Success") 100 -1)] | |||
(log/info (format "Running batch command: %s" (pr-str results))) | |||
(update-log-process! log-path {:status status | |||
:msg msg} | |||
task-id process))) | |||
;;; --------------------------------------------------- Lifecycle ---------------------------------------------------- | |||
(def events-init | |||
"Automatically called during startup; start event listener for quartet_metqc_report events." | |||
(make-events-init "quartet_metqc_report" make-report!)) |
@@ -1,54 +0,0 @@ | |||
(ns tservice.plugins.quartet-metqc-report.metqc | |||
"A wrapper for metqc tool." | |||
(:require [tservice.api.config :refer [add-env-to-path]] | |||
[tservice.lib.files :refer [is-localpath? get-plugin-jar-env-dir]] | |||
[clojure.string :as clj-str] | |||
[tservice.lib.fs :as fs-lib] | |||
[clojure.java.shell :as shell :refer [sh]] | |||
[clojure.java.io :refer [file]])) | |||
(defn call-metqc! | |||
"Call metqc bash script. more details on https://github.com/chinese-quartet/MetQC | |||
exp-file: Proteomics profiled data. | |||
meta-file: proteomics metadata. | |||
result-dir: A directory for result files. | |||
" | |||
[exp-file meta-file result-dir] | |||
(shell/with-sh-env {:PATH (add-env-to-path "quartet-metqc-report") | |||
:R_PROFILE_USER (fs-lib/join-paths (get-plugin-jar-env-dir "quartet-metqc-report") "Rprofile") | |||
:LC_ALL "en_US.utf-8" | |||
:LANG "en_US.utf-8"} | |||
(let [command ["bash" "-c" | |||
(format "metqc.sh -d %s -m %s -o %s" exp-file meta-file result-dir)] | |||
result (apply sh command) | |||
status (if (= (:exit result) 0) "Success" "Error") | |||
msg (str (:out result) "\n" (:err result))] | |||
{:status status | |||
:msg msg}))) | |||
(defn correct-filepath | |||
[filepath] | |||
(if (is-localpath? filepath) | |||
(clj-str/replace filepath #"^file:\/\/" "") | |||
filepath)) | |||
(defn ^String basename | |||
"Returns the basename of 'path'. | |||
This works by calling getName() on a java.io.File instance. It's prefered | |||
over last-dir-in-path for that reason. | |||
Parameters: | |||
path - String containing the path for an item in iRODS. | |||
Returns: | |||
String containing the basename of path." | |||
[^String path] | |||
(.getName (file path))) | |||
(defn ^String dirname | |||
"Returns the dirname of 'path'. | |||
This works by calling getParent() on a java.io.File instance. | |||
Parameters: | |||
path - String containing the path for an item in iRODS. | |||
Returns: | |||
String containing the dirname of path." | |||
[^String path] | |||
(when path (.getParent (file path)))) |