Skip to content

Analysis refactor GUI part1 #2076

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
3601c29
fix #1505
antgonza Jan 2, 2017
0d6788e
improving some GUI stuff
antgonza Jan 3, 2017
12406cc
improving some GUI stuff - missing lines
antgonza Jan 3, 2017
958fcbe
pull upstream master
antgonza Jan 4, 2017
a57ef23
addressing all comments
antgonza Jan 5, 2017
2ead7a6
ready for review
antgonza Jan 5, 2017
73a78e7
fix #1987
antgonza Jan 16, 2017
e64a22a
Merge pull request #2036 from antgonza/fix-1505
josenavas Jan 16, 2017
0dcae8b
Merge pull request #2047 from antgonza/fix-1987
josenavas Jan 17, 2017
4a5bbbc
initial commit
antgonza Jan 18, 2017
f99975c
requested changes
antgonza Jan 18, 2017
ed899a8
Merge pull request #2049 from antgonza/add-processing-suggestions
josenavas Jan 18, 2017
d508320
fix filter job list
antgonza Jan 18, 2017
025cc1e
Merge pull request #2050 from antgonza/fix-filter-job-list
josenavas Jan 18, 2017
599bcde
Fixing server cert (#2051)
josenavas Jan 19, 2017
d12ccfe
fix get_studies
antgonza Jan 20, 2017
b33983b
flake8
antgonza Jan 20, 2017
b4f1b1f
fix #503
antgonza Jan 20, 2017
62a1b93
fix #2010
antgonza Jan 20, 2017
2e36141
fix #1913
antgonza Jan 21, 2017
e006e20
fix errors
antgonza Jan 21, 2017
c174693
Merge pull request #2052 from antgonza/fix-get_studies
josenavas Jan 23, 2017
131dd6a
Merge pull request #2053 from antgonza/fix-by-blinking
josenavas Jan 23, 2017
ccb55bd
addressing @josenavas comment
antgonza Jan 24, 2017
dfe2e83
flake8
antgonza Jan 24, 2017
15fcceb
Merge pull request #2056 from antgonza/fix-1913
josenavas Jan 24, 2017
7f97f2a
fix #1010
antgonza Jan 26, 2017
9eb9dbb
fix #1066 (#2058)
antgonza Jan 26, 2017
23104d7
addressing @josenavas comments
antgonza Jan 27, 2017
1f1e826
fix #1961
antgonza Jan 27, 2017
19a9dda
fix #1837
antgonza Jan 27, 2017
19889f9
Automatic jobs & new stats (#2057)
antgonza Jan 27, 2017
4e380e0
Merge pull request #2060 from antgonza/fix-1961
wasade Jan 28, 2017
6f0dd71
generalizing this functionality
antgonza Jan 28, 2017
ed9fc65
fix #1816
antgonza Jan 29, 2017
4b19b45
fix #1959
antgonza Jan 30, 2017
d9b41e8
addressing @josenavas comments
antgonza Feb 1, 2017
5ef06ae
addressing @josenavas comments
antgonza Feb 2, 2017
5e3504a
fixing error
antgonza Feb 2, 2017
d10096a
Merge branch 'master' of https://github.com/biocore/qiita into fix-1010
antgonza Feb 2, 2017
661342f
fixed?
antgonza Feb 2, 2017
fcd249b
addressing @josenavas comments
antgonza Feb 3, 2017
f3c1216
Merge pull request #2063 from antgonza/fix-1816
josenavas Feb 3, 2017
a91a6fd
Merge pull request #2064 from antgonza/fix-1959
tanaes Feb 3, 2017
7b9fa6f
addressing @wasade comments
antgonza Feb 3, 2017
33bcbe5
Merge pull request #2059 from antgonza/fix-1010
josenavas Feb 3, 2017
5e4bd9b
Merge branch 'master' of https://github.com/biocore/qiita into fix-1837
antgonza Feb 3, 2017
8bf3d6e
fix flake8
antgonza Feb 3, 2017
7807bac
Merge pull request #2061 from antgonza/fix-1837
josenavas Feb 3, 2017
6360675
generate biom and metadata release (#2066)
antgonza Feb 3, 2017
811b7a7
database changes to fix 969
antgonza Feb 3, 2017
751d4ad
adding delete
antgonza Feb 3, 2017
65a86df
addressing @josenavas comments
antgonza Feb 3, 2017
b1817dd
addressing @ElDeveloper comments
antgonza Feb 4, 2017
18d77e1
duh!
antgonza Feb 4, 2017
01c656c
Merge pull request #2071 from antgonza/fix-969-db
josenavas Feb 6, 2017
53188a6
fix generate_biom_and_metadata_release (#2072)
antgonza Feb 7, 2017
1ab4e3b
Fixing merge conflicts with master
josenavas Feb 8, 2017
1e8332e
Merge branch 'analysis-refactor' of https://github.com/biocore/qiita …
josenavas Feb 9, 2017
cb67d3d
Removing qiita ware code that will not be used anymore
josenavas Feb 9, 2017
5a5127d
Merge branch 'analysis-refactor' of https://github.com/biocore/qiita …
josenavas Feb 9, 2017
0033480
Organizing the handlers and new analysis description page
josenavas Feb 9, 2017
067f14f
Addressing @antgonza's comments
josenavas Feb 10, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions qiita_pet/handlers/analysis_handlers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2014--, The Qiita Development Team.
#
# Distributed under the terms of the BSD 3-clause License.
#
# The full license is in the file LICENSE, distributed with this software.
# -----------------------------------------------------------------------------

from .util import check_analysis_access
from .base_handlers import (CreateAnalysisHandler, AnalysisDescriptionHandler,
AnalysisGraphHandler)
from .listing_handlers import (ListAnalysesHandler, AnalysisSummaryAJAX,
SelectedSamplesHandler)

__all__ = ['CreateAnalysisHandler', 'AnalysisDescriptionHandler',
'AnalysisGraphHandler', 'ListAnalysesHandler',
'AnalysisSummaryAJAX', 'SelectedSamplesHandler',
'check_analysis_access']
105 changes: 105 additions & 0 deletions qiita_pet/handlers/analysis_handlers/base_handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2014--, The Qiita Development Team.
#
# Distributed under the terms of the BSD 3-clause License.
#
# The full license is in the file LICENSE, distributed with this software.
# -----------------------------------------------------------------------------

from tornado.web import authenticated

from qiita_core.util import execute_as_transaction
from qiita_core.qiita_settings import qiita_config
from qiita_pet.handlers.base_handlers import BaseHandler
from qiita_pet.handlers.analysis_handlers import check_analysis_access
from qiita_pet.handlers.util import to_int
from qiita_db.analysis import Analysis


class CreateAnalysisHandler(BaseHandler):
@authenticated
@execute_as_transaction
def post(self):
name = self.get_argument('name')
desc = self.get_argument('description')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this make a desc required or do we need to put a default value?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it's not required (same behavior as before). If the user doesn't introduce anything, the description will be blank (empty string).

analysis = Analysis.create(self.current_user, name, desc,
from_default=True)

self.redirect(u"%s/analysis/description/%s/"
% (qiita_config.portal_dir, analysis.id))


class AnalysisDescriptionHandler(BaseHandler):
@authenticated
@execute_as_transaction
def get(self, analysis_id):
analysis = Analysis(analysis_id)
check_analysis_access(self.current_user, analysis)

self.render("analysis_description.html", analysis_name=analysis.name,
analysis_id=analysis_id,
analysis_description=analysis.description)


def analyisis_graph_handler_get_request(analysis_id, user):
"""Returns the graph information of the analysis

Parameters
----------
analysis_id : int
The analysis id
user : qiita_db.user.User
The user performing the request

Returns
-------
dict with the graph information
"""
analysis = Analysis(analysis_id)
# Check if the user actually has access to the analysis
check_analysis_access(user, analysis)

# A user has full access to the analysis if it is one of its private
# analyses, the analysis has been shared with the user or the user is a
# superuser or admin
full_access = (analysis in (user.private_analyses | user.shared_analyses)
or user.level in {'superuser', 'admin'})

nodes = set()
edges = set()
# Loop through all the initial artifacts of the analysis
for a in analysis.artifacts:
g = a.descendants_with_jobs
# Loop through all the nodes in artifact descendants graph
for n in g.nodes():
# Get if the object is an artifact or a job
obj_type = n[0]
# Get the actual object
obj = n[1]
if obj_type == 'job':
name = obj.command.name
elif not full_access and not obj.visibility == 'public':
# The object is an artifact, it is not public and the user
# doesn't have full access, so we don't include it in the
# graph
continue
else:
name = '%s - %s' % (obj.name, obj.artifact_type)
nodes.add((obj_type, obj.id, name))

edges.update({(s[1].id, t[1].id) for s, t in g.edges()})

# Nodes and Edges are sets, but the set object can't be serialized using
# JSON. Transforming them to lists so when this is returned to the GUI
# over HTTP can be JSONized.
return {'edges': list(edges), 'nodes': list(nodes)}


class AnalysisGraphHandler(BaseHandler):
@authenticated
@execute_as_transaction
def get(self):
analysis_id = to_int(self.get_argument('analysis_id'))
response = analyisis_graph_handler_get_request(
analysis_id, self.current_user)
self.write(response)
135 changes: 135 additions & 0 deletions qiita_pet/handlers/analysis_handlers/listing_handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2014--, The Qiita Development Team.
#
# Distributed under the terms of the BSD 3-clause License.
#
# The full license is in the file LICENSE, distributed with this software.
# -----------------------------------------------------------------------------

from functools import partial
from json import dumps
from collections import defaultdict
from future.utils import viewitems

from tornado.web import authenticated

from qiita_core.qiita_settings import qiita_config
from qiita_core.util import execute_as_transaction
from qiita_pet.handlers.base_handlers import BaseHandler
from qiita_pet.handlers.util import download_link_or_path
from qiita_pet.handlers.analysis_handlers import check_analysis_access
from qiita_pet.util import is_localhost
from qiita_db.util import get_filepath_id
from qiita_db.analysis import Analysis
from qiita_db.logger import LogEntry
from qiita_db.reference import Reference
from qiita_db.artifact import Artifact


class ListAnalysesHandler(BaseHandler):
@authenticated
@execute_as_transaction
def get(self):
message = self.get_argument('message', '')
level = self.get_argument('level', '')
user = self.current_user

analyses = user.shared_analyses | user.private_analyses

is_local_request = is_localhost(self.request.headers['host'])
gfi = partial(get_filepath_id, 'analysis')
dlop = partial(download_link_or_path, is_local_request)
mappings = {}
bioms = {}
tgzs = {}
for analysis in analyses:
_id = analysis.id
# getting mapping file
mapping = analysis.mapping_file
if mapping is not None:
mappings[_id] = dlop(mapping, gfi(mapping), 'mapping file')
else:
mappings[_id] = ''

bioms[_id] = ''
# getting tgz file
tgz = analysis.tgz
if tgz is not None:
tgzs[_id] = dlop(tgz, gfi(tgz), 'tgz file')
else:
tgzs[_id] = ''

self.render("list_analyses.html", analyses=analyses, message=message,
level=level, is_local_request=is_local_request,
mappings=mappings, bioms=bioms, tgzs=tgzs)

@authenticated
@execute_as_transaction
def post(self):
analysis_id = int(self.get_argument('analysis_id'))
analysis = Analysis(analysis_id)
analysis_name = analysis.name.decode('utf-8')

check_analysis_access(self.current_user, analysis)

try:
Analysis.delete(analysis_id)
msg = ("Analysis <b><i>%s</i></b> has been deleted." % (
analysis_name))
level = "success"
except Exception as e:
e = str(e)
msg = ("Couldn't remove <b><i>%s</i></b> analysis: %s" % (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to add a reason of why? If too difficult, that's fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last part of the message includes the str(e) so the reason should be in there.

analysis_name, e))
level = "danger"
LogEntry.create('Runtime', "Couldn't remove analysis ID %d: %s" %
(analysis_id, e))

self.redirect(u"%s/analysis/list/?level=%s&message=%s"
% (qiita_config.portal_dir, level, msg))


class AnalysisSummaryAJAX(BaseHandler):
@authenticated
@execute_as_transaction
def get(self):
info = self.current_user.default_analysis.summary_data()
self.write(dumps(info))


class SelectedSamplesHandler(BaseHandler):
@authenticated
@execute_as_transaction
def get(self):
# Format sel_data to get study IDs for the processed data
sel_data = defaultdict(dict)
proc_data_info = {}
sel_samps = self.current_user.default_analysis.samples
for aid, samples in viewitems(sel_samps):
a = Artifact(aid)
sel_data[a.study][aid] = samples
# Also get processed data info
processing_parameters = a.processing_parameters
if processing_parameters is None:
params = None
algorithm = None
else:
cmd = processing_parameters.command
params = processing_parameters.values
if 'reference' in params:
ref = Reference(params['reference'])
del params['reference']

params['reference_name'] = ref.name
params['reference_version'] = ref.version
algorithm = '%s (%s)' % (cmd.software.name, cmd.name)

proc_data_info[aid] = {
'processed_date': str(a.timestamp),
'algorithm': algorithm,
'data_type': a.data_type,
'params': params
}

self.render("analysis_selected.html", sel_data=sel_data,
proc_info=proc_data_info)
7 changes: 7 additions & 0 deletions qiita_pet/handlers/analysis_handlers/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2014--, The Qiita Development Team.
#
# Distributed under the terms of the BSD 3-clause License.
#
# The full license is in the file LICENSE, distributed with this software.
# -----------------------------------------------------------------------------
83 changes: 83 additions & 0 deletions qiita_pet/handlers/analysis_handlers/tests/test_base_handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2014--, The Qiita Development Team.
#
# Distributed under the terms of the BSD 3-clause License.
#
# The full license is in the file LICENSE, distributed with this software.
# -----------------------------------------------------------------------------

from unittest import TestCase, main
from json import loads

from tornado.web import HTTPError

from qiita_db.user import User
from qiita_db.analysis import Analysis
from qiita_pet.test.tornado_test_base import TestHandlerBase
from qiita_pet.handlers.analysis_handlers.base_handlers import (
analyisis_graph_handler_get_request)


class TestBaseHandlersUtils(TestCase):
def test_analyisis_graph_handler_get_request(self):
obs = analyisis_graph_handler_get_request(1, User('test@foo.bar'))
# The job id is randomly generated in the test environment. Gather
# it here. There is only 1 job in the first artifact of the analysis
job_id = Analysis(1).artifacts[0].jobs()[0].id
exp = {'edges': [(8, job_id), (job_id, 9)],
'nodes': [('job', job_id, 'Single Rarefaction'),
('artifact', 9, 'noname - BIOM'),
('artifact', 8, 'noname - BIOM')]}
self.assertItemsEqual(obs, exp)
self.assertItemsEqual(obs['edges'], exp['edges'])
self.assertItemsEqual(obs['nodes'], exp['nodes'])

# An admin has full access to the analysis
obs = analyisis_graph_handler_get_request(1, User('admin@foo.bar'))
self.assertItemsEqual(obs, exp)
self.assertItemsEqual(obs['edges'], exp['edges'])
self.assertItemsEqual(obs['nodes'], exp['nodes'])

# If the analysis is shared with the user he also has access
obs = analyisis_graph_handler_get_request(1, User('shared@foo.bar'))
self.assertItemsEqual(obs, exp)
self.assertItemsEqual(obs['edges'], exp['edges'])
self.assertItemsEqual(obs['nodes'], exp['nodes'])

# The user doesn't have access to the analysis
with self.assertRaises(HTTPError):
analyisis_graph_handler_get_request(1, User('demo@microbio.me'))


class TestBaseHandlers(TestHandlerBase):
def test_post_create_analysis_handler(self):
args = {'name': 'New Test Analysis',
'description': 'Test Analysis Description'}
response = self.post('/analysis/create/', args)
self.assertRegexpMatches(
response.effective_url,
r"http://localhost:\d+/analysis/description/\d+/")
self.assertEqual(response.code, 200)

def test_get_analysis_description_handler(self):
response = self.get('/analysis/description/1/')
self.assertEqual(response.code, 200)

def test_get_analysis_graph_handler(self):
response = self.get('/analysis/description/graph/', {'analysis_id': 1})
self.assertEqual(response.code, 200)
# The job id is randomly generated in the test environment. Gather
# it here. There is only 1 job in the first artifact of the analysis
job_id = Analysis(1).artifacts[0].jobs()[0].id
obs = loads(response.body)
exp = {'edges': [[8, job_id], [job_id, 9]],
'nodes': [['job', job_id, 'Single Rarefaction'],
['artifact', 9, 'noname - BIOM'],
['artifact', 8, 'noname - BIOM']]}
self.assertItemsEqual(obs, exp)
self.assertItemsEqual(obs['edges'], exp['edges'])
self.assertItemsEqual(obs['nodes'], exp['nodes'])


if __name__ == '__main__':
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# -----------------------------------------------------------------------------
# Copyright (c) 2014--, The Qiita Development Team.
#
# Distributed under the terms of the BSD 3-clause License.
#
# The full license is in the file LICENSE, distributed with this software.
# -----------------------------------------------------------------------------

from unittest import main
from json import loads

from qiita_pet.test.tornado_test_base import TestHandlerBase


class TestListingHandlers(TestHandlerBase):
def test_get_list_analyses_handler(self):
response = self.get('/analysis/list/')
self.assertEqual(response.code, 200)

def test_get_analysis_summary_ajax(self):
response = self.get('/analysis/dflt/sumary/')
self.assertEqual(response.code, 200)
self.assertEqual(loads(response.body),
{"artifacts": 1, "studies": 1, "samples": 4})

def test_get_selected_samples_handler(self):
response = self.get('/analysis/selected/')
# Make sure page response loaded sucessfully
self.assertEqual(response.code, 200)

if __name__ == '__main__':
main()
Loading