├── .github └── workflows │ ├── cleanup.yml │ └── run_tests.yml ├── .gitignore ├── .pylintrc ├── .style.yapf ├── AUTHORS ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app ├── .browserslistrc ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── README.md ├── babel.config.js ├── jsconfig.json ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── img │ │ ├── arrowL.png │ │ └── arrowR.png │ └── index.html ├── src │ ├── App.vue │ ├── assets │ │ └── logo.png │ ├── components │ │ ├── CopyButton.vue │ │ ├── DownloadResults.vue │ │ ├── EntityTable.vue │ │ ├── FloatingModal.vue │ │ ├── HeaderNav.vue │ │ ├── LoadingSpinner.vue │ │ ├── MainFooter.vue │ │ ├── ModalKetcher.vue │ │ └── ReactionCard.vue │ ├── main.js │ ├── router │ │ └── index.js │ ├── styles │ │ ├── table.sass │ │ ├── tabs.sass │ │ ├── transition.sass │ │ └── vars.sass │ ├── utils │ │ ├── amount.js │ │ ├── base64.js │ │ ├── conditions.js │ │ └── outcomes.js │ └── views │ │ ├── About.vue │ │ ├── Home.vue │ │ ├── browse │ │ ├── MainBrowse.vue │ │ └── selected-set │ │ │ └── MainSelectedSet.vue │ │ ├── contribute │ │ ├── DatasetsView.vue │ │ ├── EnumerateView.vue │ │ ├── MainContribute.vue │ │ └── UploadView.vue │ │ ├── dataset-view │ │ ├── ChartView.vue │ │ ├── MainDatasetView.vue │ │ └── SearchResults.vue │ │ ├── reaction-view │ │ ├── CompoundView.vue │ │ ├── ConditionsView.vue │ │ ├── EventsView.vue │ │ ├── MainReactionView.vue │ │ ├── NotesView.vue │ │ ├── ObservationsView.vue │ │ ├── OutcomesView.vue │ │ ├── ProvenanceView.vue │ │ ├── SetupView.vue │ │ └── WorkupsView.vue │ │ ├── search │ │ ├── MainSearch.vue │ │ ├── SearchItemList.vue │ │ ├── SearchOptions.vue │ │ └── SearchResults.vue │ │ └── viewKetcher │ │ └── MainKetcher.vue └── vue.config.js ├── copilot ├── .workspace └── front-end │ └── manifest.yml ├── dump.rdb ├── format.sh ├── ord_interface ├── Dockerfile ├── Dockerfile.dockerignore ├── __init__.py ├── about.html ├── api │ ├── README.md │ ├── __init__.py │ ├── conftest.py │ ├── main.py │ ├── queries.py │ ├── queries_test.py │ ├── search.py │ ├── search_test.py │ ├── testdata │ │ ├── ord_dataset-89b083710e2d441aa0040c361d63359f.pb.gz │ │ ├── ord_dataset-b440f8c90b6343189093770060fc4098.pb.gz │ │ └── ord_dataset-fc83743b978f4deea7d6856deacbfe53.pb.gz │ ├── testing.py │ ├── view.py │ └── view_test.py ├── build_test_database.sh ├── client │ ├── __init__.py │ └── build_database.py ├── docker-compose.yml ├── editor │ ├── README.md │ ├── build.sh │ ├── css │ │ └── reaction.css │ ├── db │ │ └── 680b0d9fe649417cb092d790907bd5a5 │ │ │ ├── empty.pbtxt │ │ │ ├── full.pbtxt │ │ │ └── ord-nielsen-example.pbtxt │ ├── html │ │ ├── dataset.html │ │ ├── datasets.html │ │ ├── login.html │ │ ├── reaction.html │ │ ├── submissions.html │ │ └── template.html │ ├── img │ │ └── template-editor-how.png │ ├── js │ │ ├── amounts.js │ │ ├── codes.js │ │ ├── compounds.js │ │ ├── conditions.js │ │ ├── crudes.js │ │ ├── data.js │ │ ├── dataset.js │ │ ├── electro.js │ │ ├── enums.js │ │ ├── flow.js │ │ ├── identifiers.js │ │ ├── illumination.js │ │ ├── inputs.js │ │ ├── notes.js │ │ ├── observations.js │ │ ├── outcomes.js │ │ ├── pressure.js │ │ ├── products.js │ │ ├── provenance.js │ │ ├── reaction.js │ │ ├── setups.js │ │ ├── stirring.js │ │ ├── temperature.js │ │ ├── test.js │ │ ├── uploads.js │ │ ├── utils.js │ │ └── workups.js │ ├── py │ │ ├── migrate.py │ │ ├── serve.py │ │ ├── serve_test.py │ │ └── testdata │ │ │ ├── nielsen_fig1.csv │ │ │ ├── nielsen_fig1_dataset.pbtxt │ │ │ └── nielsen_fig1_template.pbtxt │ └── schema.sql ├── interface.py ├── nginx.conf ├── run_tests.sh ├── start_app.sh └── visualization │ ├── __init__.py │ ├── drawing.py │ ├── filters.py │ ├── generate_text.py │ ├── generate_text_test.py │ ├── template.html │ ├── template.txt │ └── testdata │ └── reaction.pbtxt ├── pyproject.toml └── setup.py /.github/workflows/cleanup.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Open Reaction Database Project Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: Lint 16 | 17 | on: [ pull_request, push ] 18 | 19 | jobs: 20 | check_licenses: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v3 24 | - uses: actions/setup-go@v3 25 | with: 26 | go-version: '>=1.16' 27 | - name: Install dependencies 28 | run: go install github.com/google/addlicense@latest 29 | - name: addlicense 30 | run: addlicense -check -c "Open Reaction Database Project Authors" -l apache . 31 | 32 | check_python: 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@v3 36 | - uses: actions/setup-python@v4 37 | with: 38 | python-version: '3.10' 39 | - name: Install ord-interface 40 | run: | 41 | python -m pip install --upgrade pip 42 | python -m pip install wheel 43 | python -m pip install .[tests] 44 | - name: Run black 45 | run: | 46 | black --check . 47 | - name: Run isort 48 | run: | 49 | isort --check . 50 | - name: Run pylint 51 | run: | 52 | pylint ord_interface *.py 53 | - name: Run pytype 54 | run: | 55 | pytype -j auto 56 | 57 | check_javascript: 58 | runs-on: ubuntu-latest 59 | steps: 60 | - uses: actions/checkout@v3 61 | - name: clang-format 62 | run: | 63 | sudo apt update && sudo apt install clang-format --yes 64 | # NOTE(kearnes): Run clang-format before installing anything with npm. 65 | find . -name '*.js' -exec clang-format -n -Werror {} + 66 | -------------------------------------------------------------------------------- /.github/workflows/run_tests.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Open Reaction Database Project Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: CI 16 | 17 | on: [ pull_request, push ] 18 | 19 | env: 20 | # See https://dev.to/dtinth/caching-docker-builds-in-github-actions-which-approach-is-the-fastest-a-research-18ei. 21 | CACHE_TARGET: "docker.pkg.github.com/open-reaction-database/ord-interface/interface-cache" 22 | 23 | 24 | jobs: 25 | test_ord_interface: 26 | strategy: 27 | matrix: 28 | os: ["ubuntu-latest", "macos-14"] 29 | python-version: ["3.10", "3.11", "3.12"] 30 | runs-on: ${{ matrix.os }} 31 | env: 32 | PGDATA: $GITHUB_WORKSPACE/rdkit-postgres 33 | steps: 34 | - uses: actions/checkout@v4 35 | - uses: conda-incubator/setup-miniconda@v3 36 | with: 37 | miniconda-version: 'latest' 38 | - name: Setup PostgreSQL 39 | shell: bash -l {0} 40 | run: | 41 | # NOTE(skearnes): conda is only used for postgres (not python). 42 | conda install -c conda-forge rdkit-postgresql 43 | initdb 44 | - uses: actions/setup-python@v4 45 | with: 46 | python-version: ${{ matrix.python-version }} 47 | - name: actions-setup-redis 48 | uses: shogo82148/actions-setup-redis@v1.35.0 49 | - name: Install ord_interface 50 | run: | 51 | python -m pip install --upgrade pip 52 | python -m pip install wheel 53 | python -m pip install .[tests] 54 | - name: Run tests 55 | shell: bash -l {0} 56 | run: | 57 | coverage erase 58 | pytest -vv --cov=ord_interface --durations=20 --ignore=ord_interface/editor 59 | coverage xml 60 | - uses: codecov/codecov-action@v1 61 | 62 | test_app: 63 | strategy: 64 | matrix: 65 | python-version: ["3.10", "3.11", "3.12"] 66 | # NOTE(skearnes): Docker is not supported on macOS GitLab runners. 67 | # NOTE(skearnes): ubuntu-latest AppArmor doesn't play nicely with puppeteer. 68 | runs-on: ubuntu-22.04 69 | steps: 70 | - uses: actions/checkout@v4 71 | - uses: actions/setup-python@v4 72 | with: 73 | python-version: ${{ matrix.python-version }} 74 | - uses: actions/setup-node@v3 75 | - name: actions-setup-redis 76 | uses: shogo82148/actions-setup-redis@v1.35.0 77 | - name: Setup docker cache 78 | run: | 79 | docker login docker.pkg.github.com -u "${GITHUB_ACTOR}" --password="${{ secrets.GITHUB_TOKEN }}" 80 | docker pull "${CACHE_TARGET}" 81 | - name: Install ord-interface 82 | run: | 83 | python -m pip install --upgrade pip 84 | python -m pip install wheel 85 | python -m pip install .[tests] 86 | - name: Install non-python test dependencies 87 | run: | 88 | cd "${GITHUB_WORKSPACE}/ord_interface" 89 | npm install puppeteer 90 | - name: Run tests 91 | run: | 92 | cd "${GITHUB_WORKSPACE}/ord_interface" 93 | ./run_tests.sh "--cache-from=${CACHE_TARGET}" 94 | - name: Update docker cache 95 | run: | 96 | docker tag openreactiondatabase/ord-interface "${CACHE_TARGET}" 97 | docker push "${CACHE_TARGET}" 98 | # NOTE(kearnes): Actions in forks cannot update the cache. 99 | if: ${{ ! github.event.pull_request.head.repo.fork }} 100 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # PyCharm files. 2 | .idea/ 3 | # Generated protocol buffer wrappers. 4 | *_pb2.py 5 | # Generated docs output. 6 | docs/_build/ 7 | # Misc python. 8 | __pycache__ 9 | .coverage 10 | .coverage.* 11 | *.egg-info 12 | .ipynb_checkpoints 13 | # vim swap files 14 | *.sw[a-z] 15 | # build output 16 | **/build 17 | /dist 18 | # Folder tracking files 19 | .DS_Store 20 | # Vendored ketcher. 21 | **/ketcher 22 | **/standalone 23 | # Node stuff. 24 | **/node_modules 25 | 26 | # React dependencies 27 | **/.pnp 28 | **/.pnp.js 29 | 30 | # testing 31 | **/coverage 32 | 33 | # misc 34 | **/.DS_Store 35 | **/.env.local 36 | **/.env.development.local 37 | **/.env.test.local 38 | **/.env.production.local 39 | 40 | **/npm-debug.log* 41 | **/yarn-debug.log* 42 | **/yarn-error.log* 43 | 44 | # VSCode. 45 | .vscode/ 46 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of Open Reaction Database authors for copyright purposes. 2 | # 3 | # Names should be added to this file as: 4 | # Name or Organization 5 | # The email address is not required for organizations. 6 | 7 | Connor Coley 8 | Steven Kearnes 9 | Michael Maser 10 | Google LLC 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ord-interface 2 | Web interface and api for the Open Reaction Database 3 | ## Structure 4 | - **/app** - Contains the Vue single page application 5 | - **/public** - Static SPA assets such as index.html 6 | - **/src** - Vue source code that gets compiled 7 | - **/assets** - Images and other static assets 8 | - **/components** - Reusable Vue components that can be included elsewhere 9 | - **/router** - Routing files 10 | - **/styles** - Static .sass files that can be reused throughout the project 11 | - **/utils** - Static helper .js files 12 | - **/views** - Routed Vue components, aka "pages" 13 | - **/browse** - Main browse page of the different reaction sets 14 | - **/reaction-view** - Display of a single reaction from search results 15 | - **/search** - Search interface and results 16 | - **/ord_interface** - Contains the Flask app API and legacy interface 17 | - **/client** - endpoints for the browse and search functionality 18 | - **/editor** - endpoints for the reaction submission functionality 19 | - **/visualization** - helper functions for reaction and molecule visuals 20 | 21 | ## Key Caveats / Constraints 22 | - The Vue front end is dependant on the FastAPI server. Both must be running for the frontend to work. 23 | - The test database must be running on port 5432. 24 | - You will need to download the Ketcher interface and extract into appropriate folder (see instructions below) for 25 | parts of the interface to work. 26 | - To run on Windows, you can use WSL (Windows Subsystem for Linux). If using the Docker option, please ensure that Docker is installed and properly enabled for WSL. 27 | 28 | ## How to Deploy 29 | ...COMING SOON 30 | 31 | ## Setup 32 | 33 | ### 1. Download and install 34 | 35 | ```bash 36 | # you will need git, python, pip, and postgres installed on your computer first 37 | git clone https://github.com/open-reaction-database/ord-interface 38 | cd ord-interface 39 | # If you are running on Apple silicon, use `conda install postgresql` instead. 40 | conda install -c rdkit rdkit-postgresql 41 | pip install -e '.[tests]' 42 | ``` 43 | 44 | ### 2. Set up and run the API 45 | 46 | #### Option 1: Docker 47 | 48 | ```shell 49 | # you will need Docker installed on your computer first 50 | cd ord_interface 51 | ./build_test_database.sh 52 | # If you are running on Apple silicon, append `--build-arg="ARCH=aarch_64"` to the next command. 53 | docker build --file Dockerfile -t openreactiondatabase/ord-interface .. 54 | docker compose up 55 | ``` 56 | 57 | #### Option 2: Local 58 | 59 | ```shell 60 | cd ord_interface/api 61 | ORD_INTERFACE_TESTING=TRUE fastapi dev main.py --port=5000 62 | ``` 63 | 64 | ### 3. Set up and run the Vue SPA 65 | - Download [Ketcher](https://github.com/epam/ketcher/releases/tag/v2.5.1) (Here's a direct link to the [.zip file](https://github.com/epam/ketcher/releases/download/v2.5.1/ketcher-standalone-2.5.1.zip)) and extract the files into `./app/src/ketcher` 66 | - In a new terminal window: 67 | 68 | ```shell 69 | # you will need node.js and npm installed on your computer first 70 | cd ./app 71 | # install node packages 72 | npm i 73 | # run vue spa locally 74 | npm run serve 75 | ``` 76 | 77 | - Open [localhost:8080](http://localhost:8080) to view the Vue ORD interface in your browser. 78 | - The page will reload when you make changes. 79 | - You may also see any lint errors in the console. 80 | 81 | ## Updating the Schema 82 | 83 | ### Minor changes such as ENUM additions 84 | - The Vue app does not require specific modification to show additional ENUM options within existing message fields. We just have to update the package configuration to use the new ord-schema version. 85 | - Update the ord-schema version number in the [install_requires configuration in ./setup.py](https://github.com/open-reaction-database/ord-interface/blob/aa37f628b176ca241d0701b4df5f6fd7b3079bef/setup.py#L48) 86 | - Update the [code section in ./ord_interface/Dockerfile](https://github.com/open-reaction-database/ord-interface/blob/aa37f628b176ca241d0701b4df5f6fd7b3079bef/ord_interface/Dockerfile#L57C1-L60C38) which handles the ord-schema install 87 | - Contact the ORD site administrator to request upload of the new ord-interface version to the staging environment for testing. 88 | 89 | ### Major changes 90 | Where there are more extensive changes to the schema, for example addition of new message types, then the Vue app will need to be modified to show these fields. Please discuss any proposed changes with the ORD team. 91 | -------------------------------------------------------------------------------- /app/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | not ie 11 5 | -------------------------------------------------------------------------------- /app/.eslintignore: -------------------------------------------------------------------------------- 1 | /src/ketcher/** -------------------------------------------------------------------------------- /app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Open Reaction Database Project Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | module.exports = { 18 | root : true, 19 | env : {node : true}, 20 | 'extends' : [ 'plugin:vue/vue3-essential', 'eslint:recommended' ], 21 | parserOptions : {parser : '@babel/eslint-parser'}, 22 | rules : { 23 | 'no-console' : process.env.NODE_ENV === 'production' ? 'warn' : 'off', 24 | 'no-debugger' : process.env.NODE_ENV === 'production' ? 'warn' : 'off', 25 | 'no-unused-vars' : 'warn', 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /app/README.md: -------------------------------------------------------------------------------- 1 | # app 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | npm run lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /app/babel.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Open Reaction Database Project Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | module.exports = { 18 | presets : [ '@vue/cli-plugin-babel/preset' ] 19 | } 20 | -------------------------------------------------------------------------------- /app/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "esnext", 5 | "baseUrl": "./", 6 | "moduleResolution": "node", 7 | "paths": { 8 | "@/*": [ 9 | "src/*" 10 | ] 11 | }, 12 | "lib": [ 13 | "esnext", 14 | "dom", 15 | "dom.iterable", 16 | "scripthost" 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "buffer": "^6.0.3", 12 | "core-js": "^3.8.3", 13 | "d3": "^7.9.0", 14 | "multi-range-slider-vue": "^1.1.4", 15 | "ord-schema": "^0.3.54", 16 | "vue": "^3.4.33", 17 | "vue-router": "^4.0.3", 18 | "vuex": "^4.1.0" 19 | }, 20 | "devDependencies": { 21 | "@babel/core": "^7.12.16", 22 | "@babel/eslint-parser": "^7.12.16", 23 | "@vue/cli-plugin-babel": "~5.0.0", 24 | "@vue/cli-plugin-eslint": "~5.0.0", 25 | "@vue/cli-plugin-router": "~5.0.0", 26 | "@vue/cli-service": "~5.0.0", 27 | "eslint": "^7.32.0", 28 | "eslint-plugin-vue": "^8.0.3", 29 | "pug": "^3.0.2", 30 | "pug-plain-loader": "^1.1.0", 31 | "sass": "^1.32.7", 32 | "sass-loader": "^12.0.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-reaction-database/ord-interface/5b0ccdaeb2ec16435dadc1ea9486b30b223ecc0d/app/public/favicon.ico -------------------------------------------------------------------------------- /app/public/img/arrowL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-reaction-database/ord-interface/5b0ccdaeb2ec16435dadc1ea9486b30b223ecc0d/app/public/img/arrowL.png -------------------------------------------------------------------------------- /app/public/img/arrowR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-reaction-database/ord-interface/5b0ccdaeb2ec16435dadc1ea9486b30b223ecc0d/app/public/img/arrowR.png -------------------------------------------------------------------------------- /app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Open Reaction Database 26 | 30 | 31 | 32 | 42 | 43 | 45 | 47 | 49 | 51 | 52 | 53 | 54 | 55 | 58 |
59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /app/src/App.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 35 | 36 | 44 | 45 | 85 | -------------------------------------------------------------------------------- /app/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-reaction-database/ord-interface/5b0ccdaeb2ec16435dadc1ea9486b30b223ecc0d/app/src/assets/logo.png -------------------------------------------------------------------------------- /app/src/components/CopyButton.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 60 | 61 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/components/DownloadResults.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 69 | 70 | 88 | 89 | 109 | -------------------------------------------------------------------------------- /app/src/components/FloatingModal.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 33 | 34 | 45 | 46 | -------------------------------------------------------------------------------- /app/src/components/HeaderNav.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 23 | 24 | 44 | 45 | 73 | -------------------------------------------------------------------------------- /app/src/components/LoadingSpinner.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 26 | 27 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/components/MainFooter.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | 21 | -------------------------------------------------------------------------------- /app/src/components/ModalKetcher.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 72 | 73 | 94 | 95 | 142 | -------------------------------------------------------------------------------- /app/src/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Open Reaction Database Project Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import {createApp} from 'vue' 18 | import {createStore} from 'vuex' 19 | 20 | import App from './App.vue' 21 | import router from './router' 22 | 23 | const store = createStore({ 24 | state() { 25 | return { storedSet: null, downloadFileType: null, } 26 | }, 27 | mutations : { 28 | setStoredSet(state, data) { state.storedSet = data }, 29 | setDownloadFileType(state, data) { state.downloadFileType = data } 30 | }, 31 | actions : {} 32 | }) 33 | 34 | const app = createApp(App) 35 | app.use(router) 36 | app.use(store) 37 | app.mount('#app') 38 | -------------------------------------------------------------------------------- /app/src/router/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Open Reaction Database Project Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import {createRouter, createWebHistory} from 'vue-router' 18 | 19 | import About from '../views/About.vue' 20 | import Home from '../views/Home.vue' 21 | 22 | const routes = 23 | [ 24 | {path : '/', name : 'home', component : Home}, 25 | {path : '/about', name : 'about', component : About}, 26 | { 27 | path : '/browse', 28 | name : 'browse', 29 | component : () => import('../views/browse/MainBrowse.vue') 30 | }, 31 | { 32 | path : '/browse/set', 33 | name : 'selected-set', 34 | component : () => 35 | import('../views/browse/selected-set/MainSelectedSet.vue') 36 | }, 37 | { 38 | path : '/search', 39 | name : 'search', 40 | component : () => import('../views/search/MainSearch.vue') 41 | }, 42 | { 43 | path : '/dataset/:datasetId', 44 | name : 'dataset-view', 45 | component : () => import('../views/dataset-view/MainDatasetView.vue') 46 | }, 47 | { 48 | path : '/id/:reactionId', 49 | name : 'reaction-view', 50 | component : () => import('../views/reaction-view/MainReactionView.vue') 51 | }, 52 | { 53 | path : '/ketcher', 54 | name : 'ketcher', 55 | component : () => import('../views/viewKetcher/MainKetcher.vue') 56 | }, 57 | ] 58 | 59 | const router = 60 | createRouter({history : createWebHistory(process.env.BASE_URL), routes}) 61 | 62 | export default router 63 | -------------------------------------------------------------------------------- /app/src/styles/table.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Open Reaction Database Project Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @import '@/styles/vars' 18 | 19 | .content 20 | .table-container 21 | background-color: white 22 | width: 90% 23 | margin: auto 24 | border-radius: 0.5rem 25 | display: grid 26 | overflow: hidden 27 | display: grid 28 | .column 29 | border-top: 1px solid $medgrey 30 | padding: 0.5rem 31 | &.label 32 | border-top: none 33 | font-weight: 700 34 | font-size: 1.25rem -------------------------------------------------------------------------------- /app/src/styles/tabs.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Open Reaction Database Project Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @import "./vars" 18 | .tabs 19 | display: flex 20 | column-gap: 0.5rem 21 | row-gap: 0.5rem 22 | margin-bottom: 0.5rem 23 | flex-wrap: wrap 24 | .tab 25 | padding: 0.5rem 1rem 26 | border-radius: 0.25rem 27 | border: 1px solid $medgrey 28 | cursor: pointer 29 | transition: 0.25s 30 | &.selected 31 | background-color: $linkblue 32 | color: white 33 | border-color: $linkblue 34 | cursor: default 35 | &.capitalize 36 | text-transform: capitalize -------------------------------------------------------------------------------- /app/src/styles/transition.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Open Reaction Database Project Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | .expand-enter-active, .expand-leave-active 18 | transition: .5s ease 19 | max-height: 400px 20 | opacity: 1 21 | 22 | .expand-enter-from, .expand-leave-to 23 | max-height: 0 24 | overflow: hidden 25 | opacity: 0 26 | 27 | .expand-enter-to, .expand-leave-from 28 | max-height: 400px 29 | overflow: hidden 30 | opacity: 1 31 | 32 | .fade-enter-active, .fade-leave-active 33 | transition: opacity .25s 34 | 35 | .fade-enter-from, .fade-leave-to 36 | opacity: 0 -------------------------------------------------------------------------------- /app/src/styles/vars.sass: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Open Reaction Database Project Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | $lightgrey: #f8f9fa 18 | $medgrey: #e8e9e3 19 | $darkgrey: #a0a0a0 20 | $linkblue: #0d6efd 21 | $hoverblue: #0a58ca -------------------------------------------------------------------------------- /app/src/utils/amount.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Open Reaction Database Project Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import reaction_pb from "ord-schema" 18 | 19 | export const amountObj = 20 | (amount) => { 21 | if (!amount) 22 | return {} 23 | let units = {}; 24 | let unitCategory = ""; 25 | // determine unit type 26 | if (amount.moles) { 27 | units = reaction_pb.Moles.MolesUnit 28 | unitCategory = "moles" 29 | } else if (amount.volume) { 30 | units = reaction_pb.Volume.VolumeUnit 31 | unitCategory = "volume" 32 | } else if (amount.mass) { 33 | units = reaction_pb.Mass.MassUnit 34 | unitCategory = "mass" 35 | } else if (amount.unmeasured) { 36 | return { unitAmount: "", unitCategory: "unmeasured" } 37 | } 38 | const unitVal = amount[unitCategory].units 39 | const amountVal = amount[unitCategory].value 40 | const precision = amount[unitCategory].precision 41 | return { 42 | unitAmount: amountVal, 43 | unitType: Object.keys(units).find(key => units[key] == unitVal), 44 | unitCategory: unitCategory, 45 | } 46 | } 47 | 48 | export const amountStr = (amountObj) => { 49 | // takes an amountObj from above amountObj function 50 | if (!amountObj.unitAmount || !amountObj.unitType) 51 | return "" 52 | return `${Math.round(amountObj.unitAmount * 1000) / 1000} ${ 53 | amountObj.unitType.toLowerCase()}` 54 | } 55 | -------------------------------------------------------------------------------- /app/src/utils/base64.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Open Reaction Database Project Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import {Buffer} from 'buffer'; 18 | 19 | export default (base64String) => { 20 | // converts a base64 encoded string to Uint8Array so it can be parsed by 21 | // ord-schema js wrapper 22 | const buffer = Buffer.from(base64String, 'base64'); 23 | // See https://stackoverflow.com/a/12101012. 24 | const arrayBuffer = new ArrayBuffer(buffer.length); 25 | const view = new Uint8Array(arrayBuffer); 26 | for (let i = 0; i < buffer.length; ++i) { 27 | view[i] = buffer[i]; 28 | } 29 | return arrayBuffer; 30 | } -------------------------------------------------------------------------------- /app/src/utils/conditions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Open Reaction Database Project Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import reaction_pb from "ord-schema" 18 | 19 | export default { 20 | tempType(tempControlType) { 21 | const controlTypes = reaction_pb.TemperatureConditions.TemperatureControl 22 | .TemperatureControlType 23 | return Object.keys(controlTypes) 24 | .find(key => controlTypes[key] == tempControlType) 25 | }, 26 | tempSetPoint(setpoint) { 27 | if (!setpoint) 28 | return "None" 29 | const tempUnits = reaction_pb.Temperature.TemperatureUnit 30 | const tempUnit = 31 | Object.keys(tempUnits).find(key => tempUnits[key] == setpoint.units) 32 | return `${setpoint.value} (± ${setpoint.precision}) °${ 33 | tempUnit.charAt(0)}` 34 | }, 35 | pressureType(pressControlType) { 36 | const controlTypes = 37 | reaction_pb.PressureConditions.PressureControl.PressureControlType 38 | return Object.keys(controlTypes) 39 | .find(key => controlTypes[key] == pressControlType) 40 | }, 41 | pressureSetPoint(setpoint) { 42 | if (!setpoint) 43 | return "None" 44 | const pressureUnits = reaction_pb.Pressure.PressureUnit 45 | const pressureUnit = 46 | Object.keys(pressureUnits) 47 | .find(key => pressureUnits[key] == setpoint.units) 48 | return `${setpoint.value} (± ${setpoint.precision}) ${ 49 | pressureUnit.toLowerCase()}` 50 | }, 51 | pressureAtmo(atmo) { 52 | const atmoTypes = reaction_pb.PressureConditions.Atmosphere.AtmosphereType 53 | const atmoType = 54 | Object.keys(atmoTypes).find(key => atmoTypes[key] == atmo?.type) 55 | return `${atmoType}${atmo?.details ? `, ${atmo.details}` : ""}` 56 | }, 57 | stirType(stirType) { 58 | const stirTypes = reaction_pb.StirringConditions.StirringMethodType 59 | return Object.keys(stirTypes).find(key => stirTypes[key] == stirType) 60 | }, 61 | stirRate(stirRate) { 62 | const stirRates = reaction_pb.StirringConditions.StirringRate 63 | return Object.keys(stirRates).find(key => stirRates[key] == stirRate) 64 | }, 65 | illumType(illum) { 66 | const illumTypes = reaction_pb.IlluminationConditions.IlluminationType 67 | const illumType = 68 | Object.keys(illumTypes).find(key => illumTypes[key] == illum.type) 69 | return `${illumType}${illum.details ? `: ${illum.details}` : ""}` 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/src/utils/outcomes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Open Reaction Database Project Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import reaction_pb from "ord-schema" 18 | 19 | export default { 20 | formattedTime(timeData) { 21 | if (!timeData) 22 | return null; 23 | const timeUnits = reaction_pb.Time.TimeUnit 24 | const type = 25 | Object.keys(timeUnits).find(key => timeUnits[key] == timeData.units) 26 | return `${timeData.value} ${type.toLowerCase()}${ 27 | timeData.units !== 0 ? "(s)" : ""}` 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 24 | 25 | 30 | 31 | 41 | -------------------------------------------------------------------------------- /app/src/views/browse/MainBrowse.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 42 | 43 | 67 | 68 | 77 | -------------------------------------------------------------------------------- /app/src/views/browse/selected-set/MainSelectedSet.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 80 | 81 | 112 | 113 | 129 | -------------------------------------------------------------------------------- /app/src/views/contribute/DatasetsView.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 53 | 54 | 68 | 69 | -------------------------------------------------------------------------------- /app/src/views/contribute/EnumerateView.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 78 | 79 | 120 | 121 | -------------------------------------------------------------------------------- /app/src/views/contribute/MainContribute.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 57 | 58 | 120 | 121 | -------------------------------------------------------------------------------- /app/src/views/contribute/UploadView.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 68 | 69 | 84 | 85 | -------------------------------------------------------------------------------- /app/src/views/reaction-view/ConditionsView.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 53 | 54 | 134 | 135 | -------------------------------------------------------------------------------- /app/src/views/reaction-view/EventsView.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 30 | 31 | 60 | 61 | -------------------------------------------------------------------------------- /app/src/views/reaction-view/NotesView.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 47 | 48 | 55 | 56 | -------------------------------------------------------------------------------- /app/src/views/reaction-view/ObservationsView.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 31 | 32 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/views/reaction-view/ProvenanceView.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 25 | 26 | 59 | 60 | -------------------------------------------------------------------------------- /app/src/views/reaction-view/SetupView.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 69 | 70 | 98 | 99 | -------------------------------------------------------------------------------- /app/src/views/reaction-view/WorkupsView.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 41 | 42 | 73 | 74 | -------------------------------------------------------------------------------- /app/src/views/search/SearchItemList.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 45 | 46 | 64 | 65 | -------------------------------------------------------------------------------- /app/src/views/viewKetcher/MainKetcher.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 25 | 26 | 35 | 36 | -------------------------------------------------------------------------------- /app/vue.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Open Reaction Database Project Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | const {defineConfig} = require('@vue/cli-service') 18 | const path = require('path') 19 | 20 | module.exports = defineConfig({ 21 | transpileDependencies: true, 22 | chainWebpack: config => { 23 | config.plugin('copy') 24 | .tap(entries => { 25 | entries[0].patterns.push({ 26 | from: path.resolve(__dirname, 'src/ketcher/templates'), 27 | to: path.resolve(__dirname, 'dist/templates'), 28 | toType: 'dir', 29 | noErrorOnMissing: false, 30 | globOptions: {ignore: ['.DS_Store']}, 31 | }) 32 | return entries 33 | }) 34 | }, 35 | devServer: { 36 | proxy: process.env.NODE_ENV === 'development' ? { 37 | "^/api": { 38 | target: "http://0.0.0.0:5000", 39 | changeOrigin: true 40 | } 41 | } : {} 42 | } 43 | }) 44 | -------------------------------------------------------------------------------- /copilot/.workspace: -------------------------------------------------------------------------------- 1 | application: ord-interface 2 | -------------------------------------------------------------------------------- /copilot/front-end/manifest.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Open Reaction Database Project Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # The manifest for the "front-end" service. 16 | # Read the full specification for the "Load Balanced Web Service" type at: 17 | # https://aws.github.io/copilot-cli/docs/manifest/lb-web-service/ 18 | 19 | # Your service name will be used in naming your resources like log groups, ECS services, etc. 20 | name: front-end 21 | type: Load Balanced Web Service 22 | 23 | # Distribute traffic to your service. 24 | http: 25 | # Requests to this path will be forwarded to your service. 26 | # To match all requests you can use the "/" path. 27 | path: '/' 28 | # You can specify a custom health check path. The default is "/". 29 | healthcheck: 30 | path: '/editor/healthcheck' 31 | success_codes: '200,302' 32 | 33 | # Configuration for your containers and service. 34 | image: 35 | build: 36 | dockerfile: ord_interface/Dockerfile 37 | context: . 38 | port: 5001 39 | 40 | cpu: 1024 # Number of CPU units for the task. 41 | memory: 2048 # Amount of memory in MiB used by the task. 42 | count: # Number of tasks that should be running in your service. 43 | range: 44 | min: 1 45 | max: 10 46 | spot_from: 1 47 | cpu_percentage: 70 48 | memory_percentage: 80 49 | requests: 10000 50 | response_time: 2s 51 | 52 | secrets: 53 | GH_CLIENT_ID: /copilot/ord-editor/secrets/GH_CLIENT_ID 54 | GH_CLIENT_SECRET: /copilot/ord-editor/secrets/GH_CLIENT_SECRET 55 | POSTGRES_HOST: /copilot/ord-interface/secrets/POSTGRES_HOST 56 | POSTGRES_USER: /copilot/ord-interface/secrets/POSTGRES_USER 57 | POSTGRES_PASSWORD: /copilot/ord-interface/secrets/POSTGRES_PASSWORD 58 | POSTGRES_DATABASE: /copilot/ord-interface/secrets/POSTGRES_DATABASE 59 | -------------------------------------------------------------------------------- /dump.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-reaction-database/ord-interface/5b0ccdaeb2ec16435dadc1ea9486b30b223ecc0d/dump.rdb -------------------------------------------------------------------------------- /format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2020 Open Reaction Database Project Authors 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # Runs code formatters. 17 | set -ex 18 | # See https://www.ostricher.com/2014/10/the-right-way-to-get-the-directory-of-a-bash-script/. 19 | ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)" 20 | # Add missing license headers. 21 | if command -v go &> /dev/null; then 22 | go install github.com/google/addlicense@latest 23 | "${HOME}/go/bin/addlicense" \ 24 | -c "Open Reaction Database Project Authors" \ 25 | -l apache "${ROOT_DIR}" 26 | else 27 | echo "Please install Go; see https://golang.org/doc/install" 28 | fi 29 | # Format python. 30 | black "${ROOT_DIR}" 31 | # Format javascript. 32 | if command -v clang-format-10 &> /dev/null; then 33 | find "${ROOT_DIR}" -name '*.js' ! -path '*/ketcher/*' ! -path '*/node_modules/*' -exec clang-format-10 --verbose -i {} + 34 | elif command -v clang-format &> /dev/null; then 35 | # NOTE(kearnes): Make sure you have version 10 or higher! 36 | find "${ROOT_DIR}" -name '*.js' ! -path '*/ketcher/*' ! -path '*/node_modules/*' -exec clang-format --verbose -i {} + 37 | else 38 | echo "Please install clang-format:" 39 | echo " Linux: apt install clang-format-10" 40 | echo " MacOS: brew install clang-format" 41 | fi 42 | -------------------------------------------------------------------------------- /ord_interface/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Open Reaction Database Project Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # To build the interface using the current state of ord-interface: 16 | # $ cd path/to/ord-interface/ord_interface 17 | # $ docker build --file=Dockerfile -t openreactiondatabase/ord-interface .. 18 | 19 | FROM python:3.11 20 | 21 | # default-jre is required for running the closure compiler linter. 22 | # For this next line, see: 23 | # https://github.com/geerlingguy/ansible-role-java/issues/64#issuecomment-597132394 24 | RUN mkdir -p /usr/share/man/man1/ 25 | RUN apt-get update \ 26 | && apt-get install -y \ 27 | build-essential \ 28 | default-jre \ 29 | nginx \ 30 | npm \ 31 | procps \ 32 | unzip \ 33 | && apt-get clean \ 34 | && rm -rf /var/lib/apt/lists/* 35 | 36 | # Fetch and build editor dependencies. 37 | # NOTE(kearnes): Do this before COPYing the local state so it can be cached. 38 | WORKDIR /app/ord-interface/ord_interface 39 | RUN wget https://github.com/epam/ketcher/releases/download/v2.5.1/ketcher-standalone-2.5.1.zip \ 40 | && unzip ketcher-standalone-2.5.1.zip 41 | WORKDIR /app/ord-interface/ord_interface/editor 42 | RUN wget https://github.com/google/closure-library/archive/v20200517.tar.gz \ 43 | && tar -xzf v20200517.tar.gz \ 44 | && rm v20200517.tar.gz 45 | RUN wget https://github.com/protocolbuffers/protobuf/releases/download/v3.14.0/protobuf-js-3.14.0.tar.gz \ 46 | && tar -xzf protobuf-js-3.14.0.tar.gz \ 47 | && rm protobuf-js-3.14.0.tar.gz 48 | ARG ARCH=x86_64 49 | RUN wget https://github.com/protocolbuffers/protobuf/releases/download/v3.14.0/protoc-3.14.0-linux-${ARCH}.zip \ 50 | && unzip protoc-3.14.0-linux-${ARCH}.zip 51 | ENV PATH="/app/ord-interface/ord_interface/editor/bin:${PATH}" 52 | RUN wget https://raw.githubusercontent.com/google/closure-compiler/master/contrib/externs/jquery-3.3.js \ 53 | && mkdir -p externs \ 54 | && mv jquery-3.3.js externs 55 | RUN npm install google-closure-compiler@20220803 56 | WORKDIR /app 57 | RUN wget https://github.com/open-reaction-database/ord-schema/archive/refs/tags/v0.3.93.zip \ 58 | && unzip v0.3.93.zip \ 59 | && rm v0.3.93.zip \ 60 | && mv ord-schema-0.3.93 ord-schema 61 | 62 | # Install dependencies. 63 | WORKDIR /app/ord-interface 64 | COPY setup.py README.md ./ 65 | RUN pip install --upgrade pip \ 66 | && pip install gunicorn wheel 67 | # https://stackoverflow.com/a/60511098. 68 | RUN python setup.py egg_info \ 69 | && pip install $(grep -v '^\[' *.egg-info/requires.txt) \ 70 | && rm -rf *.egg-info/ 71 | 72 | # Copy the local state. 73 | COPY ord_interface/editor/js/ ord_interface/editor/js/ 74 | COPY ord_interface/editor/build.sh ord_interface/editor 75 | WORKDIR /app/ord-interface/ord_interface/editor 76 | RUN ./build.sh 77 | WORKDIR /app/ord-interface 78 | # Build the Vue app. 79 | COPY app/ app/ 80 | WORKDIR /app/ord-interface/app 81 | RUN cp -r /app/ord-interface/ord_interface/standalone/ /app/ord-interface/app/src/ketcher \ 82 | && npm install \ 83 | && npm run build \ 84 | && mv dist/ /app/ord-interface/vue 85 | WORKDIR /app/ord-interface 86 | # Copy the rest of the editor files. 87 | COPY ord_interface/editor/ ord_interface/editor/ 88 | # Now copy everything else. 89 | COPY ord_interface/about.html ord_interface/ 90 | COPY ord_interface/interface.py ord_interface/ 91 | COPY ord_interface/api/ ord_interface/api/ 92 | COPY ord_interface/visualization/ ord_interface/visualization/ 93 | RUN pip install . 94 | 95 | # Build and launch the interface. 96 | COPY ord_interface/start_app.sh . 97 | COPY ord_interface/nginx.conf /etc/nginx/nginx.conf 98 | 99 | RUN ln -sf /dev/stderr /var/log/nginx.log \ 100 | && chmod +x start_app.sh 101 | ENTRYPOINT ["/app/ord-interface/start_app.sh"] 102 | -------------------------------------------------------------------------------- /ord_interface/Dockerfile.dockerignore: -------------------------------------------------------------------------------- 1 | app/node_modules 2 | app/src/ketcher 3 | -------------------------------------------------------------------------------- /ord_interface/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Open Reaction Database Project Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /ord_interface/api/README.md: -------------------------------------------------------------------------------- 1 | # Open Reaction Database API 2 | 3 | ## Local development 4 | 5 | Set up your environment by installing postgres with the RDKit cartridge: 6 | 7 | ```shell 8 | conda install postgresql python=3.10 9 | # Note that rdkit-postgresql is currently only available on x86_64. 10 | conda install -c rdkit rdkit-postgresql 11 | cd ord-interface 12 | pip install -e ".[tests]" 13 | ``` 14 | 15 | To start a test server locally, run: 16 | 17 | ```shell 18 | cd ord-interface/ord_interface/api 19 | ORD_INTERFACE_TESTING=TRUE fastapi dev main.py 20 | ``` 21 | 22 | This does a few things: 23 | * Creates a test postgres database (using `testing.postgresql`) 24 | * Populates the database using the datasets in `testdata` 25 | * Launches a FastAPI server (see https://fastapi.tiangolo.com/#run-it) 26 | 27 | To view the API docs, navigate to http://localhost:8000/docs. 28 | 29 | To test with the Vue interface: 30 | 31 | ```shell 32 | # In one terminal session: 33 | cd ord-interface/ord_interface/api 34 | ORD_INTERFACE_TESTING=TRUE fastapi dev main --port=5000 35 | # In a second terminal session: 36 | cd ord-interface/app 37 | wget https://github.com/epam/ketcher/releases/download/v2.5.1/ketcher-standalone-2.5.1.zip 38 | unzip ketcher-standalone-2.5.1.zip 39 | mv standalone src/ketcher 40 | npm install 41 | npm run serve 42 | ``` 43 | -------------------------------------------------------------------------------- /ord_interface/api/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Open Reaction Database Project Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /ord_interface/api/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Open Reaction Database Project Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Pytest fixtures.""" 16 | import os 17 | from contextlib import ExitStack 18 | from typing import AsyncIterator, Iterator 19 | from unittest.mock import patch 20 | 21 | import psycopg 22 | import pytest 23 | import pytest_asyncio 24 | from fastapi.testclient import TestClient 25 | from ord_schema.logging import get_logger 26 | from psycopg import AsyncCursor 27 | from psycopg.rows import dict_row 28 | from testing.postgresql import Postgresql 29 | from testing.redis import RedisServer 30 | 31 | from ord_interface.api.main import app 32 | from ord_interface.api.testing import setup_test_postgres 33 | 34 | logger = get_logger(__name__) 35 | 36 | 37 | @pytest.fixture(name="test_postgres", scope="session") 38 | def test_postgres_fixture() -> Iterator[Postgresql]: 39 | with Postgresql() as postgres: 40 | setup_test_postgres(postgres.url()) 41 | yield postgres 42 | 43 | 44 | @pytest_asyncio.fixture 45 | async def test_cursor(test_postgres) -> AsyncIterator[AsyncCursor]: 46 | async with await psycopg.AsyncConnection.connect( 47 | test_postgres.url(), row_factory=dict_row, options="-c search_path=public,ord" 48 | ) as connection: 49 | await connection.set_read_only(True) 50 | async with connection.cursor() as cursor: 51 | yield cursor 52 | 53 | 54 | @pytest.fixture(scope="session") 55 | def test_client(test_postgres) -> Iterator[TestClient]: 56 | with TestClient(app) as client, RedisServer() as redis_server, patch.dict( 57 | os.environ, {"REDIS_PORT": str(redis_server.dsn()["port"])} 58 | ), ExitStack() as stack: 59 | # NOTE(skearnes): Set ORD_INTERFACE_POSTGRES to use that database instead of a testing.postgresql instance. 60 | # To force the use of testing.postgresl, set ORD_INTERFACE_TESTING=TRUE. 61 | if os.environ.get("ORD_INTERFACE_TESTING", "FALSE") == "FALSE" and not os.environ.get("ORD_INTERFACE_POSTGRES"): 62 | stack.enter_context(patch.dict(os.environ, {"ORD_INTERFACE_POSTGRES": test_postgres.url()})) 63 | logger.debug(f"ORD_INTERFACE_POSTGRES={os.environ['ORD_INTERFACE_POSTGRES']}") 64 | yield client 65 | -------------------------------------------------------------------------------- /ord_interface/api/main.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Open Reaction Database Project Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Open Reaction Database API.""" 16 | 17 | import os 18 | import re 19 | from contextlib import asynccontextmanager 20 | 21 | from fastapi import FastAPI 22 | from testing.postgresql import Postgresql 23 | 24 | from ord_interface.api import search, view 25 | from ord_interface.api.testing import setup_test_postgres 26 | 27 | 28 | @asynccontextmanager 29 | async def lifespan(*args, **kwargs): 30 | """FastAPI lifespan setup; see https://fastapi.tiangolo.com/advanced/events/#lifespan.""" 31 | del args, kwargs # Unused. 32 | if os.getenv("ORD_INTERFACE_TESTING", "FALSE") == "TRUE": 33 | with Postgresql() as postgres: 34 | url = re.sub("postgresql://", "postgresql+psycopg://", postgres.url()) 35 | setup_test_postgres(url) 36 | os.environ["ORD_INTERFACE_POSTGRES"] = postgres.url() 37 | yield 38 | else: 39 | yield 40 | 41 | 42 | app = FastAPI(lifespan=lifespan, root_path="/api") 43 | app.include_router(search.router) 44 | app.include_router(view.router) 45 | -------------------------------------------------------------------------------- /ord_interface/api/search_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Open Reaction Database Project Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Tests for ord_interface.api.search.""" 16 | import gzip 17 | 18 | import pytest 19 | from ord_schema.proto import dataset_pb2 20 | from rdkit import Chem 21 | from tenacity import retry, stop_after_attempt, wait_fixed 22 | 23 | from ord_interface.api.queries import QueryResult 24 | 25 | QUERY_PARAMS = [ 26 | # Single factor queries. 27 | ({"dataset_id": ["ord_dataset-89b083710e2d441aa0040c361d63359f"]}, 24), 28 | ({"reaction_id": ["ord-3f67aa5592fd434d97a577988d3fd241"]}, 1), 29 | ({"reaction_smarts": "[#6]>>[#7]"}, 83), 30 | ({"min_conversion": 50, "max_conversion": 90}, 7), 31 | ({"min_yield": 50, "max_yield": 90}, 51), 32 | ({"doi": ["10.1126/science.1255525"]}, 24), 33 | ({"component": ["[Br]C1=CC=C(C(C)=O)C=C1;input;exact"]}, 10), 34 | ({"component": ["C;input;substructure"]}, 144), 35 | ({"component": ["O[C@@H]1C[C@H](O)C1;input;substructure"], "use_stereochemistry": True}, 20), 36 | ({"component": ["[#6];input;smarts"]}, 144), 37 | ({"component": ["CC=O;input;similar"], "similarity": 0.5}, 0), 38 | ({"component": ["CC=O;input;similar"], "similarity": 0.05}, 120), 39 | # Multi-factor queries. 40 | ( 41 | { 42 | "min_yield": 50, 43 | "max_yield": 90, 44 | "component": ["[Br]C1=CC=C(C(C)=O)C=C1;input;exact", "CC(C)(C)OC(=O)NC;input;substructure"], 45 | }, 46 | 7, 47 | ), 48 | ] 49 | 50 | 51 | @pytest.mark.parametrize("params,num_expected", QUERY_PARAMS) 52 | def test_query(test_client, params, num_expected): 53 | response = test_client.get("/api/query", params=params) 54 | response.raise_for_status() 55 | assert len(response.json()) == num_expected 56 | 57 | 58 | def test_get_reaction(test_client): 59 | response = test_client.get("/api/reaction", params={"reaction_id": "ord-3f67aa5592fd434d97a577988d3fd241"}) 60 | response.raise_for_status() 61 | result = QueryResult(**response.json()) 62 | assert result.dataset_id == "ord_dataset-89b083710e2d441aa0040c361d63359f" 63 | assert result.reaction.reaction_id == result.reaction_id 64 | 65 | 66 | def test_get_reactions(test_client): 67 | response = test_client.post("/api/reactions", json={"reaction_ids": ["ord-3f67aa5592fd434d97a577988d3fd241"]}) 68 | response.raise_for_status() 69 | reactions = response.json() 70 | assert len(reactions) == 1 71 | assert reactions[0]["dataset_id"] == "ord_dataset-89b083710e2d441aa0040c361d63359f" 72 | 73 | 74 | def test_get_datasets(test_client): 75 | response = test_client.get("/api/datasets") 76 | response.raise_for_status() 77 | dataset_info = response.json() 78 | assert len(dataset_info) == 3 79 | 80 | 81 | def test_get_molfile(test_client): 82 | response = test_client.get("/api/molfile", params={"smiles": "C(=O)N"}) 83 | response.raise_for_status() 84 | assert Chem.MolToSmiles(Chem.MolFromMolBlock(response.json())) == "NC=O" 85 | 86 | 87 | def test_get_search_results(test_client): 88 | response = test_client.post( 89 | "/api/download_search_results", json={"reaction_ids": ["ord-3f67aa5592fd434d97a577988d3fd241"]} 90 | ) 91 | response.raise_for_status() 92 | dataset = dataset_pb2.Dataset.FromString(gzip.decompress(response.read())) 93 | assert dataset.name == "ORD Search Results" 94 | assert len(dataset.reactions) == 1 95 | assert dataset.reactions[0].reaction_id == "ord-3f67aa5592fd434d97a577988d3fd241" 96 | 97 | 98 | @retry(stop=stop_after_attempt(5), wait=wait_fixed(1)) 99 | def wait_for_task(client, task_id) -> list[QueryResult]: 100 | response = client.get("/api/fetch_query_result", params={"task_id": task_id}) 101 | if response.status_code != 200: 102 | raise ValueError(response) 103 | return response.json() 104 | 105 | 106 | @pytest.mark.parametrize("params,num_expected", QUERY_PARAMS) 107 | def test_query_async(test_client, params, num_expected): 108 | response = test_client.get("/api/submit_query", params=params) 109 | response.raise_for_status() 110 | assert len(wait_for_task(test_client, response.json())) == num_expected 111 | 112 | 113 | def test_get_input_stats(test_client): 114 | response = test_client.get( 115 | "/api/input_stats", params={"dataset_id": "ord_dataset-89b083710e2d441aa0040c361d63359f", "limit": 10} 116 | ) 117 | response.raise_for_status() 118 | input_stats = response.json() 119 | assert len(input_stats) == 10 120 | 121 | 122 | def test_get_product_stats(test_client): 123 | response = test_client.get( 124 | "/api/product_stats", params={"dataset_id": "ord_dataset-89b083710e2d441aa0040c361d63359f", "limit": 10} 125 | ) 126 | response.raise_for_status() 127 | product_stats = response.json() 128 | assert len(product_stats) == 10 129 | -------------------------------------------------------------------------------- /ord_interface/api/testdata/ord_dataset-89b083710e2d441aa0040c361d63359f.pb.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-reaction-database/ord-interface/5b0ccdaeb2ec16435dadc1ea9486b30b223ecc0d/ord_interface/api/testdata/ord_dataset-89b083710e2d441aa0040c361d63359f.pb.gz -------------------------------------------------------------------------------- /ord_interface/api/testdata/ord_dataset-b440f8c90b6343189093770060fc4098.pb.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-reaction-database/ord-interface/5b0ccdaeb2ec16435dadc1ea9486b30b223ecc0d/ord_interface/api/testdata/ord_dataset-b440f8c90b6343189093770060fc4098.pb.gz -------------------------------------------------------------------------------- /ord_interface/api/testdata/ord_dataset-fc83743b978f4deea7d6856deacbfe53.pb.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-reaction-database/ord-interface/5b0ccdaeb2ec16435dadc1ea9486b30b223ecc0d/ord_interface/api/testdata/ord_dataset-fc83743b978f4deea7d6856deacbfe53.pb.gz -------------------------------------------------------------------------------- /ord_interface/api/testing.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Open Reaction Database Project Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Testing utilities.""" 16 | import os 17 | from glob import glob 18 | 19 | from ord_schema.message_helpers import load_message 20 | from ord_schema.orm.database import add_dataset, prepare_database 21 | from ord_schema.proto import dataset_pb2 22 | from sqlalchemy import create_engine 23 | from sqlalchemy.orm import Session 24 | 25 | 26 | def setup_test_postgres(url: str) -> None: 27 | """Adds test data to a postgres database.""" 28 | datasets = [ 29 | load_message(filename, dataset_pb2.Dataset) 30 | for filename in glob(os.path.join(os.path.dirname(__file__), "testdata", "*.pb.gz")) 31 | ] 32 | assert datasets 33 | engine = create_engine(url, future=True) 34 | rdkit_cartridge = prepare_database(engine) 35 | with Session(engine) as session: 36 | for dataset in datasets: 37 | with session.begin(): 38 | add_dataset(dataset, session, rdkit_cartridge=rdkit_cartridge) 39 | -------------------------------------------------------------------------------- /ord_interface/api/view.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Open Reaction Database Project Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """View API.""" 16 | 17 | from fastapi import APIRouter, Request 18 | from fastapi.responses import HTMLResponse 19 | from ord_schema.proto import reaction_pb2 20 | 21 | from ord_interface.api.search import ReactionIdList, get_reactions 22 | from ord_interface.visualization import filters, generate_text 23 | 24 | router = APIRouter(tags=["view"]) 25 | 26 | 27 | @router.post("/compound_svg") 28 | async def get_compound(request: Request) -> str: 29 | """Returns an SVG of a molecule.""" 30 | data = await request.body() 31 | compound = reaction_pb2.Compound.FromString(data) 32 | try: 33 | return filters._compound_svg(compound) # pylint: disable=protected-access 34 | except (ValueError, KeyError): 35 | return "[Compound cannot be displayed]" 36 | 37 | 38 | @router.get("/reaction_summary", response_class=HTMLResponse) 39 | async def get_reaction_summary(reaction_id: str, compact: bool = True) -> str: 40 | """Renders a reaction as an HTML table with images and text.""" 41 | results = await get_reactions(ReactionIdList(reaction_ids=[reaction_id])) 42 | if len(results) == 0 or len(results) > 1: 43 | raise ValueError(reaction_id) 44 | try: 45 | return generate_text.generate_html(reaction=results[0].reaction, compact=compact) 46 | except (ValueError, KeyError): 47 | return "[Reaction cannot be displayed]" 48 | -------------------------------------------------------------------------------- /ord_interface/api/view_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Open Reaction Database Project Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Tests for ord_interface.api.view.""" 16 | 17 | from ord_schema.proto import reaction_pb2 18 | 19 | 20 | def test_get_compound_svg(test_client): 21 | compound = reaction_pb2.Compound() 22 | compound.identifiers.add(value="c1ccccc1", type="SMILES") 23 | response = test_client.post("/api/compound_svg", content=compound.SerializeToString()) 24 | response.raise_for_status() 25 | 26 | 27 | def test_get_reaction_summary(test_client): 28 | response = test_client.get("/api/reaction_summary", params={"reaction_id": "ord-3f67aa5592fd434d97a577988d3fd241"}) 29 | response.raise_for_status() 30 | -------------------------------------------------------------------------------- /ord_interface/build_test_database.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2022 Open Reaction Database Project Authors 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | set -e 17 | 18 | export PGPASSWORD=postgres 19 | # Use a non-standard PGDATA so the database persists; see 20 | # https://nickjanetakis.com/blog/docker-tip-79-saving-a-postgres-database-in-a-docker-image. 21 | CONTAINER="$(docker run --rm -d -p 5432:5432 -e POSTGRES_PASSWORD=${PGPASSWORD} -e PGDATA=/data informaticsmatters/rdkit-cartridge-debian)" 22 | 23 | # Wait for the database to become available. 24 | function connect() { 25 | psql -p 5432 -h localhost -U postgres < /dev/null 26 | } 27 | set +e 28 | connect 29 | while [ $? -ne 0 ]; do 30 | echo waiting for postgres 31 | sleep 1 32 | connect 33 | done 34 | set -e 35 | 36 | # Editor. 37 | cd editor 38 | psql -p 5432 -h localhost -U postgres -f schema.sql 39 | python py/migrate.py 40 | cd .. 41 | 42 | # Client. 43 | psql -p 5432 -h localhost -U postgres -c 'CREATE DATABASE ord;' 44 | python client/build_database.py 45 | 46 | # Save and shut down the container. 47 | docker commit "${CONTAINER}" "openreactiondatabase/ord-postgres:test" 48 | docker stop "${CONTAINER}" 49 | -------------------------------------------------------------------------------- /ord_interface/client/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Open Reaction Database Project Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Constants for the ORD interface backend.""" 15 | 16 | POSTGRES_HOST = "localhost" 17 | POSTGRES_PORT = 5432 18 | POSTGRES_USER = "postgres" 19 | POSTGRES_PASSWORD = "postgres" 20 | POSTGRES_DB = "ord" 21 | -------------------------------------------------------------------------------- /ord_interface/client/build_database.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Open Reaction Database Project Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Populates a PostgreSQL database containing the ORD (for local testing).""" 15 | import logging 16 | 17 | from ord_schema.orm import database 18 | 19 | from ord_interface.api.testing import setup_test_postgres 20 | from ord_interface.client import POSTGRES_DB, POSTGRES_HOST, POSTGRES_PASSWORD, POSTGRES_PORT, POSTGRES_USER 21 | 22 | 23 | def main(): 24 | logging.disable(logging.DEBUG) 25 | connection_string = database.get_connection_string( 26 | database=POSTGRES_DB, username=POSTGRES_USER, password=POSTGRES_PASSWORD, host=POSTGRES_HOST, port=POSTGRES_PORT 27 | ) 28 | setup_test_postgres(connection_string) 29 | 30 | 31 | if __name__ == "__main__": 32 | main() 33 | -------------------------------------------------------------------------------- /ord_interface/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Open Reaction Database Project Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | services: 16 | database: 17 | image: "openreactiondatabase/ord-postgres:test" 18 | command: [ "postgres", "-c", "log_statement=all" ] 19 | ports: 20 | - "5432:5432" 21 | web: 22 | depends_on: 23 | - database 24 | environment: 25 | - POSTGRES_HOST=database 26 | - POSTGRES_USER=postgres 27 | - POSTGRES_PASSWORD=postgres 28 | - POSTGRES_DATABASE=ord 29 | - GH_CLIENT_ID=${GH_CLIENT_ID} 30 | - GH_CLIENT_SECRET=${GH_CLIENT_SECRET} 31 | image: "openreactiondatabase/ord-interface:latest" 32 | ports: 33 | - "8080:8080" 34 | -------------------------------------------------------------------------------- /ord_interface/editor/README.md: -------------------------------------------------------------------------------- 1 | # Reaction Editor 2 | 3 | A [web app](https://editor.open-reaction-database.org/datasets) for browsing, 4 | modifying, and creating submissions to the Open Reaction Database. 5 | 6 | ## Getting Started 7 | 8 | ``` 9 | $ export ORD_EDITOR_POSTGRES_PASSWORD=######## 10 | $ export ORD_EDITOR_MOUNT=$HOME/ord-editor-postgres 11 | $ docker build -t openreactiondatabase/ord-editor . 12 | $ docker-compose up 13 | ``` 14 | That builds the editor, launches it in a container, and points it at a second 15 | container running Postgres where the editor keeps its state. You can see the 16 | editor at [http://localhost:5001/](http://localhost:5001). 17 | 18 | The first time you do this runs, you must initialize the Postgres schema. 19 | ``` 20 | psql -p 5432 -h localhost -U postgres -f schema.sql 21 | ``` 22 | You may also want to import some data to get started. The migration script 23 | slurps the contents of the db/ directory. 24 | ``` 25 | export PGPASSWORD=######## 26 | ./py/migrate.py 27 | ``` 28 | 29 | ## How it Works 30 | 31 | When you load a reaction in the editor, the editor reads the entire dataset 32 | from the server and maps the content of the chosen reaction onto the DOM. When 33 | you save, it reverses the process. Javascript functions called "load..." go 34 | from reaction to DOM, and functions called "unload..." do the reverse. 35 | 36 | Some fields like images are too big to go in the DOM. These fields have type 37 | "bytes". When a bytes field is loaded, the load function creates a random token 38 | for it, puts the token in the DOM element, and saves the bytes data in a table. 39 | The unload function reads back the token and uses it to restore the bytes data. 40 | 41 | You modify a bytes field by uploading a file. Since Javascript does not have 42 | access to files, the model in this case is different. When you save, the bytes 43 | field in the reaction is assigned its token and the file is uploaded in a 44 | separate connection with the same token. The server merges the reaction and its 45 | upload after both are available. 46 | 47 | Really large datasets need to pass both ways on the network every time you save 48 | them, including all their images. 49 | 50 | ## Development 51 | 52 | You can get lightweight iterations by compiling and running the editor outside 53 | Docker. 54 | 55 | First install and run Postgres, or start its Docker image with the 56 | "docker-compose" command above. Then set up the editor dependencies below. When 57 | that's done, you can start the editor in a debug mode where modifications to 58 | python or JS can be deployed quickly. 59 | ``` 60 | $ make 61 | $ export POSTGRES_PASSWORD=######## 62 | $ export FLASK_ENV=development 63 | $ ./serve.sh --port=5001 64 | ``` 65 | This starts service at [http://localhost:5001/](http://localhost:5001/). 66 | 67 | ### Dependencies 68 | 69 | There are several. 70 | 71 | #### ORD-Schema Environment 72 | 73 | You must clone 74 | [https://github.com/Open-Reaction-Database/ord-schema/](ord-schema) to provide 75 | the proto definitions. This repo assumes that both repos share a common parent 76 | directory: 77 | ``` 78 | 79 | \-ord-schema 80 | \-ord-editor 81 | ``` 82 | 83 | Activate Conda and install the setup.py modules by following the top-level 84 | [instructions](https://github.com/Open-Reaction-Database/ord-schema/blob/main/README.md). 85 | 86 | #### Closure JS Library 87 | 88 | Unpack closure-library in this directory so that Closure can find it too. 89 | ``` 90 | $ url=https://github.com/google/closure-library/archive/v20200517.tar.gz 91 | $ wget $url -O - | tar zxf - 92 | ``` 93 | The editor has been tested with [Closure 94 | v20200517](https://github.com/google/closure-library/releases/). 95 | 96 | #### Protobuf JS Runtime 97 | 98 | Unpack the archive in the editor directory so the Closure compiler can find it. 99 | ``` 100 | $ url=https://github.com/protocolbuffers/protobuf/releases/download/v3.14.0/protobuf-js-3.14.0.tar.gz 101 | $ wget $url -O - | tar zxf - 102 | ``` 103 | At the time of writing, [version 104 | 3.14.0](https://github.com/protocolbuffers/protobuf/releases/tag/v3.14.0) 105 | matches the protoc compiler in pip's protoc-wheel-0 distribution referenced 106 | from ../requirements.txt. 107 | 108 | #### Ketcher 109 | 110 | Ketcher draws molecule diagrams. First install Node.js and npm 111 | ([instructions](https://nodejs.org/en/download/)). Then, in this directory, 112 | ``` 113 | $ git clone git@github.com:Open-Reaction-Database/ketcher.git 114 | $ cd ketcher 115 | $ npm install && npm run build 116 | ``` 117 | 118 | ## Testing and Validation 119 | 120 | There is an end-to-end test that reads a densely populated dataset into the 121 | editor, writes it back out again, and asserts that the proto is unchanged. 122 | 123 | Start the editor as described above and then 124 | ``` 125 | $ make test 126 | ``` 127 | 128 | The test depends on [Node.js](https://nodejs.org/en/download/) and the 129 | Puppeteer headless Chrome package. 130 | ``` 131 | $ npm i puppeteer 132 | ``` 133 | 134 | There is also an Abseil test. Start the editor as described above and then 135 | ``` 136 | $ export POSTGRES_PASSWORD=######## 137 | python ./py/serve_test.py 138 | ``` 139 | Note that this test exercises python code in the test process, not the editor's 140 | Docker container. This test may behave differently from the editor as it 141 | appears in a browser if the two become out of sync. 142 | -------------------------------------------------------------------------------- /ord_interface/editor/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2022 Open Reaction Database Project Authors 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/ 17 | set -Eeuxo pipefail 18 | 19 | rm -rf gen # Clean up from previous run. 20 | mkdir -p gen/js/ord gen/js/proto/ord 21 | # Compile JS proto wrappers. 22 | # https://github.com/google/closure-library/releases/ 23 | CLOSURE=closure-library-20200517 24 | # https://raw.githubusercontent.com/google/closure-compiler/master/contrib/externs/jquery-3.3.js 25 | JQUERY_EXTERNS=externs/jquery-3.3.js 26 | # https://github.com/protocolbuffers/protobuf/releases 27 | PROTOBUF=protobuf-3.14.0 28 | echo "protoc: $(which protoc) $(protoc --version)" 29 | for source in /app/ord-schema/proto/*.proto; do 30 | protoc \ 31 | --experimental_allow_proto3_optional \ 32 | --js_out=binary:gen/js/proto/ord \ 33 | --proto_path=/app \ 34 | "${source}" 35 | done 36 | # Build dataset.js 37 | java -jar node_modules/google-closure-compiler-java/compiler.jar \ 38 | js/*.js \ 39 | gen/js/proto/ord/*.js \ 40 | "${PROTOBUF}"/js/*.js \ 41 | "${PROTOBUF}"/js/binary/*.js \ 42 | "${CLOSURE}"/closure/goog/*.js \ 43 | "${CLOSURE}"/closure/goog/*/*.js \ 44 | "${CLOSURE}"/closure/goog/labs/*/*.js \ 45 | --externs="${JQUERY_EXTERNS}" \ 46 | --entry_point=ord.dataset \ 47 | --js_output_file=gen/js/ord/dataset.js \ 48 | --dependency_mode=PRUNE \ 49 | --jscomp_error="*" \ 50 | --hide_warnings_for="${CLOSURE}" \ 51 | --hide_warnings_for="${PROTOBUF}" \ 52 | --hide_warnings_for=gen/js/proto/ord \ 53 | || (rm -f gen/js/ord/dataset.js && false) 54 | # Build reaction.js 55 | java -jar node_modules/google-closure-compiler-java/compiler.jar \ 56 | js/*.js \ 57 | gen/js/proto/ord/*.js \ 58 | "${PROTOBUF}"/js/*.js \ 59 | "${PROTOBUF}"/js/binary/*.js \ 60 | "${CLOSURE}"/closure/goog/*.js \ 61 | "${CLOSURE}"/closure/goog/*/*.js \ 62 | "${CLOSURE}"/closure/goog/labs/*/*.js \ 63 | --externs="${JQUERY_EXTERNS}" \ 64 | --entry_point=ord.reaction \ 65 | --js_output_file=gen/js/ord/reaction.js \ 66 | --dependency_mode=PRUNE \ 67 | --jscomp_error="*" \ 68 | --hide_warnings_for="${CLOSURE}" \ 69 | --hide_warnings_for="${PROTOBUF}" \ 70 | --hide_warnings_for=gen/js/proto/ord \ 71 | || (rm -f gen/js/ord/reaction.js && false) 72 | -------------------------------------------------------------------------------- /ord_interface/editor/db/680b0d9fe649417cb092d790907bd5a5/empty.pbtxt: -------------------------------------------------------------------------------- 1 | reactions { 2 | reaction_id: "test" 3 | } 4 | -------------------------------------------------------------------------------- /ord_interface/editor/html/login.html: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | Login 20 | 21 | 22 | 50 | 51 |
52 |
53 | 54 | Sign in with GitHub 55 |
56 |
57 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /ord_interface/editor/html/submissions.html: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 33 | 34 |
35 |

Open pull requests

36 |
37 | sync 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | {% for (number, title), names in pull_requests.items() %} 48 | 49 | 52 | 53 | 60 | 61 | {% endfor %} 62 | 63 |
NumberTitleDatasets
50 | {{ number }} 51 | {{ title }} 54 | {% for short_name, name in names %} 55 |
56 | {{ short_name }} 57 |
58 | {% endfor %} 59 |
64 | 65 | 66 | -------------------------------------------------------------------------------- /ord_interface/editor/img/template-editor-how.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-reaction-database/ord-interface/5b0ccdaeb2ec16435dadc1ea9486b30b223ecc0d/ord_interface/editor/img/template-editor-how.png -------------------------------------------------------------------------------- /ord_interface/editor/js/codes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 Open Reaction Database Project Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | goog.module('ord.codes'); 18 | goog.module.declareLegacyNamespace(); 19 | exports = { 20 | load, 21 | unload, 22 | addCode 23 | }; 24 | 25 | const JspbMap = goog.requireType('jspb.Map'); 26 | 27 | const asserts = goog.require('goog.asserts'); 28 | 29 | const data = goog.require('ord.data'); 30 | const utils = goog.require('ord.utils'); 31 | 32 | const Data = goog.require('proto.ord.Data'); 33 | 34 | /** 35 | * Adds and populates the automation_code sections in the form. 36 | * @param {!JspbMap} codes 37 | */ 38 | function load(codes) { 39 | codes.forEach(function(code, name) { loadCode(name, code); }); 40 | } 41 | 42 | /** 43 | * Adds and populates a single automation_code section in the form. 44 | * @param {string} name The name of this automation code. 45 | * @param {!Data} code 46 | */ 47 | function loadCode(name, code) { 48 | const node = addCode(); 49 | $('.setup_code_name', node).text(name); 50 | data.loadData(node, code); 51 | } 52 | 53 | /** 54 | * Fetches the automation_code sections from the form and adds them to `codes`. 55 | * @param {!JspbMap} codes 56 | */ 57 | function unload(codes) { 58 | $('.setup_code').each(function(index, node) { 59 | node = $(node); 60 | if (!utils.isTemplateOrUndoBuffer(node)) { 61 | unloadCode(codes, node); 62 | } 63 | }); 64 | } 65 | 66 | /** 67 | * Fetches a single automation_code section from the form and adds it to 68 | * `codes`. 69 | * @param {!JspbMap} codes 70 | * @param {!jQuery} node The root node of the automation_code section to fetch. 71 | */ 72 | function unloadCode(codes, node) { 73 | const name = $('.setup_code_name', node).text(); 74 | const code = data.unloadData(node); 75 | if (name || !utils.isEmptyMessage(code)) { 76 | codes.set(asserts.assertString(name), code); 77 | } 78 | } 79 | 80 | /** 81 | * Adds an automation_code section to the form. 82 | * @return {!jQuery} The newly added root node for the automation_code section. 83 | */ 84 | function addCode() { 85 | const node = utils.addSlowly('#setup_code_template', $('#setup_codes')); 86 | data.addData(node); 87 | return node; 88 | } 89 | -------------------------------------------------------------------------------- /ord_interface/editor/js/crudes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 Open Reaction Database Project Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | goog.module('ord.crudes'); 18 | goog.module.declareLegacyNamespace(); 19 | exports = { 20 | load, 21 | unload, 22 | add 23 | }; 24 | 25 | const asserts = goog.require('goog.asserts'); 26 | 27 | const amounts = goog.require('ord.amounts'); 28 | const utils = goog.require('ord.utils'); 29 | 30 | const CrudeComponent = goog.require('proto.ord.CrudeComponent'); 31 | 32 | /** 33 | * Adds and populates the crude components of a reaction input. 34 | * @param {!jQuery} node Root node for the parent reaction input. 35 | * @param {!Array} crudes 36 | */ 37 | function load(node, crudes) { crudes.forEach(crude => loadCrude(node, crude)); } 38 | 39 | /** 40 | * Adds and populates a single crude component section in the form. 41 | * @param {!jQuery} root Root node for the parent reaction input. 42 | * @param {!CrudeComponent} crude 43 | */ 44 | function loadCrude(root, crude) { 45 | const node = add(root); 46 | 47 | const reactionId = crude.getReactionId(); 48 | $('.crude_reaction', node).text(reactionId); 49 | 50 | const workup = crude.hasIncludesWorkup() ? crude.getIncludesWorkup() : null; 51 | utils.setOptionalBool($('.crude_includes_workup', node), workup); 52 | 53 | const derived = 54 | crude.hasHasDerivedAmount() ? crude.getHasDerivedAmount() : null; 55 | utils.setOptionalBool($('.crude_has_derived', node), derived); 56 | 57 | const amount = crude.getAmount(); 58 | amounts.load(node, amount); 59 | } 60 | 61 | /** 62 | * Fetches the crude components defined for a reaction input in the form. 63 | * @param {!jQuery} node Root node for the parent reaction input. 64 | * @return {!Array} 65 | */ 66 | function unload(node) { 67 | const crudes = []; 68 | $('.crude', node).each(function(index, crudeNode) { 69 | crudeNode = $(crudeNode); 70 | if (!crudeNode.attr('id')) { 71 | // Not a template. 72 | const crude = unloadCrude(crudeNode); 73 | if (!utils.isEmptyMessage(crude)) { 74 | crudes.push(crude); 75 | } 76 | } 77 | }); 78 | return crudes; 79 | } 80 | 81 | /** 82 | * Fetches a single crude component defined in the form. 83 | * @param {!jQuery} node Root node for the crude component. 84 | * @return {!CrudeComponent} 85 | */ 86 | function unloadCrude(node) { 87 | const crude = new CrudeComponent(); 88 | 89 | const reactionId = $('.crude_reaction', node).text(); 90 | crude.setReactionId(asserts.assertString(reactionId)); 91 | 92 | const workup = utils.getOptionalBool($('.crude_includes_workup', node)); 93 | if (workup !== null) { 94 | crude.setIncludesWorkup(workup); 95 | } 96 | 97 | const derived = utils.getOptionalBool($('.crude_has_derived', node)); 98 | if (derived !== null) { 99 | crude.setHasDerivedAmount(derived); 100 | } 101 | 102 | const amount = amounts.unload(node); 103 | if (!utils.isEmptyMessage(amount)) { 104 | crude.setAmount(amount); 105 | } 106 | 107 | return crude; 108 | } 109 | 110 | /** 111 | * Adds a crude component section to the given reaction input. 112 | * @param {!jQuery} root Root node for the parent reaction input. 113 | * @return {!jQuery} The newly added root node for the crude component. 114 | */ 115 | function add(root) { 116 | const node = utils.addSlowly('#crude_template', $('.crudes', root)); 117 | amounts.init(node); 118 | return node; 119 | } 120 | -------------------------------------------------------------------------------- /ord_interface/editor/js/enums.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 Open Reaction Database Project Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | goog.module('ord.enums'); 18 | goog.module.declareLegacyNamespace(); 19 | 20 | // Proto enums are referenced by reflection from "data-proto" HTML attributes. 21 | /** @suppress {extraRequire} */ 22 | goog.require('proto.ord.ReactionRole.ReactionRoleType'); 23 | /** @suppress {extraRequire} */ 24 | goog.require('proto.ord.CompoundIdentifier.CompoundIdentifierType'); 25 | /** @suppress {extraRequire} */ 26 | goog.require('proto.ord.CompoundPreparation.CompoundPreparationType'); 27 | /** @suppress {extraRequire} */ 28 | goog.require('proto.ord.ElectrochemistryConditions.ElectrochemistryType'); 29 | /** @suppress {extraRequire} */ 30 | goog.require('proto.ord.FlowConditions.FlowType'); 31 | /** @suppress {extraRequire} */ 32 | goog.require('proto.ord.IlluminationConditions.IlluminationType'); 33 | /** @suppress {extraRequire} */ 34 | goog.require('proto.ord.PressureConditions.Atmosphere.AtmosphereType'); 35 | /** @suppress {extraRequire} */ 36 | goog.require( 37 | 'proto.ord.PressureConditions.PressureMeasurement.PressureMeasurementType'); 38 | /** @suppress {extraRequire} */ 39 | goog.require( 40 | 'proto.ord.PressureConditions.PressureControl.PressureControlType'); 41 | /** @suppress {extraRequire} */ 42 | goog.require('proto.ord.ReactionIdentifier.ReactionIdentifierType'); 43 | /** @suppress {extraRequire} */ 44 | goog.require('proto.ord.ReactionInput.AdditionSpeed.AdditionSpeedType'); 45 | /** @suppress {extraRequire} */ 46 | goog.require('proto.ord.StirringConditions.StirringMethodType'); 47 | /** @suppress {extraRequire} */ 48 | goog.require('proto.ord.StirringConditions.StirringRate.StirringRateType'); 49 | /** @suppress {extraRequire} */ 50 | goog.require( 51 | 'proto.ord.TemperatureConditions.TemperatureMeasurement.TemperatureMeasurementType'); 52 | /** @suppress {extraRequire} */ 53 | goog.require( 54 | 'proto.ord.TemperatureConditions.TemperatureControl.TemperatureControlType'); 55 | /** @suppress {extraRequire} */ 56 | goog.require('proto.ord.Vessel.VesselType'); 57 | /** @suppress {extraRequire} */ 58 | goog.require('proto.ord.VesselAttachment.VesselAttachmentType'); 59 | /** @suppress {extraRequire} */ 60 | goog.require('proto.ord.VesselMaterial.VesselMaterialType'); 61 | /** @suppress {extraRequire} */ 62 | goog.require('proto.ord.VesselPreparation.VesselPreparationType'); 63 | -------------------------------------------------------------------------------- /ord_interface/editor/js/flow.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 Open Reaction Database Project Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | goog.module('ord.flows'); 18 | goog.module.declareLegacyNamespace(); 19 | 20 | const asserts = goog.require('goog.asserts'); 21 | 22 | const utils = goog.require('ord.utils'); 23 | 24 | const FlowConditions = goog.require('proto.ord.FlowConditions'); 25 | const FlowType = goog.require('proto.ord.FlowConditions.FlowType'); 26 | const Tubing = goog.require('proto.ord.FlowConditions.Tubing'); 27 | const TubingType = goog.require('proto.ord.FlowConditions.Tubing.TubingType'); 28 | const Length = goog.require('proto.ord.Length'); 29 | 30 | exports = { 31 | load, 32 | unload, 33 | validateFlow 34 | }; 35 | 36 | /** 37 | * Adds and populates the flow conditions section in the form. 38 | * @param {!FlowConditions} flow 39 | */ 40 | function load(flow) { 41 | utils.setSelector($('#flow_type'), flow.getType()); 42 | $('#flow_details').text(flow.getDetails()); 43 | $('#flow_pump').text(flow.getPumpType()); 44 | 45 | const tubing = flow.getTubing(); 46 | utils.setSelector($('#flow_tubing_type'), tubing.getType()); 47 | $('#flow_tubing_details').text(tubing.getDetails()); 48 | utils.writeMetric('#flow_tubing', tubing.getDiameter()); 49 | } 50 | 51 | /** 52 | * Fetches the flow conditions defined in the form. 53 | * @return {!FlowConditions} 54 | */ 55 | function unload() { 56 | const flow = new FlowConditions(); 57 | const flowType = utils.getSelectorText($('#flow_type')[0]); 58 | flow.setType(FlowType[flowType]); 59 | flow.setDetails(asserts.assertString($('#flow_details').text())); 60 | 61 | flow.setPumpType(asserts.assertString($('#flow_pump').text())); 62 | 63 | const tubing = new Tubing(); 64 | const tubingType = utils.getSelectorText($('#flow_tubing_type')[0]); 65 | tubing.setType(TubingType[tubingType]); 66 | tubing.setDetails(asserts.assertString($('#flow_tubing_details').text())); 67 | const diameter = utils.readMetric('#flow_tubing', new Length()); 68 | if (!utils.isEmptyMessage(diameter)) { 69 | tubing.setDiameter(diameter); 70 | } 71 | 72 | if (!utils.isEmptyMessage(tubing)) { 73 | flow.setTubing(tubing); 74 | } 75 | return flow; 76 | } 77 | 78 | /** 79 | * Validates the flow conditions defined in the form. 80 | * @param {!jQuery} node Root node for the flow conditions. 81 | * @param {?jQuery=} validateNode Target node for validation results. 82 | */ 83 | function validateFlow(node, validateNode = null) { 84 | const flow = unload(); 85 | utils.validate(flow, 'FlowConditions', node, validateNode); 86 | } 87 | -------------------------------------------------------------------------------- /ord_interface/editor/js/identifiers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 Open Reaction Database Project Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | goog.module('ord.identifiers'); 18 | goog.module.declareLegacyNamespace(); 19 | 20 | const asserts = goog.require('goog.asserts'); 21 | 22 | const uploads = goog.require('ord.uploads'); 23 | const utils = goog.require('ord.utils'); 24 | 25 | const ReactionIdentifier = goog.require('proto.ord.ReactionIdentifier'); 26 | const IdentifierType = 27 | goog.require('proto.ord.ReactionIdentifier.ReactionIdentifierType'); 28 | 29 | exports = { 30 | load, 31 | unload, 32 | add 33 | }; 34 | 35 | /** 36 | * Adds and populates the reaction identifier sections in the form. 37 | * @param {!Array} identifiers 38 | */ 39 | function load(identifiers) { 40 | identifiers.forEach(identifier => loadIdentifier(identifier)); 41 | if (!(identifiers.length)) { 42 | add(); 43 | } 44 | } 45 | 46 | /** 47 | * Adds and populates a single reaction identifier section in the form. 48 | * @param {!ReactionIdentifier} identifier 49 | */ 50 | function loadIdentifier(identifier) { 51 | const node = add(); 52 | const value = identifier.getValue(); 53 | $('.reaction_identifier_value', node).text(value); 54 | utils.setSelector(node, identifier.getType()); 55 | $('.reaction_identifier_details', node).text(identifier.getDetails()); 56 | } 57 | 58 | /** 59 | * Fetches the reaction identifiers defined in the form. 60 | * @return {!Array} 61 | */ 62 | function unload() { 63 | const identifiers = []; 64 | $('.reaction_identifier').each(function(index, node) { 65 | node = $(node); 66 | if (!utils.isTemplateOrUndoBuffer(node)) { 67 | const identifier = unloadIdentifier(node); 68 | if (!utils.isEmptyMessage(identifier)) { 69 | identifiers.push(identifier); 70 | } 71 | } 72 | }); 73 | return identifiers; 74 | } 75 | 76 | /** 77 | * Fetches a single reaction identifier defined in the form. 78 | * @param {!jQuery} node Root node for the identifier. 79 | * @return {!ReactionIdentifier} 80 | */ 81 | function unloadIdentifier(node) { 82 | const identifier = new ReactionIdentifier(); 83 | 84 | identifier.setValue( 85 | asserts.assertString($('.reaction_identifier_value', node).text())); 86 | 87 | const type = utils.getSelectorText(node[0]); 88 | identifier.setType(IdentifierType[type]); 89 | identifier.setDetails( 90 | asserts.assertString($('.reaction_identifier_details', node).text())); 91 | return identifier; 92 | } 93 | 94 | /** 95 | * Adds a reaction identifier section to the form. 96 | * @return {!jQuery} The newly added parent node for the identifier. 97 | */ 98 | function add() { 99 | const node = 100 | utils.addSlowly('#reaction_identifier_template', $('#identifiers')); 101 | 102 | const uploadButton = $('.reaction_identifier_upload', node); 103 | uploadButton.on('change', function() { 104 | if ($(this).is(':checked')) { 105 | $('.uploader', node).show(); 106 | $('.reaction_identifier_value', node).hide(); 107 | $('.text_upload', node).hide(); 108 | } else { 109 | $('.uploader', node).hide(); 110 | $('.reaction_identifier_value', node).show(); 111 | } 112 | }); 113 | uploads.initialize(node); 114 | return node; 115 | } 116 | -------------------------------------------------------------------------------- /ord_interface/editor/js/illumination.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 Open Reaction Database Project Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | goog.module('ord.illumination'); 18 | goog.module.declareLegacyNamespace(); 19 | 20 | const asserts = goog.require('goog.asserts'); 21 | 22 | const utils = goog.require('ord.utils'); 23 | 24 | const IlluminationConditions = goog.require('proto.ord.IlluminationConditions'); 25 | const IlluminationType = 26 | goog.require('proto.ord.IlluminationConditions.IlluminationType'); 27 | const Length = goog.require('proto.ord.Length'); 28 | const Wavelength = goog.require('proto.ord.Wavelength'); 29 | 30 | exports = { 31 | load, 32 | unload, 33 | validateIllumination 34 | }; 35 | 36 | /** 37 | * Adds and populates the illumination conditions section in the form. 38 | * @param {!IlluminationConditions} illumination 39 | */ 40 | function load(illumination) { 41 | utils.setSelector($('#illumination_type'), illumination.getType()); 42 | $('#illumination_details').text(illumination.getDetails()); 43 | const wavelength = illumination.getPeakWavelength(); 44 | utils.writeMetric('#illumination_wavelength', wavelength); 45 | $('#illumination_color').text(illumination.getColor()); 46 | const distance = illumination.getDistanceToVessel(); 47 | utils.writeMetric('#illumination_distance', distance); 48 | } 49 | 50 | /** 51 | * Fetches the illumination conditions defined in the form. 52 | * @return {!IlluminationConditions} 53 | */ 54 | function unload() { 55 | const illumination = new IlluminationConditions(); 56 | const illuminationType = utils.getSelectorText($('#illumination_type')[0]); 57 | illumination.setType(IlluminationType[illuminationType]); 58 | illumination.setDetails( 59 | asserts.assertString($('#illumination_details').text())); 60 | 61 | const wavelength = 62 | utils.readMetric('#illumination_wavelength', new Wavelength()); 63 | if (!utils.isEmptyMessage(wavelength)) { 64 | illumination.setPeakWavelength(wavelength); 65 | } 66 | illumination.setColor(asserts.assertString($('#illumination_color').text())); 67 | const distance = utils.readMetric('#illumination_distance', new Length()); 68 | if (!utils.isEmptyMessage(distance)) { 69 | illumination.setDistanceToVessel(distance); 70 | } 71 | return illumination; 72 | } 73 | 74 | /** 75 | * Validates the illumination conditions defined in the form. 76 | * @param {!jQuery} node Root node for the illumination conditions. 77 | * @param {?jQuery=} validateNode Target node for validation results. 78 | */ 79 | function validateIllumination(node, validateNode = null) { 80 | const illumination = unload(); 81 | utils.validate(illumination, 'IlluminationConditions', node, validateNode); 82 | } 83 | -------------------------------------------------------------------------------- /ord_interface/editor/js/notes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 Open Reaction Database Project Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | goog.module('ord.notes'); 18 | goog.module.declareLegacyNamespace(); 19 | 20 | const asserts = goog.require('goog.asserts'); 21 | 22 | const utils = goog.require('ord.utils'); 23 | 24 | const ReactionNotes = goog.require('proto.ord.ReactionNotes'); 25 | 26 | exports = { 27 | load, 28 | unload, 29 | validateNotes 30 | }; 31 | 32 | /** 33 | * Adds and populates the reaction nodes section in the form. 34 | * @param {!ReactionNotes} notes 35 | */ 36 | function load(notes) { 37 | utils.setOptionalBool($('#notes_heterogeneous'), 38 | notes.hasIsHeterogeneous() ? notes.getIsHeterogeneous() 39 | : null); 40 | utils.setOptionalBool( 41 | $('#notes_precipitate'), 42 | notes.hasFormsPrecipitate() ? notes.getFormsPrecipitate() : null); 43 | utils.setOptionalBool($('#notes_exothermic'), notes.hasIsExothermic() 44 | ? notes.getIsExothermic() 45 | : null); 46 | utils.setOptionalBool($('#notes_offgas'), 47 | notes.hasOffgasses() ? notes.getOffgasses() : null); 48 | utils.setOptionalBool($('#notes_moisture'), 49 | notes.hasIsSensitiveToMoisture() 50 | ? notes.getIsSensitiveToMoisture() 51 | : null); 52 | utils.setOptionalBool($('#notes_oxygen'), notes.hasIsSensitiveToOxygen() 53 | ? notes.getIsSensitiveToOxygen() 54 | : null); 55 | utils.setOptionalBool($('#notes_light'), notes.hasIsSensitiveToLight() 56 | ? notes.getIsSensitiveToLight() 57 | : null); 58 | $('#notes_safety').text(notes.getSafetyNotes()); 59 | $('#notes_details').text(notes.getProcedureDetails()); 60 | } 61 | 62 | /** 63 | * Fetches the reaction notes defined in the form. 64 | * @return {!ReactionNotes} 65 | */ 66 | function unload() { 67 | const notes = new ReactionNotes(); 68 | const isHeterogeneous = utils.getOptionalBool($('#notes_heterogeneous')); 69 | if (isHeterogeneous !== null) { 70 | notes.setIsHeterogeneous(isHeterogeneous); 71 | } 72 | const formsPrecipitate = utils.getOptionalBool($('#notes_precipitate')); 73 | if (formsPrecipitate !== null) { 74 | notes.setFormsPrecipitate(formsPrecipitate); 75 | } 76 | const isExothermic = utils.getOptionalBool($('#notes_exothermic')); 77 | if (isExothermic !== null) { 78 | notes.setIsExothermic(isExothermic); 79 | } 80 | const offgasses = utils.getOptionalBool($('#notes_offgas')); 81 | if (offgasses !== null) { 82 | notes.setOffgasses(offgasses); 83 | } 84 | const isSensitiveToMoisture = utils.getOptionalBool($('#notes_moisture')); 85 | if (isSensitiveToMoisture !== null) { 86 | notes.setIsSensitiveToMoisture(isSensitiveToMoisture); 87 | } 88 | const isSensitiveToOxygen = utils.getOptionalBool($('#notes_oxygen')); 89 | if (isSensitiveToOxygen !== null) { 90 | notes.setIsSensitiveToOxygen(isSensitiveToOxygen); 91 | } 92 | const isSensitiveToLight = utils.getOptionalBool($('#notes_light')); 93 | if (isSensitiveToLight !== null) { 94 | notes.setIsSensitiveToLight(isSensitiveToLight); 95 | } 96 | notes.setSafetyNotes(asserts.assertString($('#notes_safety').text())); 97 | notes.setProcedureDetails(asserts.assertString($('#notes_details').text())); 98 | return notes; 99 | } 100 | 101 | /** 102 | * Validates the reaction notes defined in the form. 103 | * @param {!jQuery} node Root node for the reaction notes. 104 | * @param {?jQuery=} validateNode Target node for validation results. 105 | */ 106 | function validateNotes(node, validateNode = null) { 107 | const notes = unload(); 108 | utils.validate(notes, 'ReactionNotes', node, validateNode); 109 | } 110 | -------------------------------------------------------------------------------- /ord_interface/editor/js/observations.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 Open Reaction Database Project Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | goog.module('ord.observations'); 18 | goog.module.declareLegacyNamespace(); 19 | 20 | const asserts = goog.require('goog.asserts'); 21 | 22 | const data = goog.require('ord.data'); 23 | const utils = goog.require('ord.utils'); 24 | 25 | const ReactionObservation = goog.require('proto.ord.ReactionObservation'); 26 | const Time = goog.require('proto.ord.Time'); 27 | 28 | exports = { 29 | load, 30 | unload, 31 | add, 32 | validateObservation 33 | }; 34 | 35 | /** 36 | * Adds and populates the reaction observation sections in the form. 37 | * @param {!Array} observations 38 | */ 39 | function load(observations) { 40 | observations.forEach(observation => loadObservation(observation)); 41 | } 42 | 43 | /** 44 | * Adds and populates a single reaction observation section in the form. 45 | * @param {!ReactionObservation} observation 46 | */ 47 | function loadObservation(observation) { 48 | const node = add(); 49 | utils.writeMetric('.observation_time', observation.getTime(), node); 50 | $('.observation_comment', node).text(observation.getComment()); 51 | data.loadData(node, observation.getImage()); 52 | } 53 | 54 | /** 55 | * Fetches the reaction observations defined in the form. 56 | * @return {!Array} 57 | */ 58 | function unload() { 59 | const observations = []; 60 | $('.observation').each(function(index, node) { 61 | node = $(node); 62 | if (!utils.isTemplateOrUndoBuffer(node)) { 63 | const observation = unloadObservation(node); 64 | if (!utils.isEmptyMessage(observation)) { 65 | observations.push(observation); 66 | } 67 | } 68 | }); 69 | return observations; 70 | } 71 | 72 | /** 73 | * Fetches a single reaction observation defined in the form. 74 | * @param {!jQuery} node Root node for the reaction observation. 75 | * @return {!ReactionObservation} 76 | */ 77 | function unloadObservation(node) { 78 | const observation = new ReactionObservation(); 79 | const time = utils.readMetric('.observation_time', new Time(), node); 80 | if (!utils.isEmptyMessage(time)) { 81 | observation.setTime(time); 82 | } 83 | observation.setComment( 84 | asserts.assertString($('.observation_comment', node).text())); 85 | const image = data.unloadData(node); 86 | if (!utils.isEmptyMessage(image)) { 87 | observation.setImage(image); 88 | } 89 | return observation; 90 | } 91 | 92 | /** 93 | * Adds a reaction observation section to the form. 94 | * @return {!jQuery} The newly added parent node for the reaction observation. 95 | */ 96 | function add() { 97 | const node = utils.addSlowly('#observation_template', $('#observations')); 98 | data.addData(node); 99 | // Add live validation handling. 100 | utils.addChangeHandler(node, () => { validateObservation(node); }); 101 | return node; 102 | } 103 | 104 | /** 105 | * Validates a single reaction observation defined in the form. 106 | * @param {!jQuery} node Root node for the reaction observation. 107 | * @param {?jQuery=} validateNode Target node for validation results. 108 | */ 109 | function validateObservation(node, validateNode = null) { 110 | const observation = unloadObservation(node); 111 | utils.validate(observation, 'ReactionObservation', node, validateNode); 112 | } 113 | -------------------------------------------------------------------------------- /ord_interface/editor/js/stirring.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 Open Reaction Database Project Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | goog.module('ord.stirring'); 18 | goog.module.declareLegacyNamespace(); 19 | 20 | const asserts = goog.require('goog.asserts'); 21 | 22 | const utils = goog.require('ord.utils'); 23 | 24 | const StirringConditions = goog.require('proto.ord.StirringConditions'); 25 | const StirringMethodType = 26 | goog.require('proto.ord.StirringConditions.StirringMethodType'); 27 | const StirringRate = goog.require('proto.ord.StirringConditions.StirringRate'); 28 | const StirringRateType = 29 | goog.require('proto.ord.StirringConditions.StirringRate.StirringRateType'); 30 | 31 | exports = { 32 | load, 33 | unload, 34 | validateStirring 35 | }; 36 | 37 | /** 38 | * Adds and populates the stirring conditions section in the form. 39 | * @param {!StirringConditions} stirring 40 | * @param {!jQuery} target 41 | */ 42 | function load(stirring, target) { 43 | utils.setSelector($('.stirring_method_type', target), stirring.getType()); 44 | $('.stirring_method_details', target).text(stirring.getDetails()); 45 | const rate = stirring.getRate(); 46 | if (rate) { 47 | utils.setSelector($('.stirring_rate_type', target), rate.getType()); 48 | $('.stirring_rate_details', target).text(rate.getDetails()); 49 | const rpm = rate.getRpm(); 50 | if (rpm !== 0) { 51 | $('.stirring_rpm', target).text(rpm); 52 | } 53 | } 54 | } 55 | 56 | /** 57 | * Fetches the stirring conditions from the form. 58 | * @param {!jQuery} target 59 | * @return {!StirringConditions} 60 | */ 61 | function unload(target) { 62 | const stirring = new StirringConditions(); 63 | const methodType = 64 | utils.getSelectorText($('.stirring_method_type', target)[0]); 65 | stirring.setType(StirringMethodType[methodType]); 66 | stirring.setDetails( 67 | asserts.assertString($('.stirring_method_details', target).text())); 68 | 69 | const rate = new StirringRate(); 70 | const rateType = utils.getSelectorText($('.stirring_rate_type', target)[0]); 71 | rate.setType(StirringRateType[rateType]); 72 | rate.setDetails( 73 | asserts.assertString($('.stirring_rate_details', target).text())); 74 | const rpm = parseFloat($('.stirring_rpm', target).text()); 75 | if (!isNaN(rpm)) { 76 | rate.setRpm(rpm); 77 | } 78 | if (!utils.isEmptyMessage(rate)) { 79 | stirring.setRate(rate); 80 | } 81 | return stirring; 82 | } 83 | 84 | /** 85 | * Validates the stirring conditions defined in the form. 86 | * @param {!jQuery} node The div containing the stirring conditions. 87 | * @param {?jQuery=} validateNode The target div for validation results. 88 | */ 89 | function validateStirring(node, validateNode = null) { 90 | const stirring = unload(node); 91 | utils.validate(stirring, 'StirringConditions', node, validateNode); 92 | } 93 | -------------------------------------------------------------------------------- /ord_interface/editor/js/test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 Open Reaction Database Project Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Automated tests of Reaction proto round-trips: proto -> DOM -> proto. 19 | * 20 | * $ node js/test.js 21 | */ 22 | 23 | const puppeteer = require('puppeteer'); 24 | 25 | (async () => { 26 | const browser = await puppeteer.launch(); 27 | const [page] = await browser.pages(); 28 | 29 | // Relay console messages. 30 | page.on('console', msg => console.log('console>', msg.text())); 31 | 32 | // First authenticate as the test user. 33 | page.goto('http://localhost:8080/editor/authenticate'); 34 | await page.waitForNavigation(); 35 | 36 | // Round-trip these reactions through the DOM and compare at the server. 37 | const roundtripTests = [ 38 | 'http://localhost:8080/editor/dataset/empty/reaction/0', 39 | 'http://localhost:8080/editor/dataset/full/reaction/0', 40 | 'http://localhost:8080/editor/dataset/ord-nielsen-example/reaction/0', 41 | ]; 42 | 43 | for (let i = 0; i < roundtripTests.length; i++) { 44 | const url = roundtripTests[i]; 45 | await page.goto(url); 46 | await page.waitForSelector('body[ready=true]'); 47 | const testResult = await page.evaluate(function(url) { 48 | const reaction = ord.reaction.unloadReaction(); 49 | const session = ord.utils.session; 50 | const reactions = session.dataset.getReactionsList(); 51 | reactions[session.index] = reaction; 52 | return ord.utils.compareDataset(session.fileName, session.dataset) 53 | .then(() => { 54 | console.log('PASS', url); 55 | return 0; 56 | }) 57 | .catch(() => { 58 | console.log('FAIL', url); 59 | return 1; 60 | }) 61 | }, url); 62 | 63 | // Report results of testing to environment (shell, Git CI, etc.) 64 | // If _any_ test fails (i.e. testResult 1), then the entire process must 65 | // fail too. We still run all tests though, for convenience's sake. 66 | if (testResult === 1) { 67 | process.exitCode = 1; 68 | } 69 | } 70 | 71 | // Additional test to ensure that pretending to change field entries does 72 | // not lead to a change in the number of validation errors. 73 | const validationTests = [ 74 | 'http://localhost:8080/editor/dataset/ord-nielsen-example/reaction/0', 75 | ]; 76 | 77 | for (let i = 0; i < validationTests.length; i++) { 78 | const url = validationTests[i]; 79 | await page.goto(url); 80 | await page.waitForSelector('body[ready=true]'); 81 | const testResult = await page.evaluate(function(url) { 82 | ord.reaction.validateReaction(); 83 | const prevErrors = 84 | parseInt($('.validate_status', '#reaction_validate').html()) || 0; 85 | $('.edittext').trigger('blur'); 86 | ord.reaction.validateReaction(); 87 | const curErrors = 88 | parseInt($('.validate_status', '#reaction_validate').html()) || 0; 89 | if (prevErrors === curErrors) { 90 | console.log('PASS', 'validation check', url); 91 | return 0; 92 | } else { 93 | console.log('FAIL', 'validation check', url); 94 | console.log('--> ' + prevErrors + ' before rechecking'); 95 | console.log('--> ' + curErrors + ' after rechecking'); 96 | return 1; 97 | } 98 | }, url); 99 | 100 | // Report results of testing to environment (shell, Git CI, etc.) 101 | // If _any_ test fails (i.e. testResult 1), then the entire process must 102 | // fail too. We still run all tests though, for convenience's sake. 103 | if (testResult === 1) { 104 | process.exitCode = 1; 105 | } 106 | } 107 | 108 | await browser.close(); 109 | })(); 110 | -------------------------------------------------------------------------------- /ord_interface/editor/py/migrate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright 2020 Open Reaction Database Project Authors 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | """Slurp the local db/ directory contents into Postgres.""" 16 | 17 | import os 18 | import re 19 | import sys 20 | import time 21 | 22 | import psycopg2 23 | import psycopg2.sql 24 | from ord_schema import message_helpers 25 | from ord_schema.proto import dataset_pb2 26 | 27 | 28 | def migrate_one(user_id, name, conn): 29 | """Slurp one named dataset from the db/ directory into Postgres.""" 30 | dataset = message_helpers.load_message( 31 | os.path.join(os.path.dirname(__file__), "..", "db", user_id, name), dataset_pb2.Dataset 32 | ) 33 | serialized = dataset.SerializeToString().hex() 34 | query = psycopg2.sql.SQL( 35 | "INSERT INTO datasets VALUES (%s, %s, %s) " "ON CONFLICT (user_id, name) DO UPDATE SET serialized=%s" 36 | ) 37 | with conn.cursor() as cursor: 38 | cursor.execute(query, [user_id, name[:-6], serialized, serialized]) 39 | 40 | 41 | def migrate_all(): 42 | """Run as a script, copies the entire contents of the db/ directory.""" 43 | with psycopg2.connect(dbname="editor", host="localhost", port=5432, user="postgres") as conn: 44 | for user_id in os.listdir(os.path.join(os.path.dirname(__file__), "..", "db")): 45 | if re.match("^[0-9a-fA-F]{32}$", user_id) is None: 46 | continue 47 | query = psycopg2.sql.SQL("INSERT INTO users VALUES (%s, %s, %s) ON CONFLICT DO NOTHING") 48 | with conn.cursor() as cursor: 49 | timestamp = int(time.time()) 50 | cursor.execute(query, [user_id, None, timestamp]) 51 | for name in os.listdir(f"db/{user_id}"): 52 | if not name.endswith(".pbtxt"): 53 | continue 54 | migrate_one(user_id, name, conn) 55 | 56 | 57 | if __name__ == "__main__": 58 | sys.exit(migrate_all()) 59 | -------------------------------------------------------------------------------- /ord_interface/editor/py/testdata/nielsen_fig1_template.pbtxt: -------------------------------------------------------------------------------- 1 | identifiers { 2 | type: REACTION_TYPE 3 | value: "deoxyfluorination" 4 | } 5 | inputs { 6 | key: "alcohol in THF" 7 | value { 8 | components { 9 | identifiers { 10 | type: SMILES 11 | value: "$alcohol_smiles$" 12 | } 13 | amount { 14 | moles { 15 | value: 0.1 16 | units: MILLIMOLE 17 | } 18 | } 19 | reaction_role: REACTANT 20 | is_limiting: true 21 | } 22 | components { 23 | identifiers { 24 | type: SMILES 25 | value: "C1CCOC1" 26 | } 27 | amount { 28 | volume { 29 | value: 125.0 30 | units: MICROLITER 31 | } 32 | volume_includes_solutes: true 33 | } 34 | reaction_role: SOLVENT 35 | preparations { 36 | type: DRIED 37 | } 38 | } 39 | addition_order: 1 40 | } 41 | } 42 | inputs { 43 | key: "base" 44 | value { 45 | components { 46 | identifiers { 47 | type: SMILES 48 | value: "$base_smiles$" 49 | } 50 | amount { 51 | moles { 52 | value: 0.15 53 | units: MILLIMOLE 54 | } 55 | } 56 | reaction_role: REAGENT 57 | } 58 | addition_order: 3 59 | } 60 | } 61 | inputs { 62 | key: "sulfonyl fluoride solution" 63 | value { 64 | components { 65 | identifiers { 66 | type: SMILES 67 | value: "$sulfonyl_fluoride_smiles$" 68 | } 69 | amount { 70 | moles { 71 | value: 0.11 72 | units: MILLIMOLE 73 | } 74 | } 75 | reaction_role: REACTANT 76 | is_limiting: false 77 | } 78 | components { 79 | identifiers { 80 | type: SMILES 81 | value: "C1CCOC1" 82 | } 83 | amount { 84 | volume { 85 | value: 125.0 86 | units: MICROLITER 87 | } 88 | volume_includes_solutes: true 89 | } 90 | reaction_role: SOLVENT 91 | preparations { 92 | type: DRIED 93 | } 94 | } 95 | addition_order: 2 96 | } 97 | } 98 | setup { 99 | vessel { 100 | type: VIAL 101 | material { 102 | type: GLASS 103 | } 104 | attachments { 105 | type: CAP 106 | } 107 | volume { 108 | value: 1.0 109 | units: MILLILITER 110 | } 111 | } 112 | is_automated: false 113 | } 114 | conditions { 115 | temperature { 116 | control { 117 | type: AMBIENT 118 | } 119 | setpoint { 120 | value: 20.0 121 | precision: 5.0 122 | units: CELSIUS 123 | } 124 | } 125 | pressure { 126 | control { 127 | type: SEALED 128 | } 129 | atmosphere { 130 | type: AIR 131 | } 132 | } 133 | stirring { 134 | type: STIR_BAR 135 | rate { 136 | type: HIGH 137 | rpm: 600 138 | } 139 | } 140 | } 141 | workups { 142 | type: ADDITION 143 | input { 144 | components { 145 | identifiers { 146 | type: NAME 147 | value: "1-fluoronaphthalene" 148 | } 149 | identifiers { 150 | type: SMILES 151 | value: "C1=CC=C2C(=C1)C=CC=C2F" 152 | } 153 | amount { 154 | moles { 155 | value: 0.1 156 | units: MILLIMOLE 157 | } 158 | } 159 | reaction_role: INTERNAL_STANDARD 160 | } 161 | components { 162 | identifiers { 163 | type: NAME 164 | value: "CDCl3" 165 | } 166 | identifiers { 167 | type: SMILES 168 | value: "[2H]C(Cl)(Cl)Cl" 169 | } 170 | amount { 171 | volume { 172 | value: 250.0 173 | units: MICROLITER 174 | } 175 | volume_includes_solutes: true 176 | } 177 | reaction_role: WORKUP 178 | } 179 | } 180 | } 181 | outcomes { 182 | reaction_time { 183 | value: 48.0 184 | units: HOUR 185 | } 186 | products { 187 | identifiers { 188 | type: SMILES 189 | value: "$product_smiles$" 190 | } 191 | is_desired_product: true 192 | measurements { 193 | analysis_key: "19f nmr of crude" 194 | type: YIELD 195 | uses_internal_standard: true 196 | percentage { 197 | value: $product_yield$ 198 | precision: 4.8 199 | } 200 | } 201 | measurements { 202 | analysis_key: "19f nmr of crude" 203 | type: IDENTITY 204 | } 205 | } 206 | analyses { 207 | key: "19f nmr of crude" 208 | value { 209 | type: NMR_OTHER 210 | details: "19F NMR using 1 equiv 1-fluoronaphthalene in chloroform as internal standard" 211 | instrument_manufacturer: "Bruker" 212 | } 213 | } 214 | } 215 | provenance { 216 | city: "Princeton, NJ" 217 | doi: "10.1021/jacs.8b01523" 218 | publication_url: "https://pubs.acs.org/doi/10.1021/jacs.8b01523" 219 | record_created { 220 | time { 221 | value: "8/8/2020, 3:01:25 PM" 222 | } 223 | person { 224 | name: "Connor W. Coley" 225 | orcid: "0000-0002-8271-8723" 226 | organization: "MIT" 227 | email: "ccoley@mit.edu" 228 | } 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /ord_interface/editor/schema.sql: -------------------------------------------------------------------------------- 1 | -- Copyright 2020 Open Reaction Database Project Authors 2 | -- 3 | -- Licensed under the Apache License, Version 2.0 (the "License"); 4 | -- you may not use this file except in compliance with the License. 5 | -- You may obtain a copy of the License at 6 | -- 7 | -- http://www.apache.org/licenses/LICENSE-2.0 8 | -- 9 | -- Unless required by applicable law or agreed to in writing, software 10 | -- distributed under the License is distributed on an "AS IS" BASIS, 11 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | -- See the License for the specific language governing permissions and 13 | -- limitations under the License. 14 | 15 | -- To initialize the editor database on a fresh Postgres volume: 16 | -- 17 | -- $ psql -p 5432 -h localhost -U postgres -f schema.sql 18 | -- 19 | -- To populate the editor database from the db/ directory: 20 | -- 21 | -- $ ./py/migrate.py 22 | 23 | DROP DATABASE IF EXISTS editor; 24 | 25 | \c postgres; 26 | 27 | CREATE DATABASE editor; 28 | 29 | \c editor; 30 | 31 | CREATE TABLE users ( 32 | user_id CHARACTER(32) PRIMARY KEY, 33 | name TEXT, 34 | created_time INTEGER NOT NULL 35 | ); 36 | 37 | CREATE TABLE logins ( 38 | access_token TEXT PRIMARY KEY, 39 | user_id CHARACTER(32) REFERENCES users, 40 | timestamp INTEGER NOT NULL 41 | ); 42 | 43 | CREATE TABLE datasets ( 44 | user_id CHARACTER(32) REFERENCES users, 45 | name TEXT NOT NULL, 46 | serialized BYTEA NOT NULL, 47 | created timestamp default current_timestamp not null, 48 | PRIMARY KEY (user_id, name) 49 | ); 50 | 51 | -- System users: 52 | -- "review" owns read-only datasets imported from GitHub pull requests. 53 | -- "test" owns datasets imported from db/ and used only in tests. 54 | INSERT INTO users VALUES 55 | -- review: 56 | ('8df09572f3c74dbcb6003e2eef8e48fc', NULL, EXTRACT(EPOCH FROM NOW())), 57 | -- test: 58 | ('680b0d9fe649417cb092d790907bd5a5', NULL, EXTRACT(EPOCH FROM NOW())); 59 | -------------------------------------------------------------------------------- /ord_interface/interface.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Open Reaction Database Project Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Entrypoint for the web interface.""" 15 | import flask 16 | 17 | from ord_interface.editor.py import serve # pytype: disable=import-error 18 | from ord_interface.visualization import filters 19 | 20 | # TODO(skearnes): Figure out how to use this. 21 | # import flask_talisman 22 | 23 | 24 | # Set the ketcher distribution as the static folder. 25 | app = flask.Flask(__name__, static_folder="standalone", template_folder=".") 26 | # https://flask.palletsprojects.com/en/2.1.x/security/#security-headers 27 | # TODO(skearnes): Figure out how to use this. 28 | # flask_talisman.Talisman(app) 29 | # TODO(skearnes): Figure out bp.add_app_template_filter? 30 | app.jinja_env.filters.update(filters.TEMPLATE_FILTERS) # pylint: disable=no-member 31 | app.register_blueprint(serve.bp) 32 | 33 | 34 | @app.route("/ketcher") 35 | def show_ketcher(): 36 | return flask.redirect("/standalone/index.html") 37 | -------------------------------------------------------------------------------- /ord_interface/nginx.conf: -------------------------------------------------------------------------------- 1 | events { 2 | worker_connections 1024; 3 | } 4 | 5 | http { 6 | sendfile on; 7 | tcp_nopush on; 8 | tcp_nodelay on; 9 | keepalive_timeout 65; 10 | types_hash_max_size 2048; 11 | include /etc/nginx/mime.types; 12 | default_type application/octet-stream; 13 | 14 | access_log /dev/stdout; 15 | error_log /var/log/nginx.log; 16 | 17 | gzip on; 18 | gzip_disable "msie6"; 19 | 20 | upstream fastapi { 21 | server unix:/run/fastapi.sock max_fails=3 fail_timeout=60s; 22 | } 23 | 24 | upstream flask { 25 | server unix:/run/flask.sock max_fails=3 fail_timeout=60s; 26 | } 27 | 28 | server { 29 | listen 8080; 30 | 31 | location / { 32 | root /app/ord-interface/vue; 33 | index index.html; 34 | try_files $uri $uri/ /index.html; 35 | } 36 | 37 | location /api/ { 38 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 39 | proxy_set_header X-Forwarded-Proto $scheme; 40 | proxy_set_header X-Forwarded-Host $host; 41 | proxy_set_header X-Forwarded-Prefix /; 42 | proxy_connect_timeout 120; 43 | proxy_read_timeout 60; 44 | proxy_send_timeout 60; 45 | proxy_pass http://fastapi/; 46 | proxy_ignore_client_abort on; 47 | } 48 | 49 | location /editor/ { 50 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 51 | proxy_set_header X-Forwarded-Proto $scheme; 52 | proxy_set_header X-Forwarded-Host $host; 53 | proxy_set_header X-Forwarded-Prefix /; 54 | proxy_connect_timeout 120; 55 | proxy_read_timeout 60; 56 | proxy_send_timeout 60; 57 | proxy_pass http://flask/editor/; 58 | proxy_ignore_client_abort on; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /ord_interface/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2022 Open Reaction Database Project Authors 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | set -e 17 | 18 | set -x 19 | ./build_test_database.sh 20 | ARCH="x86_64" 21 | if [[ "$(uname -p)" =~ "arm" ]]; then 22 | ARCH="aarch_64" 23 | fi 24 | docker build -f Dockerfile -t openreactiondatabase/ord-interface .. --build-arg="ARCH=${ARCH}" "$@" 25 | docker compose up --detach 26 | set +x 27 | 28 | # Wait for the database to become available. 29 | function connect() { 30 | PGPASSWORD=postgres psql -p 5432 -h localhost -U postgres < /dev/null 31 | } 32 | set +e 33 | connect 34 | while [ $? -ne 0 ]; do 35 | echo waiting for postgres 36 | sleep 1 37 | connect 38 | done 39 | status=0 40 | 41 | # Run tests (only the ones that depend on the running app; not those that use testing.postgresql). 42 | ORD_INTERFACE_POSTGRES='postgresql://postgres:postgres@localhost:5432/ord?client_encoding=utf-8' \ 43 | pytest -vv --ignore=api/queries_test.py || status=1 44 | node editor/js/test.js || status=1 45 | 46 | # Shut down the containers. 47 | docker compose down 48 | 49 | # Report pass/fail. 50 | red='\033[0;31m' 51 | green='\033[0;32m' 52 | neutral='\033[0m' 53 | [ "${status}" -eq 0 ] && \ 54 | printf "${green}PASS${neutral}\n" || printf "${red}FAIL${neutral}\n" 55 | 56 | # Relay the status for GitHub CI. 57 | test "${status}" -eq 0 58 | -------------------------------------------------------------------------------- /ord_interface/start_app.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2023 Open Reaction Database Project Authors 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # 17 | # Runs the app with gunicorn behind a nginx proxy. 18 | set -e 19 | 20 | # Start nginx server. 21 | nginx 22 | 23 | # Start flask app. 24 | LOG_FORMAT='FLASK %(t)s %({user-id}o)s %(U)s %(s)s %(L)s %(b)s %(f)s "%(r)s" "%(a)s"' 25 | gunicorn ord_interface.interface:app \ 26 | --bind unix:/run/flask.sock \ 27 | --workers 2 \ 28 | --access-logfile - \ 29 | --access-logformat "${LOG_FORMAT}" \ 30 | --daemon 31 | 32 | # Start fastapi app. 33 | LOG_FORMAT='FASTAPI %(t)s %({user-id}o)s %(U)s %(s)s %(L)s %(b)s %(f)s "%(r)s" "%(a)s"' 34 | gunicorn ord_interface.api.main:app \ 35 | --worker-class uvicorn.workers.UvicornWorker \ 36 | --bind unix:/run/fastapi.sock \ 37 | --workers 2 \ 38 | --access-logfile - \ 39 | --access-logformat "${LOG_FORMAT}" 40 | -------------------------------------------------------------------------------- /ord_interface/visualization/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Open Reaction Database Project Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /ord_interface/visualization/generate_text.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Open Reaction Database Project Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Text generation for Reaction messages.""" 15 | 16 | import os 17 | import re 18 | from typing import Optional 19 | 20 | import jinja2 21 | from ord_schema import message_helpers 22 | from ord_schema.proto import reaction_pb2 23 | 24 | from ord_interface.visualization import filters 25 | 26 | 27 | def _generate(reaction: reaction_pb2.Reaction, template_string: str, line_breaks: bool, **kwargs) -> str: 28 | """Renders a Jinja2 template string with a reaction message. 29 | 30 | Args: 31 | reaction: a Reaction message. 32 | template_string: Jinja template string. 33 | line_breaks: Whether to keep line breaks. 34 | **kwargs: Additional keyword arguments to render(). 35 | 36 | Returns: 37 | Rendered string. 38 | """ 39 | env = jinja2.Environment(loader=jinja2.BaseLoader()) 40 | env.filters.update(filters.TEMPLATE_FILTERS) 41 | template = env.from_string(template_string) 42 | text = template.render(reaction=reaction, **kwargs) 43 | 44 | # Fix line breaks, extra spaces, "a" versus "an" 45 | if not line_breaks: 46 | text = "".join(text.strip().splitlines()) 47 | text = re.sub(r"[ ]{2,}", " ", text) 48 | text = re.sub(r" a ([aeiouAEIOU])", r" an \1", text) 49 | text = re.sub(r"[ ]([.;),]){1,2}", r"\1", text) 50 | return text 51 | 52 | 53 | def generate_text(reaction: reaction_pb2.Reaction) -> str: 54 | """Generates a textual reaction description.""" 55 | with open(os.path.join(os.path.dirname(__file__), "template.txt"), "r") as f: 56 | template = f.read() 57 | return _generate(reaction, template_string=template, line_breaks=False) 58 | 59 | 60 | def generate_html(reaction: reaction_pb2.Reaction, compact=False, bond_length: Optional[int] = None) -> str: 61 | """Generates an HTML reaction description.""" 62 | # Special handling for e.g. USPTO reactions. 63 | reaction_smiles = message_helpers.get_reaction_smiles(reaction) 64 | if reaction_smiles and not reaction.inputs and not reaction.outcomes: 65 | reaction = message_helpers.reaction_from_smiles(reaction_smiles) 66 | with open(os.path.join(os.path.dirname(__file__), "template.html"), "r") as f: 67 | template = f.read() 68 | if not bond_length: 69 | if compact: 70 | bond_length = 18 71 | else: 72 | bond_length = 25 73 | return _generate( 74 | reaction, 75 | template_string=template, 76 | line_breaks=True, 77 | compact=compact, 78 | bond_length=bond_length, 79 | ) 80 | -------------------------------------------------------------------------------- /ord_interface/visualization/generate_text_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Open Reaction Database Project Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Tests for ord_interface.visualization.generate_text.""" 15 | import pytest 16 | from ord_schema import message_helpers, units 17 | from ord_schema.proto import reaction_pb2 18 | 19 | from ord_interface.visualization import generate_text 20 | 21 | 22 | @pytest.fixture 23 | def reaction() -> reaction_pb2.Reaction: 24 | resolver = units.UnitResolver() 25 | reaction = reaction_pb2.Reaction() 26 | reaction.setup.is_automated = True 27 | reaction.inputs["dummy_input"].components.add().CopyFrom( 28 | message_helpers.build_compound( 29 | name="n-hexane", 30 | smiles="CCCCCC", 31 | role="reactant", 32 | amount="1 milliliters", 33 | ) 34 | ) 35 | reaction.inputs["dummy_input"].components.add().CopyFrom( 36 | message_helpers.build_compound( 37 | name="THF", 38 | smiles="C1OCCC1", 39 | role="solvent", 40 | amount="40 liters", 41 | ) 42 | ) 43 | reaction.inputs["dummy_input2"].components.add().CopyFrom( 44 | message_helpers.build_compound( 45 | name="Pd", 46 | smiles="[Pd]", 47 | role="catalyst", 48 | amount="catalytic", 49 | ) 50 | ) 51 | reaction.conditions.pressure.atmosphere.type = reaction_pb2.PressureConditions.Atmosphere.OXYGEN 52 | reaction.conditions.stirring.rate.rpm = 100 53 | reaction.conditions.temperature.control.type = reaction_pb2.TemperatureConditions.TemperatureControl.OIL_BATH 54 | reaction.conditions.temperature.setpoint.CopyFrom( 55 | reaction_pb2.Temperature(value=100, units=reaction_pb2.Temperature.CELSIUS) 56 | ) 57 | outcome = reaction.outcomes.add() 58 | outcome.reaction_time.CopyFrom(resolver.resolve("40 minutes")) 59 | outcome.products.add().identifiers.extend( 60 | message_helpers.build_compound(name="hexanone", smiles="CCCCC(=O)C").identifiers 61 | ) 62 | yield reaction 63 | 64 | 65 | def test_text(reaction): 66 | text = generate_text.generate_text(reaction) 67 | expected = ["vessel", "oil bath", "after 40 min", "as a solvent", "hexanone", "automatically", "mL", "catalytic"] 68 | for value in expected: 69 | assert value in text 70 | 71 | 72 | def test_html(reaction): 73 | html = generate_text.generate_html(reaction) 74 | expected = [" 1 %} and {% endif %} 65 | {% if observation.comment %} 66 | after 67 | {% if observation.time.value %} 68 | {{ observation.time|unit_format }} 69 | {% else %} 70 | an unspecified amount of time 71 | {% endif %}, 72 | {{ observation.comment }} 73 | {% if not loop.last %}; {% endif %} 74 | {% endif %} 75 | {% endfor %}. 76 | {% endif %} 77 | 78 | {# TODO(ccoley) WORKUP #} 79 | {% if reaction.workups %} 80 | The workup procedure consisted of 81 | {% for workup in reaction.workups %} 82 | {% if loop.last and loop.index > 1 %} and {% endif %} 83 | ({{ loop.index }}) 84 | 85 | {% if not loop.last %}; {% endif %} 86 | {% endfor %} 87 | {% endif %} 88 | 89 | {# OUTCOME #} 90 | {% for outcome in reaction.outcomes %} 91 | The reaction was {% if not loop.first %} also {% endif %} analyzed after 92 | {% if outcome.reaction_time.value %} 93 | {{ outcome.reaction_time|unit_format }} 94 | {% else %} 95 | an unspecified amount of time 96 | {% endif %} 97 | by 98 | {% for analysis in outcome.analyses.values() %} 99 | {% if loop.last and loop.index > 1 %} and {% endif %} 100 | {{ analysis|analysis_format }} 101 | {{ analysis.details|parenthetical_if_def }} 102 | {% if not loop.last %}, {% endif %} 103 | {% endfor %}. 104 | 105 | {# PRODUCTS #} 106 | {% for product in outcome.products %} 107 | {{ product|compound_name }} 108 | {% if product.is_desired_product|is_true %} 109 | , the desired product, 110 | {% endif %} 111 | 112 | was observed 113 | 114 | {# PRODUCT YIELD #} 115 | {% if product|product_yield %} 116 | with a yield of {{ product|product_yield }} 117 | {% endif %}. 118 | 119 | {# PRODUCT COLOR/TEXTURE #} 120 | {{ product|product_color_texture }}. 121 | 122 | 123 | {% endfor %} 124 | 125 | {% endfor %} 126 | 127 | {# DICLAIMER ABOUT SCHEMA LIMITATIONS #} 128 | {% if reaction.conditions.conditions_are_dynamic|is_true %} 129 | Note: the conditions for this reaction may not precisely fit the schema, so 130 | additional care should be taken when reading this description. 131 | {% endif %} 132 | {% if reaction.conditions.details %} 133 | Condition details: {{ reaction.conditions.details }}. 134 | {% endif %} 135 | {% if reaction.notes.procedure_details %} 136 | Procedure details: {{ reaction.notes.procedure_details }}. 137 | {% endif %} 138 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | line-length = 120 3 | 4 | [tool.isort] 5 | skip_glob = ["**/*_pb2.py*"] 6 | line_length = 120 7 | profile = "black" 8 | known_third_party = ["ord_schema"] 9 | 10 | [tool.pytype] 11 | inputs = ["ord_interface"] 12 | exclude = ["**/*_test.py", "**/test_*.py", "build/", "dist/"] 13 | keep_going = true 14 | disable = ["module-attr", "pyi-error"] 15 | jobs = 0 16 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # Copyright 2020 Open Reaction Database Project Authors 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | """Installer script.""" 16 | import setuptools 17 | 18 | with open("README.md") as f: 19 | long_description = f.read() 20 | 21 | 22 | if __name__ == "__main__": 23 | setuptools.setup( 24 | name="ord-interface", 25 | description="Interface for the Open Reaction Database", 26 | long_description=long_description, 27 | long_description_content_type="text/markdown", 28 | url="https://github.com/Open-Reaction-Database/ord-interface", 29 | classifiers=[ 30 | "Programming Language :: Python :: 3", 31 | "License :: OSI Approved :: Apache Software License", 32 | "Operating System :: OS Independent", 33 | ], 34 | packages=setuptools.find_packages(), 35 | package_data={ 36 | # NOTE(skearnes): These entries seem to be required for actions/checkout to find these files. 37 | "ord_interface.api": ["**/*.pb.gz"], 38 | "ord_interface.visualization": ["template.html", "template.txt"], 39 | }, 40 | python_requires=">=3.10", 41 | install_requires=[ 42 | "docopt>=0.6.2", 43 | "fastapi", 44 | "flask>=1.1.2", 45 | "gunicorn", 46 | "jinja2>=2.0.0", 47 | "numpy<2", 48 | "ord-schema==0.3.93", 49 | "pandas>=1.0.4", 50 | "protobuf==4.22.3", 51 | "psycopg[binary,pool]>=3", 52 | "psycopg2-binary>=2.8.5", 53 | "pygithub>=1.51", 54 | "rdkit>=2021.9.5", 55 | "redis[hiredis]", 56 | "requests>=2.24.0", 57 | "setuptools", 58 | "testing-postgresql", 59 | "uvicorn[standard]", 60 | ], 61 | extras_require={ 62 | "tests": [ 63 | "black>=22.3.0", 64 | "coverage>=5.2.1", 65 | "httpx", 66 | "pylint>=2.13.9", 67 | "pytest>=6.2.5", 68 | "pytest-asyncio", 69 | "pytest-cov>=3.0.0", 70 | "pytype>=2022.5.19", 71 | "tenacity", 72 | "testing.redis", 73 | ], 74 | }, 75 | ) 76 | --------------------------------------------------------------------------------