Skip to content
This repository was archived by the owner on Jan 10, 2023. It is now read-only.

Commit

Permalink
Changed how blast queries are issued and the reporting UI, among othe…
Browse files Browse the repository at this point in the history
…r things.
  • Loading branch information
falquaddoomi committed Aug 29, 2019
1 parent 3996eb3 commit 4434356
Show file tree
Hide file tree
Showing 11 changed files with 269 additions and 115 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/__pycache__
*.pyc
/.venv
/logs
/uploads
/blast_db
/static/node_modules
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
<router-link to="/debug" class="nav-link" tag="a">Debug</router-link>
</li>

<!--
<li class="nav-item">
<router-link to="/designer" class="nav-link" tag="a">Designer</router-link>
</li>
-->
</ul>
</div>
</nav>
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/assets/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,17 @@
.slideup-enter, .slideup-leave-to /* .list-leave-active below version 2.1.8 */ {
opacity: 0;
transform: translateY(30px);
}

/* faster version of the slideup animation */
.slideup_fast-item {
display: inline-block;
margin-bottom: 10px;
}
.slideup_fast-enter-active, .slideup_fast-leave-active {
transition: all 200s;
}
.slideup_fast-enter, .slideup_fast-leave-to /* .list-leave-active below version 2.1.8 */ {
opacity: 0;
transform: translateY(30px);
}
65 changes: 41 additions & 24 deletions frontend/src/components/Blaster.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,25 @@
</button>
</div>

<div v-if="blast_status || blast_hits">
<div class="status_pane">
<h4>Status:</h4>

<transition-group name="slideright" tag="ol">
<li v-for="(rec, idx) in blast_status" :key="idx">
{{ rec.status }}
<fa-icon v-if="blast_pending && idx === blast_status.length - 1" icon="spinner" pulse />
</li>
</transition-group>
</div>
<div v-if="blast_status || blast_hits" class="status_and_results">
<transition-group name="slideup" mode="out-in">

<div class="status_pane" key="status_pane" v-if="!blast_result || !blast_first_five || blast_first_five.length <= 0">
<h4>Status:</h4>

<div class="status_scroller" ref="status_scroller">
<transition-group name="slideright" tag="ol">
<li v-for="(rec, idx) in blast_status" :key="idx">
{{ rec.status }}
<fa-icon v-if="blast_pending && idx === blast_status.length - 1" icon="circle-notch" spin />
</li>
</transition-group>
</div>
</div>

<div class="results_pane">
<transition name="slideup">
<div v-if="blast_result">
<h3 style="text-align: left;">Hits:</h3>
<div class="results_pane" key="results_pane" v-if="blast_result">
<div>
<h3 style="text-align: left;">Hits ({{ blast_first_five.length }}):</h3>
<hr />

<div v-if="blast_first_five.length > 0" class="species-tiles">
Expand All @@ -38,13 +41,14 @@
</div>
</div>
<div v-else class="no-results">
No matching species found! <fa-icon icon="sad-cry" />
<br />
Try again with another sequence!
No matching species found.
<br />
Try again with another sequence!
</div>
</div>
</transition>
</div>
</div>

</transition-group>
</div>

<Sidebar ref="sidedar">
Expand Down Expand Up @@ -72,8 +76,8 @@

<div class="mini-sec"><b>Bases:</b>
<div class="alignment">{{ hit.qseq }}
{{ hit.midline }}
{{ hit.hseq }}
{{ hit.midline }}
{{ hit.hseq }}
</div>
</div>
</div>
Expand All @@ -87,6 +91,7 @@
<script>
import * as oboe from 'oboe';
import uniqBy from "lodash/uniqBy";
import countBy from "lodash/countBy";
import {col_to_base} from "../constants";
import SpeciesResult from "./SpeciesResult";
import Sidebar from "./Sidebar";
Expand Down Expand Up @@ -127,6 +132,8 @@ export default {
return uniqBy(this.blast_hits, x => x.sciname);
},
blast_first_five() {
if (!this.blast_unique_hits)
return null;
return this.blast_unique_hits.slice(0, 6);
}
},
Expand All @@ -144,7 +151,13 @@ export default {
})
.node('!.{status}', rec => {
console.log(rec);
this.blast_status.push(rec)
this.blast_status.push(rec);
const status_scroller = this.$refs.status_scroller;
if (status_scroller) {
setTimeout(() => {
status_scroller.scrollTop = status_scroller.scrollHeight;
}, 10);
}
})
.node('!.{results}', rec => {
console.log("Done: ", rec);
Expand All @@ -163,7 +176,7 @@ export default {
})
},
show_details(options) {
this.$refs.sidedar.showModal(options);
this.$refs.sidedar.showModal(options);
}
}
}
Expand All @@ -187,6 +200,10 @@ export default {
font-size: 20px;
/*border: solid 1px #ccc; border-radius: 5px;*/
}
.status_pane .status_scroller {
max-height: 6.5em;
overflow-y: auto;
}
.species-tiles {
display: flex; flex-wrap: wrap; justify-content: space-around;
Expand Down
8 changes: 7 additions & 1 deletion frontend/src/components/SpeciesResult.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
<div class="card-body">
<h5 class="card-title">{{ species }}</h5>
<p class="card-text">Score: {{ score }}</p>
<a href="#" @click.stop="show_details" class="btn btn-primary">Details</a>
<a href="#" @click.stop="show_details" class="btn btn-primary">Details</a>&nbsp;
<a :href="gsearch_url" target="_blank" class="btn btn-success">Search <fa-icon icon="external-link-alt" /></a>
</div>
</div>
</template>
Expand All @@ -29,6 +30,11 @@ export default {
error: null
}
},
computed: {
gsearch_url() {
return `https://www.google.com/search?safe=active&q=%22${this.species}%22`;
}
},
mounted() {
// fire off a request to our API for the image
this.loading = true;
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ import './assets/modal-sidebar.css';
// font-awesome stuff
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faUserSecret, faSpinner, faTrash, faCircleNotch, faAngleDoubleDown, faQuestion, faSadCry
faUserSecret, faSpinner, faTrash, faCircleNotch, faAngleDoubleDown, faQuestion, faSadCry, faExternalLinkAlt
} from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'

library.add(faUserSecret, faSpinner, faTrash, faCircleNotch, faAngleDoubleDown, faQuestion, faSadCry);
library.add(faUserSecret, faSpinner, faTrash, faCircleNotch, faAngleDoubleDown, faQuestion, faSadCry, faExternalLinkAlt);

Vue.component('fa-icon', FontAwesomeIcon);

Expand Down
4 changes: 3 additions & 1 deletion sequencer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def create_app(test_config=None):
app.config.from_mapping(
SECRET_KEY='dev',
DATABASE=os.path.join(app.instance_path, 'sequencer.sqlite'),
BLAST_DB_DIR=os.path.join(app.root_path, 'blast_db'),
# CACHE_DIR=os.path.join(app.instance_path, 'cache'),
# CACHE_TYPE="filesystem"
)
Expand Down Expand Up @@ -49,7 +50,8 @@ def create_app(test_config=None):
app.register_blueprint(
api.bp,
GCE_KEY=app.config['GCE_KEY'],
GCE_PROJECT_CX=app.config['GCE_PROJECT_CX']
GCE_PROJECT_CX=app.config['GCE_PROJECT_CX'],
BLAST_DB_DIR=app.config['BLAST_DB_DIR']
)

# inject frontend-serving bits
Expand Down
102 changes: 72 additions & 30 deletions sequencer/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@
)

from sequencer import cache
from sequencer.default_settings import MOCK_BLAST_HAS_RESULTS
from sequencer.default_settings import MOCK_BLAST_HAS_RESULTS, USE_GIS_CACHING, BLAST_TIMEOUT
from sequencer.support import ev3_reader
from sequencer.db import get_db
from sequencer.support.blaster import blast_sequence
from sequencer.support.blaster import blast_sequence, blast_sequence_local
from sequencer.support.ev3_reader import query_full_sequence

GCE_KEY = None
GCE_PROJECT_CX = None
BLAST_DB_DIR = None


class APIBlueprint(Blueprint):
Expand All @@ -31,6 +32,7 @@ def register(self, app, options, first_registration=False):
config = app.config
GCE_KEY = config.get('GCE_KEY')
GCE_PROJECT_CX = config.get('GCE_PROJECT_CX')
BLAST_DB_DIR = config.get('BLAST_DB_DIR')

super(APIBlueprint, self).register(app, options, first_registration)

Expand All @@ -50,6 +52,10 @@ def ping():
return jsonify({'msg': 'pong!'})


# ------------------------------------------------------
# --- LEGO Brick Comm.
# ------------------------------------------------------

@bp.route('/nudge/<direction>', defaults={'amount': 1})
@bp.route('/nudge/<direction>/<amount>')
def nudge(direction, amount):
Expand Down Expand Up @@ -99,6 +105,10 @@ def g():
}), 500)


# ------------------------------------------------------
# --- BLAST proxy
# ------------------------------------------------------

@bp.route('/blast')
def blast():
try:
Expand All @@ -107,7 +117,29 @@ def blast():
return jsonify({'error': 'must specify a sequence'})

def g():
gen = blast_sequence(sequence, "nr")
gen = blast_sequence(sequence, timeout=BLAST_TIMEOUT)
not_first = False
yield "["
for rec in gen:
if not_first:
yield ","
not_first = True
yield json.dumps(rec)
yield "]"

# incrementally yields json objects
return Response(stream_with_context(g()))


@bp.route('/local_blast')
def local_blast():
try:
sequence = request.args['sequence']
except KeyError:
return jsonify({'error': 'must specify a sequence'})

def g():
gen = blast_sequence_local(sequence, blast_db_dir=BLAST_DB_DIR)
not_first = False
yield "["
for rec in gen:
Expand All @@ -121,6 +153,43 @@ def g():
return Response(stream_with_context(g()))


# ------------------------------------------------------
# --- GIS species images
# ------------------------------------------------------

@bp.route('/species_img')
def species_img():
species_name = request.args.get('species')
# species_name = "Homo sapiens"

cache_key = 'SPECIES_IMG:%s' % species_name
cached_val = cache.get(cache_key) if USE_GIS_CACHING else None

if not cached_val:
resp = requests.get('https://www.googleapis.com/customsearch/v1/siterestrict', params={
"key": GCE_KEY,
"cx": GCE_PROJECT_CX,
"safe": "high",
"fileType": "png|jpg|gif",
"imgType": "photo",
"searchType": "image",
"limit": 1,
"q": species_name
})
result = resp.json()

cached_val = [x['link'] for x in result['items']]
cache.set(cache_key, cached_val)

return jsonify({
'results': cached_val
})


# ------------------------------------------------------
# --- mocked endpoints
# ------------------------------------------------------

@bp.route('/mock_blast', methods=['GET','POST'])
def mock_blast():
"""
Expand Down Expand Up @@ -156,30 +225,3 @@ def mock_blast():
elif arg_set.get('FORMAT_TYPE') == 'JSON2_S':
return send_file("mockdata/canned_blast.json") if MOCK_BLAST_HAS_RESULTS else send_file("mockdata/canned_blast_noresults.json")


@bp.route('/species_img')
def species_img():
species_name = request.args.get('species')

cache_key = 'SPECIES_IMG:%s' % species_name
cached_val = cache.get(cache_key)

if not cached_val:
resp = requests.get('https://www.googleapis.com/customsearch/v1/siterestrict', params={
"key": GCE_KEY,
"cx": GCE_PROJECT_CX,
"safe": "high",
"fileType": "png|jpg|gif",
"imgType": "photo",
"searchType": "image",
"limit": 1,
"q": species_name
})
result = resp.json()

cached_val = [x['link'] for x in result['items']]
cache.set(cache_key, cached_val)

return jsonify({
'results': cached_val
})
Loading

0 comments on commit 4434356

Please sign in to comment.