From 9f3abbc5a15c317fdbb9261150d1a3e1fa852b2a Mon Sep 17 00:00:00 2001 From: Eoin Houlihan Date: Mon, 6 Mar 2017 16:26:48 +0000 Subject: [PATCH 01/72] Make file upload required again (#93) --- panacea/web/templates/page/index.html.eex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panacea/web/templates/page/index.html.eex b/panacea/web/templates/page/index.html.eex index 26eebdb3..f550f9ff 100644 --- a/panacea/web/templates/page/index.html.eex +++ b/panacea/web/templates/page/index.html.eex @@ -1,7 +1,7 @@ <%= form_for @conn, pml_path(@conn, :upload), [as: :upload, multipart: true, id: "file-form"], fn f -> %>
From fd1d952360ad2cdec6c110da812433bd46365246 Mon Sep 17 00:00:00 2001 From: Conor Brennan Date: Mon, 6 Mar 2017 19:15:18 +0000 Subject: [PATCH 02/72] new readme and docker install script (#95) * new readme *docker install script * updates changelog * adds sudo to docker commands in features.md * Ascelpius README markdown fixes #94 fixes #92 --- BUILDING_MANUALLY.md | 39 ++++++++++++ CHANGELOG.md | 12 ++++ FEATURES.md | 14 ++--- README.md | 93 +++++++++++++++++++++++++++++ README.org | 89 --------------------------- asclepius/{README.org => README.md} | 77 +++++++++++++++--------- install-docker.sh | 30 ++++++++++ 7 files changed, 229 insertions(+), 125 deletions(-) create mode 100644 BUILDING_MANUALLY.md create mode 100644 README.md delete mode 100644 README.org rename asclepius/{README.org => README.md} (60%) create mode 100755 install-docker.sh diff --git a/BUILDING_MANUALLY.md b/BUILDING_MANUALLY.md new file mode 100644 index 00000000..b38064ef --- /dev/null +++ b/BUILDING_MANUALLY.md @@ -0,0 +1,39 @@ +# Building Manually + +There are DNS problems when running containers in *Ubuntu* within the *TCD +network*. This can be resolved while on the TCD network by editing the Docker +DNS config. + +```bash +$ echo '{"dns": ["134.226.251.200", "134.226.251.100"]}' | sudo tee -a /etc/docker/daemon.json +$ sudo service docker restart +``` + +You should revert these settings when leaving the TCD network by running + +```bash +$ sudo rm /etc/docker/daemon.json +$ sudo service docker restart +``` + +1. Clone the Repo + + ```bash + $ git clone https://github.com/tom-and-the-toothfairies/pathways.git + ``` + +2. Build with Docker + + ```bash + $ cd pathways + $ sudo docker build -t tomtoothfairies/asclepius asclepius + $ sudo docker build -t tomtoothfairies/panacea panacea + $ sudo docker built -t tomtoothfairies/chiron chiron + ``` + +3. Run the tests + + ```bash + $ sudo docker run -t -e "MIX_ENV=test" tomtoothfairies/panacea mix test + $ sudo docker run -t tomtoothfairies/asclepius pytest + ``` diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a7cc371..7c6bd161 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to this project will be documented in this file. +## [Unreleased] + +## Changed +- Clarified installation instructions after feedback from client. Client left + feedback after release 0.3 indicating that our current README was unhelpful. + We have greatly simplified it and moved more non-essential information into + separate documents. + +## Added +- install-docker.sh for easy docker installation on Ubuntu 16.0.4. Part of + simplifying the installation steps. + ## [0.3] 2017-03-05 ### Added diff --git a/FEATURES.md b/FEATURES.md index bf32f719..e58b57cf 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -30,7 +30,7 @@ Once a file has been selected, users must be able to load it into the system for This feature has automated tests which can be run with the following command ```bash -$ docker run -e "MIX_ENV=test" tomtoothfairies/panacea mix test --only pml_loading +$ sudo docker run -e "MIX_ENV=test" tomtoothfairies/panacea mix test --only pml_loading ``` This feature can also be tested manually. Visit the [homepage] and @@ -49,7 +49,7 @@ about the encountered error must be readily available. This feature has automated tests which can be run with the following command ```bash -$ docker run -e "MIX_ENV=test" tomtoothfairies/panacea mix test --only pml_analysis +$ sudo docker run -e "MIX_ENV=test" tomtoothfairies/panacea mix test --only pml_analysis ``` This feature can also be tested manually. Visit the [homepage] and select a @@ -68,7 +68,7 @@ must be easily identified, and the error messages must be useful. This feature has automated tests which can be run with the following command ```bash -$ docker run -e "MIX_ENV=test" tomtoothfairies/panacea mix test --only err_highlights +$ sudo docker run -e "MIX_ENV=test" tomtoothfairies/panacea mix test --only err_highlights ``` This feature can also be tested manually. Visit the [homepage] and select a @@ -81,7 +81,7 @@ valid PML and should result in a successful analysis. ## PML Log-file Generation - Complete ### Description -The successful or insuccessful loading of PML files into the system is output +The successful or unsuccessful loading of PML files into the system is output to the console logs of the panacea service. ### Testing @@ -89,7 +89,7 @@ This feature is tested manually. First, open up the tail of the panacea logs: ```bash -$ docker-compose logs -f panacea +$ sudo docker-compose logs -f panacea ``` Visit the [homepage] and select a file. Some useful files can be found in the [fixtures directory]. Press the `Submit` button. You should see log entries @@ -132,7 +132,7 @@ must be reported back to the user as `identified drugs`. This feature has automated tests which can be run with the following command ```bash -$ docker run -e "MIX_ENV=test" tomtoothfairies/panacea mix test --only identify_drugs +$ sudo docker run -e "MIX_ENV=test" tomtoothfairies/panacea mix test --only identify_drugs ``` This feature can also be manually tested by uploading files. Some useful files @@ -157,7 +157,7 @@ uploaded PML files. The drug-drug interactions are contained in DINTO. This feature has automated tests which can be run with the following command ```bash -$ docker run -e "MIX_ENV=test" tomtoothfairies/panacea mix test --only identify_ddis +$ sudo docker run -e "MIX_ENV=test" tomtoothfairies/panacea mix test --only identify_ddis ``` This feature can also be manually tested by uploading files. Some usefull files diff --git a/README.md b/README.md new file mode 100644 index 00000000..fbd04e82 --- /dev/null +++ b/README.md @@ -0,0 +1,93 @@ +# Tom and the Toothfairies +[![spacemacs badge]][spacemacs github] [![build status badge]][circle ci] + +This file contains instructions on how to install and run the project, as well +as an overview of the project's design. The target platform is Ubuntu 16.04. + +At the time of writing the current release is `0.3` + +## Installing Dependencies + +This project uses Docker and Docker Compose. For Ubuntu 16.04, an installation +script `install-docker.sh` is provided for convenience. + +```bash +$ ./install-docker.sh +``` + +For platforms other than Ubuntu, follow the installation instructions on +the [Docker Website][install docker ce]. Note - on other platforms, sudo is not +required for docker commands. + +You can test your docker installation by running docker's built in hello-world: + +```bash +$ sudo docker run hello-world +``` + +## Running + +The project can be run with `docker compose` + +```bash +$ sudo docker-compose up -d +``` + +To stop the project, run the following command + +```bash +$ sudo docker-compose down +``` + +## Features + +The feature list for the project can be found [here](./FEATURES.md) + +## Change Log + +The change log for the project can be found [here](./CHANGELOG.md) + + +## Architecture Overview + +The system is split into three distinct services, Pancea, Asclepius and Chiron. +They each run inside a docker container. The containers can be easily managed +using `docker-compose` as mentioned earlier. + +### Panacea + +Panacea is responsible for the UI and PML analysis. It is a web application that +serves the UI and exposes an API for uploading PML files for analysis. + +More information about Panacea can be found [here](./panacea/README.md). + +### Chiron + +Chiron houses the DINTO data. The data is compiled into a triple store, Chiron +exposes a HTTP API for querying the triple store. + +More information about Chiron can be found [here](./chiron/README.md). + +### Asclepius + +Asclepius acts as an intermediary between Panacea and Chiron. It accepts +requests to identify DDIs from Panacea, creates the necessary SPARQL query and +passes it on to Chiron. + +More information about Asclepius can be found [here](./asclepius/README.org). + +## Building Manually + +Docker compose will pull down the required versions of each service from [Docker +Hub] when you run the project. If for some reason you wish to build these +services manually, instructions for doing so can be +found [here](./BUILDING_MANUALLY.md). + + + +[spacemacs badge]: https://cdn.rawgit.com/syl20bnr/spacemacs/442d025779da2f62fc86c2082703697714db6514/assets/spacemacs-badge.svg +[spacemacs github]: https://github.com/syl20bnr/spacemacs +[build status badge]: https://img.shields.io/circleci/project/github/tom-and-the-toothfairies/pathways.svg +[circle ci]: https://circleci.com/gh/tom-and-the-toothfairies/pathways +[install docker ce]: https://www.docker.com/community-edition#/download +[docker hub]: https://hub.docker.com/u/tomtoothfairies/ diff --git a/README.org b/README.org deleted file mode 100644 index 63c9f658..00000000 --- a/README.org +++ /dev/null @@ -1,89 +0,0 @@ -* Tom and the Toothfairies -[[https://github.com/syl20bnr/spacemacs][https://cdn.rawgit.com/syl20bnr/spacemacs/442d025779da2f62fc86c2082703697714db6514/assets/spacemacs-badge.svg]] [[https://circleci.com/gh/tom-and-the-toothfairies/pathways][https://img.shields.io/circleci/project/github/tom-and-the-toothfairies/pathways.svg]] -** Installation -This project includes a Docker Compose file for easy installation and testing. -Installation instructions for Docker on your platform can be found [[https://www.docker.com/community-edition#/download][here]]. - -For Docker installations on Ubuntu ~sudo~ is required to run Docker and Docker -Compose commands. You will also need to install ~docker-compose~ separately with -#+BEGIN_SRC bash -$ sudo curl -o /usr/local/bin/docker-compose -L https://github.com/docker/compose/releases/download/1.11.2/docker-compose-`uname -s`-`uname -m` -$ sudo chmod +x /usr/local/bin/docker-compose -#+END_SRC -*** The Happy Path -If you do not wish to build the Docker containers yourself you can run it -directly from the Docker registry. First, install the necessary Docker -components for your operating system as outlined above. - -Then you can run the latest release from [[https://hub.docker.com/u/tomtoothfairies/][Docker Hub]] -#+BEGIN_SRC bash -$ docker-compose up -d -#+END_SRC -You will then be able to access the web interface at ~http://localhost:4000~\\ -You can stop the Docker containers with: -#+BEGIN_SRC bash -$ docker-compose down -#+END_SRC - -To run a release other than the current you may manually checkout the release -tag and run ~docker-compose~ -#+BEGIN_SRC bash -$ git checkout 0.3 -$ docker-compose up -d -#+END_SRC - -*** Building Manually -There are DNS problems when running containers in *Ubuntu* within the *TCD -network*. This can be resolved while on the TCD network by editing the Docker -DNS config. -#+BEGIN_SRC bash -$ echo "{\"dns\": [\"134.226.251.200\", \"134.226.251.100\"]}" | sudo tee -a /etc/docker/daemon.json -$ sudo service docker restart -#+END_SRC -You should revert these settings when leaving the TCD network by running -#+BEGIN_SRC bash -$ sudo rm /etc/docker/daemon.json -$ sudo service docker restart -#+END_SRC - -1) Clone the Repo - #+BEGIN_SRC bash - $ git clone https://github.com/tom-and-the-toothfairies/pathways.git - #+END_SRC -2) Build with Docker - #+BEGIN_SRC bash - $ cd pathways - $ docker build -t tomtoothfairies/asclepius asclepius - $ docker build -t tomtoothfairies/panacea panacea - $ docker built -t tomtoothfairies/chiron chiron - #+END_SRC -3) Run the tests - #+BEGIN_SRC bash - $ docker run -t -e "MIX_ENV=test" tomtoothfairies/panacea mix test - $ docker run -t tomtoothfairies/asclepius pytest - #+END_SRC - -** Features -The feature list for the project can be found [[./FEATURES.md][here]]. -** Change Log -The project's change log can be found [[./CHANGELOG.md][here]] -** Architecture Overview -The system is split into three distinct services, Pancea, Asclepius and Chiron. -They each run inside a docker container. The containers can be easily managed -using ~docker-compose~ as mentioned earlier. -*** Panacea -Panacea is responsible for the UI and PML analysis. It is a web application that -serves the UI and exposes an API for uploading PML files for analysis. - -More information about Panacea can be found [[./panacea/README.md][here]]. -*** Chiron -Chiron houses the DINTO data. The data is compiled into a triple store, Chiron -exposes a HTTP API for querying the triple store. - -More information about Chiron can be found [[./chiron/REAME.md][here]]. -*** Asclepius -Asclepius acts as an intermediary between Panacea and Chiron. It accepts -requests to identify DDIs from Panacea, creates the necessary SPARQL query and -passes it on to Chiron. - -More information about Asclepius can be found [[./asclepius/README.org][here]]. diff --git a/asclepius/README.org b/asclepius/README.md similarity index 60% rename from asclepius/README.org rename to asclepius/README.md index 2d950685..53193c21 100644 --- a/asclepius/README.org +++ b/asclepius/README.md @@ -1,20 +1,27 @@ -* Asclepius ⚕ -Flask endpoint for querying DINTO. Supports querying for all drugs listed, as -well as finding all, or specific drug-drug interactions. +Asclepius ⚕ +=========== -This application acts as an adaptor to Chiron - an instance of Apache Fuseki, -Chiron must be running before any queries can be served. +Flask endpoint for querying DINTO. Supports querying for all drugs listed, as well as finding all, or specific drug-drug interactions. -** Endpoints -*** ~/all_drugs~ +This application acts as an adaptor to Chiron - an instance of Apache Fuseki, Chiron must be running before any queries can be served. + +Endpoints +--------- + +### `/all_drugs` + +| | | +|-------------|---------------------------------------------------------------------------------------| | Description | Find all drugs in the DINTO ontology | -| Methods | ~GET~ | +| Methods | `GET` | | Parameters | None | | Returns | A list containing pairs of the canonical URI for a drug, as well as its English Label | -**** Example -***** Response Body (Truncated) -#+BEGIN_SRC json +#### Example + +##### Response Body (Truncated) + +```json [ { "label": "carbapenem MM22383", @@ -33,17 +40,22 @@ Chiron must be running before any queries can be served. "uri": "http://purl.obolibrary.org/obo/CHEBI_4911" } ] -#+END_SRC +``` + +### `/all_ddis` -*** ~/all_ddis~ +| | | +|-------------|--------------------------------------------------------------------------------------------------------| | Description | Find all drug-drug interactions (DDIs) in the DINTO ontology | -| Methods | ~GET~ | +| Methods | `GET` | | Parameters | None | | Returns | A list containing pairs of the canonical URI for a drug-drug interaction, as well as its English Label | -**** Example -***** Response Body (Truncated) -#+BEGIN_SRC json +#### Example + +##### Response Body (Truncated) + +```json [ { "label": "torasemide/trandolapril DDI", @@ -58,21 +70,28 @@ Chiron must be running before any queries can be served. "uri": "http://purl.obolibrary.org/obo/DINTO_10154" } ] -#+END_SRC +``` + +### `/ddis` -*** ~/ddis~ -| Description | Find all drug-drug interactions (DDI) in the DINTO ontology which involve only the /given/ drugs | -| Methods | ~POST~ | -| Request Body | An object containing a list of /drug references/, named ~drugs~, where a /drug reference/ is either ~dinto:DB123~ or ~chebi:123~ | +| | | +|--------------|----------------------------------------------------------------------------------------------------------------------------------| +| Description | Find all drug-drug interactions (DDI) in the DINTO ontology which involve only the *given* drugs | +| Methods | `POST` | +| Request Body | An object containing a list of *drug references*, named `drugs`, where a *drug reference* is either `dinto:DB123` or `chebi:123` | | Returns | A list of DDI objects; its label, its URI, and the identifiers of the two drugs involved | -**** Example -***** Request Body -#+BEGIN_SRC json +#### Example + +##### Request Body + +```json {"drugs": ["chebi:421707", "chebi:465284", "dinto:DB00503", "chebi:9342"]} -#+END_SRC -***** Response Body -#+BEGIN_SRC json +``` + +##### Response Body + +```json [ { "drug_a": "chebi:421707", @@ -87,4 +106,4 @@ Chiron must be running before any queries can be served. "uri": "http://purl.obolibrary.org/obo/DINTO_11043" } ] -#+END_SRC +``` diff --git a/install-docker.sh b/install-docker.sh new file mode 100755 index 00000000..a94b19eb --- /dev/null +++ b/install-docker.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# Install Dependencies + +sudo apt-get -y install \ + apt-transport-https \ + ca-certificates \ + curl + +if ! hash docker 2>/dev/null; then + # Install Docker + + curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - + + sudo add-apt-repository \ + "deb [arch=amd64] https://download.docker.com/linux/ubuntu \ + $(lsb_release -cs) \ + stable" + + sudo apt-get update + + sudo apt-get -y install docker-ce +fi + +if ! hash docker-compose 2>/dev/null; then + # Install Docker Compose + + sudo curl -o /usr/local/bin/docker-compose -L https://github.com/docker/compose/releases/download/1.11.2/docker-compose-`uname -s`-`uname -m` + sudo chmod +x /usr/local/bin/docker-compose +fi From b23810a3215eba706d31dc0222bc9f1ca0c37eb1 Mon Sep 17 00:00:00 2001 From: Eoin Houlihan Date: Mon, 6 Mar 2017 19:22:00 +0000 Subject: [PATCH 03/72] Minor doodad --- FEATURES.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/FEATURES.md b/FEATURES.md index e58b57cf..4dcb5c2c 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -175,6 +175,6 @@ result in DDIs being reported to the user. ## DINTO Error and Warning highlights -[README]: ./README.org +[README]: ./README.md [homepage]: http://localhost:4000 [fixtures directory]: ./panacea/test/fixtures diff --git a/README.md b/README.md index fbd04e82..209311aa 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ Asclepius acts as an intermediary between Panacea and Chiron. It accepts requests to identify DDIs from Panacea, creates the necessary SPARQL query and passes it on to Chiron. -More information about Asclepius can be found [here](./asclepius/README.org). +More information about Asclepius can be found [here](./asclepius/README.md). ## Building Manually From 15cc07b78407ea3f66930823b9fe6f0fa37313a5 Mon Sep 17 00:00:00 2001 From: Conor Brennan Date: Tue, 7 Mar 2017 10:52:21 +0000 Subject: [PATCH 04/72] api pipeline (#101) * adds custom API pipeline Switches to minimal api pipeline for `/api` requests instead of using the browser pipeline. Adds basic access control using Phoenix.Token --- panacea/lib/panacea/access_token.ex | 19 +++++++++ .../test/controllers/pml_controller_test.exs | 2 +- .../test/plugs/require_access_token_test.exs | 30 ++++++++++++++ panacea/test/support/authorized_conn_case.ex | 39 +++++++++++++++++++ panacea/web/controllers/page_controller.ex | 2 +- panacea/web/plugs/require_access_token.ex | 35 +++++++++++++++++ panacea/web/router.ex | 5 +-- panacea/web/static/js/app.js | 5 ++- panacea/web/templates/layout/app.html.eex | 2 + 9 files changed, 133 insertions(+), 6 deletions(-) create mode 100644 panacea/lib/panacea/access_token.ex create mode 100644 panacea/test/plugs/require_access_token_test.exs create mode 100644 panacea/test/support/authorized_conn_case.ex create mode 100644 panacea/web/plugs/require_access_token.ex diff --git a/panacea/lib/panacea/access_token.ex b/panacea/lib/panacea/access_token.ex new file mode 100644 index 00000000..da3be65d --- /dev/null +++ b/panacea/lib/panacea/access_token.ex @@ -0,0 +1,19 @@ +defmodule Panacea.AccessToken do + # arbitraty TTL for tokens - 6 hours + @ttl 60 * 60 * 6 + + def generate do + Phoenix.Token.sign(Panacea.Endpoint, "access-token", nil) + end + + def verify(token) do + {status, _} = Phoenix.Token.verify( + Panacea.Endpoint, + "access-token", + token, + max_age: @ttl + ) + + status == :ok + end +end diff --git a/panacea/test/controllers/pml_controller_test.exs b/panacea/test/controllers/pml_controller_test.exs index aa04a74b..a99e3baf 100644 --- a/panacea/test/controllers/pml_controller_test.exs +++ b/panacea/test/controllers/pml_controller_test.exs @@ -1,5 +1,5 @@ defmodule Panacea.PmlControllerTest do - use Panacea.ConnCase + use Panacea.AuthorizedConnCase @fixtures_dir "test/fixtures/" diff --git a/panacea/test/plugs/require_access_token_test.exs b/panacea/test/plugs/require_access_token_test.exs new file mode 100644 index 00000000..03e33a77 --- /dev/null +++ b/panacea/test/plugs/require_access_token_test.exs @@ -0,0 +1,30 @@ +defmodule Panacea.Plugs.RequireAccessTokenTest do + use Panacea.ConnCase + alias Panacea.Plugs.RequireAccessToken + + describe "call/2" do + test "forbids requests with no authorization header", %{conn: conn} do + resp = RequireAccessToken.call(conn, []) + + assert resp.status == 403 + end + + test "forbids requests with an invalid authorization header", %{conn: conn} do + resp = + conn + |> put_req_header("authorization", "foo") + |> RequireAccessToken.call([]) + + assert resp.status == 403 + end + + test "permits requests with a valid authorization header", %{conn: conn} do + authorized_conn = + conn + |> put_req_header("authorization", Panacea.AccessToken.generate) + |> RequireAccessToken.call([]) + + refute authorized_conn.halted + end + end +end diff --git a/panacea/test/support/authorized_conn_case.ex b/panacea/test/support/authorized_conn_case.ex new file mode 100644 index 00000000..47cf8e30 --- /dev/null +++ b/panacea/test/support/authorized_conn_case.ex @@ -0,0 +1,39 @@ +defmodule Panacea.AuthorizedConnCase do + @moduledoc """ + The `:api` pipeline requires an authorization header to be + present. This module defines the test case to be used by + tests that require setting up an authorized connection. + + Such tests rely on `Phoenix.ConnTest` and also + import other functionality to make it easier + to build and query models. + + Finally, if the test case interacts with the database, + it cannot be async. For this reason, every test runs + inside a transaction which is reset at the beginning + of the test unless the test case is marked as async. + """ + + use ExUnit.CaseTemplate + + using do + quote do + # Import conveniences for testing with connections + use Phoenix.ConnTest + + import Panacea.Router.Helpers + + # The default endpoint for testing + @endpoint Panacea.Endpoint + end + end + + setup _tags do + token = Panacea.AccessToken.generate + conn = + Phoenix.ConnTest.build_conn() + |> Plug.Conn.put_req_header("authorization", token) + + {:ok, conn: conn} + end +end diff --git a/panacea/web/controllers/page_controller.ex b/panacea/web/controllers/page_controller.ex index ccbe2f13..b56a8c04 100644 --- a/panacea/web/controllers/page_controller.ex +++ b/panacea/web/controllers/page_controller.ex @@ -2,6 +2,6 @@ defmodule Panacea.PageController do use Panacea.Web, :controller def index(conn, _params) do - render conn, "index.html" + render conn, "index.html", token: Panacea.AccessToken.generate end end diff --git a/panacea/web/plugs/require_access_token.ex b/panacea/web/plugs/require_access_token.ex new file mode 100644 index 00000000..3e00c33a --- /dev/null +++ b/panacea/web/plugs/require_access_token.ex @@ -0,0 +1,35 @@ +defmodule Panacea.Plugs.RequireAccessToken do + import Plug.Conn + + def init(opts), do: opts + + def call(conn, _opts) do + conn + |> fetch_auth_header() + |> verify_token() + end + + defp fetch_auth_header(conn) do + case get_req_header(conn, "authorization") do + [] -> + conn + |> send_resp(:forbidden, "access token missing") + |> halt() + + [token] -> + conn + |> assign(:access_token, token) + end + end + + defp verify_token(%Plug.Conn{halted: true} = conn), do: conn + defp verify_token(conn) do + if Panacea.AccessToken.verify(conn.assigns[:access_token]) do + conn + else + conn + |> send_resp(:forbidden, "invalid access token") + |> halt() + end + end +end diff --git a/panacea/web/router.ex b/panacea/web/router.ex index d1373dc7..49d2754d 100644 --- a/panacea/web/router.ex +++ b/panacea/web/router.ex @@ -11,6 +11,7 @@ defmodule Panacea.Router do pipeline :api do plug :accepts, ["json"] + plug Panacea.Plugs.RequireAccessToken, [] end scope "/", Panacea do @@ -20,9 +21,7 @@ defmodule Panacea.Router do end scope "/api", Panacea do - # use browser pipeline until we add some simple - # auth to api pipeline - pipe_through :browser + pipe_through :api post "/pml", PmlController, :upload end diff --git a/panacea/web/static/js/app.js b/panacea/web/static/js/app.js index 85896c30..bf6948de 100644 --- a/panacea/web/static/js/app.js +++ b/panacea/web/static/js/app.js @@ -1,6 +1,8 @@ import "babel-polyfill"; import "phoenix_html"; +const apiAccessToken = document.getElementById('api-access-token').content; + document.getElementById('file-form').addEventListener('submit', e => { e.preventDefault(); submitFile.bind(e.target)(); @@ -11,7 +13,8 @@ async function submitFile() { const response = await fetch(this.action, { method: 'POST', body: new FormData(this), - credentials: 'same-origin' + credentials: 'same-origin', + headers: new Headers({authorization: apiAccessToken}) }); if (response.ok) { diff --git a/panacea/web/templates/layout/app.html.eex b/panacea/web/templates/layout/app.html.eex index b95fa1fd..28497dc0 100644 --- a/panacea/web/templates/layout/app.html.eex +++ b/panacea/web/templates/layout/app.html.eex @@ -7,6 +7,8 @@ + <%= tag :meta, name: "api-access-token", id: "api-access-token", content: @token %> + Panacea Pathways "> From a5f0afc82ce9437250a5dabdef7513d1f33a79eb Mon Sep 17 00:00:00 2001 From: Peter Meehan Date: Tue, 7 Mar 2017 17:14:13 +0000 Subject: [PATCH 05/72] Ajax Loading UI, addresses #91 (#99) * added loading icon, modified file input [wip] * cleaner filename display logic * added fades to elemens that flashed * updated readme with ui changes --- CHANGELOG.md | 6 +- .../fonts/glyphicons-halflings-regular.woff2 | Bin 0 -> 18028 bytes panacea/web/static/assets/images/heart.svg | 1 + panacea/web/static/css/app.css | 28 ++++++++- panacea/web/static/js/app.js | 56 +++++++++++++----- panacea/web/templates/page/index.html.eex | 36 +++++++---- 6 files changed, 100 insertions(+), 27 deletions(-) create mode 100644 panacea/web/static/assets/fonts/glyphicons-halflings-regular.woff2 create mode 100644 panacea/web/static/assets/images/heart.svg diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c6bd161..f3c933b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ All notable changes to this project will be documented in this file. feedback after release 0.3 indicating that our current README was unhelpful. We have greatly simplified it and moved more non-essential information into separate documents. +- Updated user interface with loading icon to provide visual representation of + the request's duration. ## Added - install-docker.sh for easy docker installation on Ubuntu 16.0.4. Part of @@ -32,7 +34,7 @@ All notable changes to this project will be documented in this file. take several minutes to load DINTO into memory so Panacea would have to poll it to see if it was ready. Chiron can load DINTO from its triple store instantly, so the waiting is no longer required. - + ## [0.2] 2017-02-26 ### Added @@ -62,7 +64,7 @@ All notable changes to this project will be documented in this file. build process. Now Asclepius clones DINTO and checks out a specific revision instead. - + ## [0.1] 2017-02-12 ### Fixed diff --git a/panacea/web/static/assets/fonts/glyphicons-halflings-regular.woff2 b/panacea/web/static/assets/fonts/glyphicons-halflings-regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..64539b54c3751a6d9adb44c8e3a45ba5a73b77f0 GIT binary patch literal 18028 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 literal 0 HcmV?d00001 diff --git a/panacea/web/static/assets/images/heart.svg b/panacea/web/static/assets/images/heart.svg new file mode 100644 index 00000000..95c73960 --- /dev/null +++ b/panacea/web/static/assets/images/heart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/panacea/web/static/css/app.css b/panacea/web/static/css/app.css index 9c795f97..2a0f300f 100644 --- a/panacea/web/static/css/app.css +++ b/panacea/web/static/css/app.css @@ -5,6 +5,32 @@ margin-top: -10px; } -.panel-hidden, #success { +.hidden { display: none; } + +@keyframes fade-in { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +.fades-in { + animation: fade-in 500ms ease-in-out both; +} + +.center { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.loading-icon { + width: 128px; + height: 128px; + margin: 40px; +} diff --git a/panacea/web/static/js/app.js b/panacea/web/static/js/app.js index bf6948de..1a46b2ec 100644 --- a/panacea/web/static/js/app.js +++ b/panacea/web/static/js/app.js @@ -9,6 +9,8 @@ document.getElementById('file-form').addEventListener('submit', e => { }); async function submitFile() { + hideFileForm(); + hideResults(); try { const response = await fetch(this.action, { method: 'POST', @@ -18,35 +20,61 @@ async function submitFile() { }); if (response.ok) { - renderFileResponse(await response.json()); + renderSuccess(await response.json()); } else { - renderFileError(await response.json()); + renderError(await response.json()); } this.reset(); } catch (err) { console.log(err); } + restoreFileForm(); } -const successPanel = document.getElementById('success'); -const errorPanel = document.getElementById('error-panel'); +const successElement = document.getElementById('success'); +const errorElement = document.getElementById('error'); -const renderFileResponse = data => { - // Parsed drugs - const drugsResultMessage = document.getElementById('success-result-message'); - drugsResultMessage.innerHTML = JSON.stringify(data.drugs); +const hideResults = () => { + errorElement.classList.add('hidden'); + successElement.classList.add('hidden'); +} - // DDIS +const renderSuccess = data => { + const drugsResultMessage = document.getElementById('success-result-message'); const ddisResultMessage = document.getElementById('success-ddis-message'); + + drugsResultMessage.innerHTML = JSON.stringify(data.drugs); ddisResultMessage.innerHTML = JSON.stringify(data.ddis); - errorPanel.style.display = 'none'; - successPanel.style.display = 'block'; + errorElement.classList.add('hidden'); + successElement.classList.remove('hidden'); }; -const renderFileError = data => { +const renderError = data => { const errorResultMessage = document.getElementById('error-result-message'); errorResultMessage.innerHTML = data.message; - errorPanel.style.display = 'block'; - successPanel.style.display = 'none'; + + errorElement.classList.remove('hidden'); + successElement.classList.add('hidden'); }; + +const formElement = document.getElementById('file-form'); +const loadingElement = document.getElementById('loading-container'); + +const hideFileForm = () => { + formElement.classList.add('hidden'); + loadingElement.classList.remove('hidden'); +} + +const restoreFileForm = () => { + formElement.classList.remove('hidden'); + loadingElement.classList.add('hidden'); +} + +// to make the file input pretty take the filename from the form +// and place it in a disabled input box right beside it :art: +const filenameDisplayElement = document.getElementById('filename-display'); +const fileInputElement = document.getElementById('file-input'); +fileInputElement.addEventListener('change', function(e) { + filenameDisplayElement.value = this.files[0].name; +}); diff --git a/panacea/web/templates/page/index.html.eex b/panacea/web/templates/page/index.html.eex index f550f9ff..19d95434 100644 --- a/panacea/web/templates/page/index.html.eex +++ b/panacea/web/templates/page/index.html.eex @@ -1,24 +1,40 @@ -<%= form_for @conn, pml_path(@conn, :upload), [as: :upload, multipart: true, id: "file-form"], fn f -> %> -
- +<%= form_for @conn, pml_path(@conn, :upload), [as: :upload, multipart: true, id: "file-form", class: "center fades-in"], fn f -> %> +
+
+ + +
+
<%= submit "Submit", class: "btn btn-success btn-lg" %>
<% end %> -
+ + + + -
-
PML Error
-
+ + From a8328914280890c42d7cbbfa2faab9c6de8cd39b Mon Sep 17 00:00:00 2001 From: shawa Date: Wed, 8 Mar 2017 14:23:27 +0000 Subject: [PATCH 06/72] Draft new API reference --- asclepius/README.md | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/asclepius/README.md b/asclepius/README.md index 53193c21..af9b9314 100644 --- a/asclepius/README.md +++ b/asclepius/README.md @@ -81,29 +81,31 @@ Endpoints | Request Body | An object containing a list of *drug references*, named `drugs`, where a *drug reference* is either `dinto:DB123` or `chebi:123` | | Returns | A list of DDI objects; its label, its URI, and the identifiers of the two drugs involved | + +### `/uris` + +| | | +|--------------|------------------------------------------------------------------------------------------------------------------| +| Description | For a given list of drug labels, find their `chebi:123/dinto:db123` identifier (to be used when calling `/ddis`) | +| Methods | `POST` | +| Request Body | An object containing a list of drug labels, named `labels` | +| Returns | See below | + #### Example ##### Request Body ```json -{"drugs": ["chebi:421707", "chebi:465284", "dinto:DB00503", "chebi:9342"]} +{"drugs": [ "paracetamol",,,]} ``` ##### Response Body ```json [ - { - "drug_a": "chebi:421707", - "drug_b": "chebi:465284", - "label": "abacavir/ganciclovir DDI", - "uri": "http://purl.obolibrary.org/obo/DINTO_05759" - }, - { - "drug_a": "chebi:421707", - "drug_b": "dinto:DB00503", - "label": "abacavir/ritonavir DDI", - "uri": "http://purl.obolibrary.org/obo/DINTO_11043" + "not_found": ['flat seven up'], + "found": { + "paracetamol": "dinto:db12345" } ] ``` From 173e487144107a5b2e72dc42ca16b42328136418 Mon Sep 17 00:00:00 2001 From: shawa Date: Wed, 8 Mar 2017 15:20:44 +0000 Subject: [PATCH 07/72] Add labels parameter to drugs dinto query, to filter by name --- asclepius/asclepius/dinto.py | 15 ++++++++++++--- asclepius/asclepius/main.py | 12 ++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/asclepius/asclepius/dinto.py b/asclepius/asclepius/dinto.py index 0de2e931..5f268cde 100644 --- a/asclepius/asclepius/dinto.py +++ b/asclepius/asclepius/dinto.py @@ -58,13 +58,23 @@ def sparqled(*args, **kwargs): @sparql -def all_drugs(): +def drugs(labels=None): + + if labels is not None: + if not isinstance(labels, frozenset): + raise ValueError("for cachability, `labels` must be given as a frozenset") + label_str_literals = [f'"{label}"' for label in labels] + filter = f'FILTER (?label in ({", ".join(label_str_literals)}))' + else: + filter = '' + return f''' {PREFIXES} SELECT ?uri ?label WHERE {{ - ?uri rdfs:subClassOf {PHARMACOLOGICAL_ENTITY}. + ?uri rdfs:subClassOf {PHARMACOLOGICAL_ENTITY} . ?uri rdfs:label ?label + {filter} }} ''' @@ -80,7 +90,6 @@ def all_ddis(): }} ''' - def _valid_drug(drug_identifier): return DRUG_PATTERN.match(drug_identifier) is not None diff --git a/asclepius/asclepius/main.py b/asclepius/asclepius/main.py index 7db9528d..7640ec56 100644 --- a/asclepius/asclepius/main.py +++ b/asclepius/asclepius/main.py @@ -73,5 +73,17 @@ def ddis(): return jsonify(dinto_res) +@app.route('/uris') +def uris(): + """ + """ + params = request.get_json() + if params is None or 'labels' not in params or not params['labels']: + raise InvalidUsage("Expecting {'labels': [...]} with at least two labels") + + labels = params['labels'] + drugs = dinto.drugs(labels) + + drugs = dinto.drugs(labels) if __name__ == '__main__': app.run() From a032c1211a98ba15e9ab91014c0961d5e8855ae8 Mon Sep 17 00:00:00 2001 From: shawa Date: Wed, 8 Mar 2017 15:29:41 +0000 Subject: [PATCH 08/72] Add uris endpoint --- asclepius/README.md | 16 ++++++++++------ asclepius/asclepius/dinto.py | 1 + asclepius/asclepius/main.py | 17 ++++++++++++++--- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/asclepius/README.md b/asclepius/README.md index af9b9314..3561ccc6 100644 --- a/asclepius/README.md +++ b/asclepius/README.md @@ -96,16 +96,20 @@ Endpoints ##### Request Body ```json -{"drugs": [ "paracetamol",,,]} +{"labels": [ "paracetamol", "flat seven up", "cocaine"]} ``` ##### Response Body ```json -[ - "not_found": ['flat seven up'], +{ "found": { - "paracetamol": "dinto:db12345" - } -] + "cocaine": "http://purl.obolibrary.org/obo/CHEBI_27958", + "paracetamol": "http://purl.obolibrary.org/obo/CHEBI_46195" + }, + "not_found": [ + "flat seven up" + ] +} + ``` diff --git a/asclepius/asclepius/dinto.py b/asclepius/asclepius/dinto.py index 5f268cde..51308760 100644 --- a/asclepius/asclepius/dinto.py +++ b/asclepius/asclepius/dinto.py @@ -57,6 +57,7 @@ def sparqled(*args, **kwargs): return sparqled +@lru_cache() @sparql def drugs(labels=None): diff --git a/asclepius/asclepius/main.py b/asclepius/asclepius/main.py index 7640ec56..77fc3290 100644 --- a/asclepius/asclepius/main.py +++ b/asclepius/asclepius/main.py @@ -73,7 +73,7 @@ def ddis(): return jsonify(dinto_res) -@app.route('/uris') +@app.route('/uris', methods=["POST"]) def uris(): """ """ @@ -81,9 +81,20 @@ def uris(): if params is None or 'labels' not in params or not params['labels']: raise InvalidUsage("Expecting {'labels': [...]} with at least two labels") - labels = params['labels'] + labels = frozenset(params['labels']) drugs = dinto.drugs(labels) - drugs = dinto.drugs(labels) + to_uri = {} + for entry in drugs: + to_uri[entry['label']] = entry['uri'] + + missing = labels - set(to_uri.keys()) + + result = { + 'not_found': list(missing), + 'found': to_uri, + } + return jsonify(result) + if __name__ == '__main__': app.run() From 4d5cb3794d4e276de613be06f5ae9e02ddbc1473 Mon Sep 17 00:00:00 2001 From: shawa Date: Wed, 8 Mar 2017 15:50:30 +0000 Subject: [PATCH 09/72] Find DDIs by URL --- asclepius/README.md | 27 +++++++++++++++++++++++++-- asclepius/asclepius/dinto.py | 11 +++-------- asclepius/asclepius/main.py | 15 ++++----------- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/asclepius/README.md b/asclepius/README.md index 3561ccc6..e1188422 100644 --- a/asclepius/README.md +++ b/asclepius/README.md @@ -81,7 +81,29 @@ Endpoints | Request Body | An object containing a list of *drug references*, named `drugs`, where a *drug reference* is either `dinto:DB123` or `chebi:123` | | Returns | A list of DDI objects; its label, its URI, and the identifiers of the two drugs involved | +#### Example +##### Request Body +```json +{ + "drugs": [ + "http://purl.obolibrary.org/obo/DINTO_DB00214", + "http://purl.obolibrary.org/obo/DINTO_DB00519" + ] +} +``` + +##### Response Body +```json +[ + { + "drug_a": "http://purl.obolibrary.org/obo/DINTO_DB00214", + "drug_b": "http://purl.obolibrary.org/obo/DINTO_DB00519", + "label": "torasemide/trandolapril DDI", + "uri": "http://purl.obolibrary.org/obo/DINTO_11031" + } +] +``` ### `/uris` | | | @@ -104,8 +126,9 @@ Endpoints ```json { "found": { - "cocaine": "http://purl.obolibrary.org/obo/CHEBI_27958", - "paracetamol": "http://purl.obolibrary.org/obo/CHEBI_46195" + "http://purl.obolibrary.org/obo/CHEBI_27958": "cocaine", + "http://purl.obolibrary.org/obo/CHEBI_46195": "paracetamol", + }, "not_found": [ "flat seven up" diff --git a/asclepius/asclepius/dinto.py b/asclepius/asclepius/dinto.py index 51308760..0e050291 100644 --- a/asclepius/asclepius/dinto.py +++ b/asclepius/asclepius/dinto.py @@ -91,22 +91,17 @@ def all_ddis(): }} ''' -def _valid_drug(drug_identifier): - return DRUG_PATTERN.match(drug_identifier) is not None - @sparql def ddi_from_drugs(drugs): if not isinstance(drugs, frozenset): raise ValueError("for cachability, `drugs` must be given as a frozenset") - + print(drugs) if len(drugs) < 2: raise ValueError("Need at least 2 drugs to find interactions") - if not all(_valid_drug(drug) for drug in drugs): - raise ValueError("Drugs must be specified as chebi:123 or dinto:DB123") - - drug_search_space = ', '.join(drugs) + quoted = (f'<{uri}>' for uri in drugs) + drug_search_space = ', '.join(quoted) return f''' {PREFIXES} diff --git a/asclepius/asclepius/main.py b/asclepius/asclepius/main.py index 77fc3290..8ba7661e 100644 --- a/asclepius/asclepius/main.py +++ b/asclepius/asclepius/main.py @@ -63,13 +63,6 @@ def ddis(): except ValueError as e: raise InvalidUsage(str(e)) - for ddi in dinto_res: - for drug in ('drug_a', 'drug_b'): - if ddi[drug].startswith(dinto.DINTO_PREFIX): - ddi[drug] = ddi[drug].replace(dinto.DINTO_PREFIX, 'dinto:') - elif ddi[drug].startswith(dinto.CHEBI_PREFIX): - ddi[drug] = ddi[drug].replace(dinto.CHEBI_PREFIX, 'chebi:') - return jsonify(dinto_res) @@ -84,15 +77,15 @@ def uris(): labels = frozenset(params['labels']) drugs = dinto.drugs(labels) - to_uri = {} + to_label = {} for entry in drugs: - to_uri[entry['label']] = entry['uri'] + to_label[entry['uri']] = entry['label'] - missing = labels - set(to_uri.keys()) + missing = labels - set(to_label.values()) result = { 'not_found': list(missing), - 'found': to_uri, + 'found': to_label, } return jsonify(result) From 6f2c808b10575d63486f64427ebee21e84ec3e45 Mon Sep 17 00:00:00 2001 From: Aaron Jenner Date: Wed, 8 Mar 2017 16:58:16 +0000 Subject: [PATCH 10/72] increase timeouts to 15s, clear ui on new file select (#105) * increased timeout delay for dinto requests to 15s, up from 5 * page now clears results when new file selected --- panacea/lib/panacea/asclepius/remote/http.ex | 4 +++- panacea/web/static/js/app.js | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/panacea/lib/panacea/asclepius/remote/http.ex b/panacea/lib/panacea/asclepius/remote/http.ex index 01666ff6..b1121985 100644 --- a/panacea/lib/panacea/asclepius/remote/http.ex +++ b/panacea/lib/panacea/asclepius/remote/http.ex @@ -3,11 +3,13 @@ defmodule Panacea.Asclepius.Remote.HTTP do @asclepius_uri Keyword.get(Application.get_env(:panacea, :asclepius), :uri) @default_headers [{"Content-Type", "application/json"}] + @default_timeout 1000 * 15 + @default_options [recv_timeout: @default_timeout] def ddis(drugs) do {:ok, body} = %{drugs: drugs} |> Poison.encode asclepius_uri("/ddis") - |> HTTPoison.post(body, @default_headers) + |> HTTPoison.post(body, @default_headers, @default_options) |> decode_response() end diff --git a/panacea/web/static/js/app.js b/panacea/web/static/js/app.js index 1a46b2ec..e5bd360c 100644 --- a/panacea/web/static/js/app.js +++ b/panacea/web/static/js/app.js @@ -77,4 +77,5 @@ const filenameDisplayElement = document.getElementById('filename-display'); const fileInputElement = document.getElementById('file-input'); fileInputElement.addEventListener('change', function(e) { filenameDisplayElement.value = this.files[0].name; + hideResults(); }); From 4f3b36cd65de37bdd41dd82fa4f8253b860c9153 Mon Sep 17 00:00:00 2001 From: shawa Date: Wed, 8 Mar 2017 14:23:27 +0000 Subject: [PATCH 11/72] Draft new API reference --- asclepius/README.md | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/asclepius/README.md b/asclepius/README.md index 53193c21..af9b9314 100644 --- a/asclepius/README.md +++ b/asclepius/README.md @@ -81,29 +81,31 @@ Endpoints | Request Body | An object containing a list of *drug references*, named `drugs`, where a *drug reference* is either `dinto:DB123` or `chebi:123` | | Returns | A list of DDI objects; its label, its URI, and the identifiers of the two drugs involved | + +### `/uris` + +| | | +|--------------|------------------------------------------------------------------------------------------------------------------| +| Description | For a given list of drug labels, find their `chebi:123/dinto:db123` identifier (to be used when calling `/ddis`) | +| Methods | `POST` | +| Request Body | An object containing a list of drug labels, named `labels` | +| Returns | See below | + #### Example ##### Request Body ```json -{"drugs": ["chebi:421707", "chebi:465284", "dinto:DB00503", "chebi:9342"]} +{"drugs": [ "paracetamol",,,]} ``` ##### Response Body ```json [ - { - "drug_a": "chebi:421707", - "drug_b": "chebi:465284", - "label": "abacavir/ganciclovir DDI", - "uri": "http://purl.obolibrary.org/obo/DINTO_05759" - }, - { - "drug_a": "chebi:421707", - "drug_b": "dinto:DB00503", - "label": "abacavir/ritonavir DDI", - "uri": "http://purl.obolibrary.org/obo/DINTO_11043" + "not_found": ['flat seven up'], + "found": { + "paracetamol": "dinto:db12345" } ] ``` From 5378eeac8a3e9948f1f337cfcc91510d4ffb4f16 Mon Sep 17 00:00:00 2001 From: shawa Date: Wed, 8 Mar 2017 15:20:44 +0000 Subject: [PATCH 12/72] Add labels parameter to drugs dinto query, to filter by name --- asclepius/asclepius/dinto.py | 15 ++++++++++++--- asclepius/asclepius/main.py | 12 ++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/asclepius/asclepius/dinto.py b/asclepius/asclepius/dinto.py index 0de2e931..5f268cde 100644 --- a/asclepius/asclepius/dinto.py +++ b/asclepius/asclepius/dinto.py @@ -58,13 +58,23 @@ def sparqled(*args, **kwargs): @sparql -def all_drugs(): +def drugs(labels=None): + + if labels is not None: + if not isinstance(labels, frozenset): + raise ValueError("for cachability, `labels` must be given as a frozenset") + label_str_literals = [f'"{label}"' for label in labels] + filter = f'FILTER (?label in ({", ".join(label_str_literals)}))' + else: + filter = '' + return f''' {PREFIXES} SELECT ?uri ?label WHERE {{ - ?uri rdfs:subClassOf {PHARMACOLOGICAL_ENTITY}. + ?uri rdfs:subClassOf {PHARMACOLOGICAL_ENTITY} . ?uri rdfs:label ?label + {filter} }} ''' @@ -80,7 +90,6 @@ def all_ddis(): }} ''' - def _valid_drug(drug_identifier): return DRUG_PATTERN.match(drug_identifier) is not None diff --git a/asclepius/asclepius/main.py b/asclepius/asclepius/main.py index 7db9528d..7640ec56 100644 --- a/asclepius/asclepius/main.py +++ b/asclepius/asclepius/main.py @@ -73,5 +73,17 @@ def ddis(): return jsonify(dinto_res) +@app.route('/uris') +def uris(): + """ + """ + params = request.get_json() + if params is None or 'labels' not in params or not params['labels']: + raise InvalidUsage("Expecting {'labels': [...]} with at least two labels") + + labels = params['labels'] + drugs = dinto.drugs(labels) + + drugs = dinto.drugs(labels) if __name__ == '__main__': app.run() From b01cfd31a02ef512394bea9281e8ed633a1e0d59 Mon Sep 17 00:00:00 2001 From: shawa Date: Wed, 8 Mar 2017 15:29:41 +0000 Subject: [PATCH 13/72] Add uris endpoint --- asclepius/README.md | 16 ++++++++++------ asclepius/asclepius/dinto.py | 1 + asclepius/asclepius/main.py | 17 ++++++++++++++--- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/asclepius/README.md b/asclepius/README.md index af9b9314..3561ccc6 100644 --- a/asclepius/README.md +++ b/asclepius/README.md @@ -96,16 +96,20 @@ Endpoints ##### Request Body ```json -{"drugs": [ "paracetamol",,,]} +{"labels": [ "paracetamol", "flat seven up", "cocaine"]} ``` ##### Response Body ```json -[ - "not_found": ['flat seven up'], +{ "found": { - "paracetamol": "dinto:db12345" - } -] + "cocaine": "http://purl.obolibrary.org/obo/CHEBI_27958", + "paracetamol": "http://purl.obolibrary.org/obo/CHEBI_46195" + }, + "not_found": [ + "flat seven up" + ] +} + ``` diff --git a/asclepius/asclepius/dinto.py b/asclepius/asclepius/dinto.py index 5f268cde..51308760 100644 --- a/asclepius/asclepius/dinto.py +++ b/asclepius/asclepius/dinto.py @@ -57,6 +57,7 @@ def sparqled(*args, **kwargs): return sparqled +@lru_cache() @sparql def drugs(labels=None): diff --git a/asclepius/asclepius/main.py b/asclepius/asclepius/main.py index 7640ec56..77fc3290 100644 --- a/asclepius/asclepius/main.py +++ b/asclepius/asclepius/main.py @@ -73,7 +73,7 @@ def ddis(): return jsonify(dinto_res) -@app.route('/uris') +@app.route('/uris', methods=["POST"]) def uris(): """ """ @@ -81,9 +81,20 @@ def uris(): if params is None or 'labels' not in params or not params['labels']: raise InvalidUsage("Expecting {'labels': [...]} with at least two labels") - labels = params['labels'] + labels = frozenset(params['labels']) drugs = dinto.drugs(labels) - drugs = dinto.drugs(labels) + to_uri = {} + for entry in drugs: + to_uri[entry['label']] = entry['uri'] + + missing = labels - set(to_uri.keys()) + + result = { + 'not_found': list(missing), + 'found': to_uri, + } + return jsonify(result) + if __name__ == '__main__': app.run() From fa5a7b8d33f46928beae2bd7cf79c5ce8d89968d Mon Sep 17 00:00:00 2001 From: shawa Date: Wed, 8 Mar 2017 15:50:30 +0000 Subject: [PATCH 14/72] Find DDIs by URL --- asclepius/README.md | 27 +++++++++++++++++++++++++-- asclepius/asclepius/dinto.py | 11 +++-------- asclepius/asclepius/main.py | 15 ++++----------- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/asclepius/README.md b/asclepius/README.md index 3561ccc6..e1188422 100644 --- a/asclepius/README.md +++ b/asclepius/README.md @@ -81,7 +81,29 @@ Endpoints | Request Body | An object containing a list of *drug references*, named `drugs`, where a *drug reference* is either `dinto:DB123` or `chebi:123` | | Returns | A list of DDI objects; its label, its URI, and the identifiers of the two drugs involved | +#### Example +##### Request Body +```json +{ + "drugs": [ + "http://purl.obolibrary.org/obo/DINTO_DB00214", + "http://purl.obolibrary.org/obo/DINTO_DB00519" + ] +} +``` + +##### Response Body +```json +[ + { + "drug_a": "http://purl.obolibrary.org/obo/DINTO_DB00214", + "drug_b": "http://purl.obolibrary.org/obo/DINTO_DB00519", + "label": "torasemide/trandolapril DDI", + "uri": "http://purl.obolibrary.org/obo/DINTO_11031" + } +] +``` ### `/uris` | | | @@ -104,8 +126,9 @@ Endpoints ```json { "found": { - "cocaine": "http://purl.obolibrary.org/obo/CHEBI_27958", - "paracetamol": "http://purl.obolibrary.org/obo/CHEBI_46195" + "http://purl.obolibrary.org/obo/CHEBI_27958": "cocaine", + "http://purl.obolibrary.org/obo/CHEBI_46195": "paracetamol", + }, "not_found": [ "flat seven up" diff --git a/asclepius/asclepius/dinto.py b/asclepius/asclepius/dinto.py index 51308760..0e050291 100644 --- a/asclepius/asclepius/dinto.py +++ b/asclepius/asclepius/dinto.py @@ -91,22 +91,17 @@ def all_ddis(): }} ''' -def _valid_drug(drug_identifier): - return DRUG_PATTERN.match(drug_identifier) is not None - @sparql def ddi_from_drugs(drugs): if not isinstance(drugs, frozenset): raise ValueError("for cachability, `drugs` must be given as a frozenset") - + print(drugs) if len(drugs) < 2: raise ValueError("Need at least 2 drugs to find interactions") - if not all(_valid_drug(drug) for drug in drugs): - raise ValueError("Drugs must be specified as chebi:123 or dinto:DB123") - - drug_search_space = ', '.join(drugs) + quoted = (f'<{uri}>' for uri in drugs) + drug_search_space = ', '.join(quoted) return f''' {PREFIXES} diff --git a/asclepius/asclepius/main.py b/asclepius/asclepius/main.py index 77fc3290..8ba7661e 100644 --- a/asclepius/asclepius/main.py +++ b/asclepius/asclepius/main.py @@ -63,13 +63,6 @@ def ddis(): except ValueError as e: raise InvalidUsage(str(e)) - for ddi in dinto_res: - for drug in ('drug_a', 'drug_b'): - if ddi[drug].startswith(dinto.DINTO_PREFIX): - ddi[drug] = ddi[drug].replace(dinto.DINTO_PREFIX, 'dinto:') - elif ddi[drug].startswith(dinto.CHEBI_PREFIX): - ddi[drug] = ddi[drug].replace(dinto.CHEBI_PREFIX, 'chebi:') - return jsonify(dinto_res) @@ -84,15 +77,15 @@ def uris(): labels = frozenset(params['labels']) drugs = dinto.drugs(labels) - to_uri = {} + to_label = {} for entry in drugs: - to_uri[entry['label']] = entry['uri'] + to_label[entry['uri']] = entry['label'] - missing = labels - set(to_uri.keys()) + missing = labels - set(to_label.values()) result = { 'not_found': list(missing), - 'found': to_uri, + 'found': to_label, } return jsonify(result) From 85dc4ee84dd37df223906931029498713d5f6401 Mon Sep 17 00:00:00 2001 From: c-brenn Date: Wed, 8 Mar 2017 18:16:50 +0000 Subject: [PATCH 15/72] asclepius controller * removes ddi fetching from pml controller * adds endpoint for fetching ddis for a given list of dinto uris * adds endpoint for fetching dinto uris for a given list of drugs --- panacea/lib/panacea/asclepius.ex | 16 +++- panacea/lib/panacea/asclepius/remote.ex | 10 ++- panacea/lib/panacea/asclepius/remote/http.ex | 7 ++ panacea/lib/panacea/asclepius/remote/mock.ex | 35 +++++---- .../controllers/asclepius_controller_test.exs | 73 +++++++++++++++++++ .../test/controllers/pml_controller_test.exs | 30 -------- panacea/test/support/authorized_conn_case.ex | 5 ++ .../web/controllers/asclepius_controller.ex | 34 +++++++++ panacea/web/controllers/pml_controller.ex | 19 ++--- panacea/web/router.ex | 2 + 10 files changed, 169 insertions(+), 62 deletions(-) create mode 100644 panacea/test/controllers/asclepius_controller_test.exs create mode 100644 panacea/web/controllers/asclepius_controller.ex diff --git a/panacea/lib/panacea/asclepius.ex b/panacea/lib/panacea/asclepius.ex index a1b30a85..51769840 100644 --- a/panacea/lib/panacea/asclepius.ex +++ b/panacea/lib/panacea/asclepius.ex @@ -1,5 +1,19 @@ defmodule Panacea.Asclepius do @asclepius_api Keyword.get(Application.get_env(:panacea, :asclepius), :api) - def ddis(drugs), do: @asclepius_api.ddis(drugs) + def ddis(drugs) when is_list(drugs) and length(drugs) >= 2 do + @asclepius_api.ddis(drugs) + end + def ddis(drugs) do + message = "expected a list 2 or more drugs - got #{inspect drugs}" + {:error, {:bad_data, message}} + end + + def uris_for_labels(labels) when is_list(labels) do + @asclepius_api.uris_for_labels(labels) + end + def uris_for_labels(labels) do + message = "expected a list of drug labels - got #{inspect labels}" + {:error, {:bad_data, message}} + end end diff --git a/panacea/lib/panacea/asclepius/remote.ex b/panacea/lib/panacea/asclepius/remote.ex index e8372f51..0c5dc140 100644 --- a/panacea/lib/panacea/asclepius/remote.ex +++ b/panacea/lib/panacea/asclepius/remote.ex @@ -1,7 +1,9 @@ defmodule Panacea.Asclepius.Remote do - @type drug :: String.t - @type reason :: any - @type response :: any + @type dinto_uri :: String.t + @type label :: String.t + @type reason :: any + @type response :: any - @callback ddis([drug]) :: {:ok, response} | {:error, reason} + @callback ddis([dinto_uri]) :: {:ok, response} | {:error, reason} + @callback uris_for_labels([label]) :: {:ok, response} | {:error, reason} end diff --git a/panacea/lib/panacea/asclepius/remote/http.ex b/panacea/lib/panacea/asclepius/remote/http.ex index b1121985..d8bdf13f 100644 --- a/panacea/lib/panacea/asclepius/remote/http.ex +++ b/panacea/lib/panacea/asclepius/remote/http.ex @@ -13,6 +13,13 @@ defmodule Panacea.Asclepius.Remote.HTTP do |> decode_response() end + def uris_for_labels(labels) do + {:ok, body} = %{labels: labels} |> Poison.encode + asclepius_uri("/drugs") + |> HTTPoison.post(body, @default_headers) + |> decode_response() + end + defp asclepius_uri(path) do URI.merge(@asclepius_uri, path) |> to_string() end diff --git a/panacea/lib/panacea/asclepius/remote/mock.ex b/panacea/lib/panacea/asclepius/remote/mock.ex index 042c39dd..3628a127 100644 --- a/panacea/lib/panacea/asclepius/remote/mock.ex +++ b/panacea/lib/panacea/asclepius/remote/mock.ex @@ -4,19 +4,28 @@ defmodule Panacea.Asclepius.Remote.Mock do def ddis(_drugs) do {:ok, [ - %{ - "drug_a" => "chebi:421707", - "drug_b" => "chebi:465284", - "label" => "abacavir/ganciclovir DDI", - "uri" => "http://purl.obolibrary.org/obo/DINTO_05759" - }, - %{ - "drug_a" => "chebi:421707", - "drug_b" => "dinto:DB00503", - "label" => "abacavir/ritonavir DDI", - "uri" => "http://purl.obolibrary.org/obo/DINTO_11043" - } - ] + %{ + "drug_a" => "http://purl.obolibrary.org/obo/DINTO_DB00214", + "drug_b" => "http://purl.obolibrary.org/obo/DINTO_DB00519", + "label" => "torasemide/trandolapril DDI", + "uri" => "http://purl.obolibrary.org/obo/DINTO_11031" + } + ] + } + end + + def uris_for_labels(_labels) do + {:ok, + %{ + "found" => %{ + "http://purl.obolibrary.org/obo/CHEBI_27958" => "cocaine", + "http://purl.obolibrary.org/obo/CHEBI_46195" => "paracetamol", + + }, + "not_found" => [ + "flat seven up" + ] + } } end end diff --git a/panacea/test/controllers/asclepius_controller_test.exs b/panacea/test/controllers/asclepius_controller_test.exs new file mode 100644 index 00000000..40729fdd --- /dev/null +++ b/panacea/test/controllers/asclepius_controller_test.exs @@ -0,0 +1,73 @@ +defmodule Panacea.AsclepiusControllerTest do + use Panacea.AuthorizedConnCase + + describe "AsclepiusController.uris_for_labels/2" do + test "raises an error when no labels are provided", %{conn: conn} do + assert_raise Phoenix.ActionClauseError, fn -> + post conn, asclepius_path(conn, :uris_for_labels) + end + end + + test "returns an error when the labels are invalid", %{conn: conn} do + resp = post conn, asclepius_path(conn, :uris_for_labels), %{labels: "foo"} + + assert resp.status == 422 + end + + test "returns the uris for the given labels", %{conn: conn} do + labels = ["cocaine", "paracetamol", "flat seven up"] + resp = post conn, asclepius_path(conn, :uris_for_labels), %{labels: labels} + + assert resp.status == 200 + assert response_body(resp) |> Map.get("uris") == %{ + "found" => %{ + "http://purl.obolibrary.org/obo/CHEBI_27958" => "cocaine", + "http://purl.obolibrary.org/obo/CHEBI_46195" => "paracetamol", + }, + "not_found" => [ + "flat seven up" + ] + } + end + end + + describe "AsclepiusController.ddis/2" do + test "raises an error when no drugs are provided", %{conn: conn} do + assert_raise Phoenix.ActionClauseError, fn -> + post conn, asclepius_path(conn, :ddis) + end + end + + test "returns an error when the drugs are invalid", %{conn: conn} do + resp = post conn, asclepius_path(conn, :ddis), %{drugs: "foo"} + + assert resp.status == 422 + end + + test "returns an error when fewer than 2 drugs are provided", %{conn: conn} do + drugs = ["http://purl.obolibrary.org/obo/CHEBI_27958"] + resp = post conn, asclepius_path(conn, :ddis), %{drugs: drugs} + + assert resp.status == 422 + end + + test "returns the ddis for the given drugs", %{conn: conn} do + drugs = [ + "http://purl.obolibrary.org/obo/DINTO_DB00214", + "http://purl.obolibrary.org/obo/DINTO_DB00519", + ] + resp = post conn, asclepius_path(conn, :ddis), %{drugs: drugs} + + assert resp.status == 200 + assert response_body(resp) |> Map.get("ddis") == + [ + %{ + "drug_a" => "http://purl.obolibrary.org/obo/DINTO_DB00214", + "drug_b" => "http://purl.obolibrary.org/obo/DINTO_DB00519", + "label" => "torasemide/trandolapril DDI", + "uri" => "http://purl.obolibrary.org/obo/DINTO_11031" + } + ] + end + end +end diff --git a/panacea/test/controllers/pml_controller_test.exs b/panacea/test/controllers/pml_controller_test.exs index a99e3baf..b312f2f0 100644 --- a/panacea/test/controllers/pml_controller_test.exs +++ b/panacea/test/controllers/pml_controller_test.exs @@ -3,10 +3,6 @@ defmodule Panacea.PmlControllerTest do @fixtures_dir "test/fixtures/" - defp response_body(conn) do - Poison.decode!(conn.resp_body) - end - describe "PmlController.upload/2" do @tag :err_highlights @tag :pml_loading @@ -64,31 +60,5 @@ defmodule Panacea.PmlControllerTest do assert conn.status == 200 assert response_body(conn) |> Map.get("drugs") == ["chebi:1234", "dinto:DB1234"] end - - @tag :identify_ddis - @tag :pml_loading - test "identifies DDIs with the drugs from the pml", %{conn: conn} do - filename = "ddis.pml" - file_path = Path.join(@fixtures_dir, filename) - upload = %Plug.Upload{path: file_path, filename: filename} - - conn = post conn, pml_path(conn, :upload), %{upload: %{file: upload}} - - assert conn.status == 200 - assert response_body(conn) |> Map.get("ddis") == [ - %{ - "drug_a" => "chebi:421707", - "drug_b" => "chebi:465284", - "label" => "abacavir/ganciclovir DDI", - "uri" => "http://purl.obolibrary.org/obo/DINTO_05759" - }, - %{ - "drug_a" => "chebi:421707", - "drug_b" => "dinto:DB00503", - "label" => "abacavir/ritonavir DDI", - "uri" => "http://purl.obolibrary.org/obo/DINTO_11043" - } - ] - end end end diff --git a/panacea/test/support/authorized_conn_case.ex b/panacea/test/support/authorized_conn_case.ex index 47cf8e30..7439f523 100644 --- a/panacea/test/support/authorized_conn_case.ex +++ b/panacea/test/support/authorized_conn_case.ex @@ -23,6 +23,11 @@ defmodule Panacea.AuthorizedConnCase do import Panacea.Router.Helpers + + def response_body(conn) do + Poison.decode!(conn.resp_body) + end + # The default endpoint for testing @endpoint Panacea.Endpoint end diff --git a/panacea/web/controllers/asclepius_controller.ex b/panacea/web/controllers/asclepius_controller.ex new file mode 100644 index 00000000..b0fa97c5 --- /dev/null +++ b/panacea/web/controllers/asclepius_controller.ex @@ -0,0 +1,34 @@ +defmodule Panacea.AsclepiusController do + use Panacea.Web, :controller + alias Panacea.Asclepius + + def uris_for_labels(conn, %{"labels" => labels}) do + labels + |> Asclepius.uris_for_labels() + |> respond(:uris, conn) + end + + def ddis(conn, %{"drugs" => drugs}) do + drugs + |> Asclepius.ddis() + |> respond(:ddis, conn) + end + + defp respond({:ok, data}, tag, conn) do + conn + |> put_status(:ok) + |> json(%{tag => data}) + end + + defp respond({:error, {:bad_data, message}}, _tag, conn) do + conn + |> put_status(:unprocessable_entity) + |> json(%{message: message}) + end + + defp respond({:error, reason}, _tag, conn) do + conn + |> put_status(:internal_server_error) + |> json(%{message: inspect(reason)}) + end +end diff --git a/panacea/web/controllers/pml_controller.ex b/panacea/web/controllers/pml_controller.ex index 24320ab3..c9767494 100644 --- a/panacea/web/controllers/pml_controller.ex +++ b/panacea/web/controllers/pml_controller.ex @@ -3,10 +3,9 @@ defmodule Panacea.PmlController do def upload(conn, %{"upload" => %{"file" => %Plug.Upload{path: path}}}) do path - |> File.read - |> validate - |> parse - |> get_ddis + |> File.read() + |> validate() + |> parse() |> respond(conn) end @@ -25,18 +24,10 @@ defmodule Panacea.PmlController do {:error, message} end - defp get_ddis({:ok, drugs}) do - {:ok, ddis} = Panacea.Asclepius.ddis(drugs) - {:ok, %{drugs: drugs, ddis: ddis}} - end - defp get_ddis({:error, message}) do - {:error, message} - end - - defp respond({:ok, response}, conn) do + defp respond({:ok, drugs}, conn) do conn |> put_status(:ok) - |> json(response) + |> json(%{drugs: drugs}) end defp respond({:error, message}, conn) do conn diff --git a/panacea/web/router.ex b/panacea/web/router.ex index 49d2754d..c22c12de 100644 --- a/panacea/web/router.ex +++ b/panacea/web/router.ex @@ -24,5 +24,7 @@ defmodule Panacea.Router do pipe_through :api post "/pml", PmlController, :upload + post "/uris", AsclepiusController, :uris_for_labels + post "/ddis", AsclepiusController, :ddis end end From 82e277286acd64ee281cebc1e42840c83704c3f9 Mon Sep 17 00:00:00 2001 From: c-brenn Date: Wed, 8 Mar 2017 18:38:46 +0000 Subject: [PATCH 16/72] new drug format drugs are now specified in PML using `drug { "..." }`. --- panacea/src/pml_lexer.xrl | 11 +++------- panacea/src/pml_parser.yrl | 20 ++++++++++++----- .../test/controllers/pml_controller_test.exs | 5 ++++- panacea/test/fixtures/ddis.pml | 22 +++++-------------- panacea/test/fixtures/no_ddis.pml | 10 ++++++--- panacea/test/panacea/pml/parser_test.exs | 10 ++++++--- 6 files changed, 41 insertions(+), 37 deletions(-) diff --git a/panacea/src/pml_lexer.xrl b/panacea/src/pml_lexer.xrl index 1f2b375b..eb544b36 100644 --- a/panacea/src/pml_lexer.xrl +++ b/panacea/src/pml_lexer.xrl @@ -1,6 +1,5 @@ Definitions. -DRUG = "(chebi:|dinto:DB)[0-9]+" IDENT = [a-zA-Z0-9_]+ WS = [\s\t\n\r]+ STRING = "[^"]*" @@ -11,6 +10,7 @@ Rules. \{ : {token, {'{', TokenLine}}. \} : {token, {'}', TokenLine}}. +drug : {token, {drug, TokenLine}}. process : {token, {process, TokenLine}}. task : {token, {task, TokenLine}}. action : {token, {action, TokenLine}}. @@ -25,17 +25,12 @@ script : {token, {script, TokenLine}}. tool : {token, {tool, TokenLine}}. input : {token, {input, TokenLine}}. output : {token, {output, TokenLine}}. -{DRUG} : {token, {drug, TokenLine, strip_quotes(TokenChars)}}. {ACTION_TYPE} : {token, {action_type, TokenLine}}. {COMMENT} : skip_token. -{STRING} : {token, {string, TokenLine}}. +{STRING} : {token, {string, TokenLine, TokenChars}}. \. : {token, {dot, TokenLine}}. == : {token, {equals, TokenLine}}. -{IDENT} : {token, {ident, TokenLine, TokenChars}}. +{IDENT} : {token, {ident, TokenLine}}. {WS} : skip_token. Erlang code. - -strip_quotes(Drug) -> - CharList = string:strip(Drug,both,$"), - list_to_binary(CharList). diff --git a/panacea/src/pml_parser.yrl b/panacea/src/pml_parser.yrl index d881637d..a51eec69 100644 --- a/panacea/src/pml_parser.yrl +++ b/panacea/src/pml_parser.yrl @@ -2,7 +2,7 @@ Nonterminals pml primitive_block primitive_list primitive action_block action_attributes action_attribute -expression variable operator. +expression variable operator requires_expr. Terminals @@ -57,7 +57,7 @@ action_attributes -> action_attribute action_attributes : '$1' ++ '$2'. action_attribute -> - 'requires' '{' expression '}' : '$3'. + 'requires' '{' requires_expr '}' : '$3'. action_attribute -> 'provides' '{' expression '}' : []. action_attribute -> @@ -71,8 +71,12 @@ action_attribute -> action_attribute -> 'output' '{' expression '}' : []. -expression -> - 'drug' : extract_drug('$1'). +requires_expr -> + 'drug' '{' 'string' '}' : extract_drug('$3'). + +requires_expr -> + expression : []. + expression -> 'string' : []. @@ -97,5 +101,9 @@ operator -> Erlang code. -extract_drug({_,_,Drug}) -> - [Drug]. +extract_drug({_,_,DrugStr}) -> + [strip_quotes(DrugStr)]. + +strip_quotes(Drug) -> + CharList = string:strip(Drug,both,$"), + list_to_binary(CharList). diff --git a/panacea/test/controllers/pml_controller_test.exs b/panacea/test/controllers/pml_controller_test.exs index b312f2f0..c7d8a7e1 100644 --- a/panacea/test/controllers/pml_controller_test.exs +++ b/panacea/test/controllers/pml_controller_test.exs @@ -58,7 +58,10 @@ defmodule Panacea.PmlControllerTest do conn = post conn, pml_path(conn, :upload), %{upload: %{file: upload}} assert conn.status == 200 - assert response_body(conn) |> Map.get("drugs") == ["chebi:1234", "dinto:DB1234"] + assert response_body(conn) |> Map.get("drugs") == [ + "paracetamol", + "cocaine" + ] end end end diff --git a/panacea/test/fixtures/ddis.pml b/panacea/test/fixtures/ddis.pml index e0852430..4e833370 100644 --- a/panacea/test/fixtures/ddis.pml +++ b/panacea/test/fixtures/ddis.pml @@ -4,28 +4,18 @@ process foo { tool { "pills" } script { "eat the pills" } agent { "patient" } - requires { "chebi:9342" } + requires { + drug { "torasemide" } + } provides { "a cured patient" } } action baz2 { tool { "pills" } script { "eat the pills" } agent { "patient" } - requires { "dinto:DB00503" } - provides { "a cured patient" } - } - action baz3 { - tool { "pills" } - script { "eat the pills" } - agent { "patient" } - requires { "chebi:465284" } - provides { "a cured patient" } - } - action baz4 { - tool { "pills" } - script { "eat the pills" } - agent { "patient" } - requires { "chebi:421707" } + requires { + drug { "trandolapril" } + } provides { "a cured patient" } } } diff --git a/panacea/test/fixtures/no_ddis.pml b/panacea/test/fixtures/no_ddis.pml index 8b840440..80fb72c3 100644 --- a/panacea/test/fixtures/no_ddis.pml +++ b/panacea/test/fixtures/no_ddis.pml @@ -4,15 +4,19 @@ process foo { tool { "pills" } script { "eat the pills" } agent { "patient" } - requires { "chebi:1234" } + requires { + drug { "paracetamol" } + } provides { "a cured patient" } } action baz2 { tool { "pills" } script { "eat the pills" } agent { "patient" } - requires { "dinto:DB1234" } + requires { + drug { "cocaine" } + } provides { "a cured patient" } } } -} +} \ No newline at end of file diff --git a/panacea/test/panacea/pml/parser_test.exs b/panacea/test/panacea/pml/parser_test.exs index 5eb74e96..e99248f6 100644 --- a/panacea/test/panacea/pml/parser_test.exs +++ b/panacea/test/panacea/pml/parser_test.exs @@ -56,21 +56,25 @@ defmodule Panacea.Pml.ParserTest do tool { "pills" } script { "eat the pills" } agent { "patient" } - requires { "chebi:1234" } + requires { + drug { "paracetamol" } + } provides { "a cured patient" } } action baz2 { tool { "pills" } script { "eat the pills" } agent { "patient" } - requires { "dinto:DB1234" } + requires { + drug { "cocaine" } + } provides { "a cured patient" } } } } """ - assert Parser.parse(pml) == {:ok, ["chebi:1234","dinto:DB1234"]} + assert Parser.parse(pml) == {:ok, ["paracetamol", "cocaine"]} end end end From 46e4b3c83e0633e5cd14f0756a2beab32c83214d Mon Sep 17 00:00:00 2001 From: c-brenn Date: Wed, 8 Mar 2017 19:34:39 +0000 Subject: [PATCH 17/72] make frontend work ugly code but works :older-woman: --- panacea/lib/panacea/asclepius/remote/http.ex | 2 +- panacea/web/static/js/app.js | 45 +++++++++++++++++--- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/panacea/lib/panacea/asclepius/remote/http.ex b/panacea/lib/panacea/asclepius/remote/http.ex index d8bdf13f..52139d6b 100644 --- a/panacea/lib/panacea/asclepius/remote/http.ex +++ b/panacea/lib/panacea/asclepius/remote/http.ex @@ -15,7 +15,7 @@ defmodule Panacea.Asclepius.Remote.HTTP do def uris_for_labels(labels) do {:ok, body} = %{labels: labels} |> Poison.encode - asclepius_uri("/drugs") + asclepius_uri("/uris") |> HTTPoison.post(body, @default_headers) |> decode_response() end diff --git a/panacea/web/static/js/app.js b/panacea/web/static/js/app.js index e5bd360c..cde80dc4 100644 --- a/panacea/web/static/js/app.js +++ b/panacea/web/static/js/app.js @@ -12,17 +12,48 @@ async function submitFile() { hideFileForm(); hideResults(); try { - const response = await fetch(this.action, { + const drugs_response = await fetch(this.action, { method: 'POST', body: new FormData(this), credentials: 'same-origin', headers: new Headers({authorization: apiAccessToken}) }); - if (response.ok) { - renderSuccess(await response.json()); + if (drugs_response.ok) { + const data = await drugs_response.json(); + const drugs = data.drugs; + + const uris_response = await fetch("/api/uris", { + method: 'POST', + body: JSON.stringify({labels: drugs}), + credentials: 'same-origin', + headers: new Headers({authorization: apiAccessToken, "Content-Type": "application/json"}) + }); + + if (uris_response.ok) { + const data = await uris_response.json(); + const uris = Object.keys(data.uris.found); + console.log(uris); + + const ddis_response = await fetch("/api/ddis", { + method: 'POST', + body: JSON.stringify({drugs: uris}), + credentials: 'same-origin', + headers: new Headers({authorization: apiAccessToken, "Content-Type": "application/json"}) + }); + + if (ddis_response.ok) { + const data = await ddis_response.json(); + const ddis = data.ddis; + renderSuccess(drugs, ddis); + } else { + renderError(await ddis_response.json()); + } + } else { + renderError(await uris_response.json()); + } } else { - renderError(await response.json()); + renderError(await drugs_response.json()); } this.reset(); } catch (err) { @@ -39,12 +70,12 @@ const hideResults = () => { successElement.classList.add('hidden'); } -const renderSuccess = data => { +const renderSuccess = (drugs, ddis) => { const drugsResultMessage = document.getElementById('success-result-message'); const ddisResultMessage = document.getElementById('success-ddis-message'); - drugsResultMessage.innerHTML = JSON.stringify(data.drugs); - ddisResultMessage.innerHTML = JSON.stringify(data.ddis); + drugsResultMessage.innerHTML = JSON.stringify(drugs); + ddisResultMessage.innerHTML = JSON.stringify(ddis); errorElement.classList.add('hidden'); successElement.classList.remove('hidden'); From a32d19945ef3666c780d87d99e994d09c81a4850 Mon Sep 17 00:00:00 2001 From: c-brenn Date: Wed, 8 Mar 2017 19:36:53 +0000 Subject: [PATCH 18/72] rebase --- panacea/lib/panacea/asclepius/remote/http.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panacea/lib/panacea/asclepius/remote/http.ex b/panacea/lib/panacea/asclepius/remote/http.ex index 52139d6b..a2313ac0 100644 --- a/panacea/lib/panacea/asclepius/remote/http.ex +++ b/panacea/lib/panacea/asclepius/remote/http.ex @@ -16,7 +16,7 @@ defmodule Panacea.Asclepius.Remote.HTTP do def uris_for_labels(labels) do {:ok, body} = %{labels: labels} |> Poison.encode asclepius_uri("/uris") - |> HTTPoison.post(body, @default_headers) + |> HTTPoison.post(body, @default_headers, @default_options) |> decode_response() end From c56cd9fb8bd0aebd4ca5bdd7af382fd23d690572 Mon Sep 17 00:00:00 2001 From: c-brenn Date: Wed, 8 Mar 2017 19:47:35 +0000 Subject: [PATCH 19/72] removes console.log --- panacea/web/static/js/app.js | 1 - 1 file changed, 1 deletion(-) diff --git a/panacea/web/static/js/app.js b/panacea/web/static/js/app.js index cde80dc4..950252f5 100644 --- a/panacea/web/static/js/app.js +++ b/panacea/web/static/js/app.js @@ -33,7 +33,6 @@ async function submitFile() { if (uris_response.ok) { const data = await uris_response.json(); const uris = Object.keys(data.uris.found); - console.log(uris); const ddis_response = await fetch("/api/ddis", { method: 'POST', From b50408fd1d031eaac6085fb192974a4dbf13f1ca Mon Sep 17 00:00:00 2001 From: c-brenn Date: Wed, 8 Mar 2017 20:21:14 +0000 Subject: [PATCH 20/72] headers and camel case --- panacea/web/static/js/app.js | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/panacea/web/static/js/app.js b/panacea/web/static/js/app.js index 950252f5..77849b95 100644 --- a/panacea/web/static/js/app.js +++ b/panacea/web/static/js/app.js @@ -2,6 +2,11 @@ import "babel-polyfill"; import "phoenix_html"; const apiAccessToken = document.getElementById('api-access-token').content; +const defaultHeaders = new Headers({ + "Authorization": apiAccessToken, + "Content-Type": "application/json", + "Accept": "application/json, text/plain, */*" +}); document.getElementById('file-form').addEventListener('submit', e => { e.preventDefault(); @@ -12,47 +17,47 @@ async function submitFile() { hideFileForm(); hideResults(); try { - const drugs_response = await fetch(this.action, { + const drugsResponse = await fetch(this.action, { method: 'POST', body: new FormData(this), credentials: 'same-origin', headers: new Headers({authorization: apiAccessToken}) }); - if (drugs_response.ok) { - const data = await drugs_response.json(); + if (drugsResponse.ok) { + const data = await drugsResponse.json(); const drugs = data.drugs; - const uris_response = await fetch("/api/uris", { + const urisResponse = await fetch("/api/uris", { method: 'POST', body: JSON.stringify({labels: drugs}), credentials: 'same-origin', - headers: new Headers({authorization: apiAccessToken, "Content-Type": "application/json"}) + headers: defaultHeaders }); - if (uris_response.ok) { - const data = await uris_response.json(); + if (urisResponse.ok) { + const data = await urisResponse.json(); const uris = Object.keys(data.uris.found); - const ddis_response = await fetch("/api/ddis", { + const ddisResponse = await fetch("/api/ddis", { method: 'POST', body: JSON.stringify({drugs: uris}), credentials: 'same-origin', - headers: new Headers({authorization: apiAccessToken, "Content-Type": "application/json"}) + headers: defaultHeaders }); - if (ddis_response.ok) { - const data = await ddis_response.json(); + if (ddisResponse.ok) { + const data = await ddisResponse.json(); const ddis = data.ddis; renderSuccess(drugs, ddis); } else { - renderError(await ddis_response.json()); + renderError(await ddisResponse.json()); } } else { - renderError(await uris_response.json()); + renderError(await urisResponse.json()); } } else { - renderError(await drugs_response.json()); + renderError(await drugsResponse.json()); } this.reset(); } catch (err) { From 5f498f87913ee6842461799903884bd50c2d1a35 Mon Sep 17 00:00:00 2001 From: c-brenn Date: Wed, 8 Mar 2017 20:34:23 +0000 Subject: [PATCH 21/72] updates documentation --- CHANGELOG.md | 3 +++ FEATURES.md | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3c933b8..e39443fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ All notable changes to this project will be documented in this file. separate documents. - Updated user interface with loading icon to provide visual representation of the request's duration. +- The representation of drugs in PML. Our initial representation meant that + users had to look up the CHEBI/DINTO identifier for a drug. Now users can + specify drugs with a new `drug { "name" }` construct. ## Added - install-docker.sh for easy docker installation on Ubuntu 16.0.4. Part of diff --git a/FEATURES.md b/FEATURES.md index 4dcb5c2c..65fb9d61 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -110,9 +110,9 @@ See [On-Screen PML Reporting](#on-screen-pml-reporting---complete) ### Description The system must identify drug-drug interactions between any drugs in a PML file. To do this the system must be able to identify drugs in a given PML file. -We have chosen to use CHEBI and DINTO identifiers to denote drugs in PML. These -identifiers take the form `chebi:\d+` or `dinto:DB\d+` where `\d+` is any -sequence of digits. +Users can specify a drug using the following construct: + +`drug { "drug_name" }` Drugs to be administered to patients must be placed in `requires` blocks within the PML document. For example @@ -120,7 +120,7 @@ the PML document. For example ``` process foo { task bar { - requires { "chebi:1234" } + requires { drug { "paracetamol" } } } } ``` From b05ff2447fa55e3b03d1b8b5716e9bb87a3ef0bf Mon Sep 17 00:00:00 2001 From: Peter Meehan Date: Wed, 8 Mar 2017 20:52:36 +0000 Subject: [PATCH 22/72] added live UI updating to the request chain :link: --- panacea/web/static/js/app.js | 55 ++++++++++++++--------- panacea/web/templates/page/index.html.eex | 29 +++++------- 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/panacea/web/static/js/app.js b/panacea/web/static/js/app.js index 950252f5..5db027e5 100644 --- a/panacea/web/static/js/app.js +++ b/panacea/web/static/js/app.js @@ -22,6 +22,7 @@ async function submitFile() { if (drugs_response.ok) { const data = await drugs_response.json(); const drugs = data.drugs; + displayDrugs(drugs); const uris_response = await fetch("/api/uris", { method: 'POST', @@ -44,15 +45,15 @@ async function submitFile() { if (ddis_response.ok) { const data = await ddis_response.json(); const ddis = data.ddis; - renderSuccess(drugs, ddis); + displayDdis(ddis); } else { - renderError(await ddis_response.json()); + displayError(await ddis_response.json()); } } else { - renderError(await uris_response.json()); + displayError(await uris_response.json()); } } else { - renderError(await drugs_response.json()); + displayError(await drugs_response.json()); } this.reset(); } catch (err) { @@ -61,32 +62,42 @@ async function submitFile() { restoreFileForm(); } -const successElement = document.getElementById('success'); -const errorElement = document.getElementById('error'); +const drugsPanel = document.getElementById('drugs-panel'); +const ddisPanel = document.getElementById('ddis-panel'); +const errorPanel = document.getElementById('error-panel'); const hideResults = () => { - errorElement.classList.add('hidden'); - successElement.classList.add('hidden'); + drugsPanel.classList.add('hidden'); + ddisPanel.classList.add('hidden'); + errorPanel.classList.add('hidden'); } -const renderSuccess = (drugs, ddis) => { - const drugsResultMessage = document.getElementById('success-result-message'); - const ddisResultMessage = document.getElementById('success-ddis-message'); +const displayDrugs = drugs => { + const drugsTextElement = document.getElementById('drugs-text'); + drugsTextElement.innerHTML = JSON.stringify(drugs); - drugsResultMessage.innerHTML = JSON.stringify(drugs); - ddisResultMessage.innerHTML = JSON.stringify(ddis); + errorPanel.classList.add('hidden'); + drugsPanel.classList.remove('hidden'); + ddisPanel.classList.add('hidden'); +} + +const displayDdis = ddis => { + const ddisTextElement = document.getElementById('ddis-text'); + ddisTextElement.innerHTML = JSON.stringify(ddis); - errorElement.classList.add('hidden'); - successElement.classList.remove('hidden'); -}; + errorPanel.classList.add('hidden'); + drugsPanel.classList.remove('hidden'); + ddisPanel.classList.remove('hidden'); +} -const renderError = data => { - const errorResultMessage = document.getElementById('error-result-message'); - errorResultMessage.innerHTML = data.message; +const displayError = error => { + const errorTextElement = document.getElementById('error-text'); + errorTextElement.innerHTML = JSON.stringify(error); - errorElement.classList.remove('hidden'); - successElement.classList.add('hidden'); -}; + errorPanel.classList.remove('hidden'); + drugsPanel.classList.add('hidden'); + ddisPanel.classList.add('hidden'); +} const formElement = document.getElementById('file-form'); const loadingElement = document.getElementById('loading-container'); diff --git a/panacea/web/templates/page/index.html.eex b/panacea/web/templates/page/index.html.eex index 19d95434..02cc3c00 100644 --- a/panacea/web/templates/page/index.html.eex +++ b/panacea/web/templates/page/index.html.eex @@ -17,24 +17,19 @@
<% end %> -