Skip to content

Commit

Permalink
Merge pull request #1716 from josenavas/artifact-study-pages-import-a…
Browse files Browse the repository at this point in the history
…rtifact

Import artifact
  • Loading branch information
antgonza committed Mar 29, 2016
2 parents df9ddec + 00d110a commit 588f63c
Show file tree
Hide file tree
Showing 15 changed files with 291 additions and 63 deletions.
34 changes: 21 additions & 13 deletions qiita_db/study.py
Original file line number Diff line number Diff line change
Expand Up @@ -952,33 +952,41 @@ def ebi_submission_status(self, value):
ebi_submission_status.__doc__.format(', '.join(_VALID_EBI_STATUS))

# --- methods ---
def artifacts(self, dtype=None):
def artifacts(self, dtype=None, artifact_type=None):
"""Returns the list of artifacts associated with the study
Parameters
----------
dtype : str, optional
If given, retrieve only artifacts for given data type. Default,
return all artifacts associated with the study.
artifact_type : str, optional
If given, retrieve only artifacts of given data type. Default,
return all artifacts associated with the study
Returns
-------
list of qiita_db.artifact.Artifact
"""
with qdb.sql_connection.TRN:
sql_args = [self._id]
sql_where = ""
if dtype:
sql = """SELECT artifact_id
FROM qiita.artifact
JOIN qiita.data_type USING (data_type_id)
JOIN qiita.study_artifact USING (artifact_id)
WHERE study_id = %s AND data_type = %s"""
sql_args = [self._id, dtype]
else:
sql = """SELECT artifact_id
FROM qiita.artifact
JOIN qiita.study_artifact USING (artifact_id)
WHERE study_id = %s"""
sql_args = [self._id]
sql_args.append(dtype)
sql_where = " AND data_type = %s"

if artifact_type:
sql_args.append(artifact_type)
sql_where += "AND artifact_type = %s"

sql = """SELECT artifact_id
FROM qiita.artifact
JOIN qiita.data_type USING (data_type_id)
JOIN qiita.study_artifact USING (artifact_id)
JOIN qiita.artifact_type USING (artifact_type_id)
WHERE study_id = %s{0}
ORDER BY artifact_id""".format(sql_where)

qdb.sql_connection.TRN.add(sql, sql_args)
return [qdb.artifact.Artifact(aid)
for aid in qdb.sql_connection.TRN.execute_fetchflatten()]
Expand Down
10 changes: 10 additions & 0 deletions qiita_db/test/test_study.py
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,16 @@ def test_retrieve_artifacts(self):
self.assertEqual(self.study.artifacts(dtype="16S"), [exp[-1]])
self.assertEqual(self.study.artifacts(dtype="18S"), exp[:-1])

self.assertEqual(self.study.artifacts(artifact_type="BIOM"),
[qdb.artifact.Artifact(4),
qdb.artifact.Artifact(5),
qdb.artifact.Artifact(6)])

self.assertEqual(self.study.artifacts(dtype="18S",
artifact_type="BIOM"),
[qdb.artifact.Artifact(4),
qdb.artifact.Artifact(5)])

def test_retrieve_artifacts_none(self):
new = qdb.study.Study.create(
qdb.user.User('test@foo.bar'),
Expand Down
16 changes: 16 additions & 0 deletions qiita_db/test/test_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,5 +418,21 @@ def test_delete_messages(self):
obs = self.conn_handler.execute_fetchall(sql)
self.assertItemsEqual(obs, [[1], [3]])

def test_user_artifacts(self):
user = qdb.user.User('test@foo.bar')
obs = user.user_artifacts()
exp = {qdb.study.Study(1): [qdb.artifact.Artifact(1),
qdb.artifact.Artifact(2),
qdb.artifact.Artifact(3),
qdb.artifact.Artifact(4),
qdb.artifact.Artifact(5),
qdb.artifact.Artifact(6)]}
self.assertEqual(obs, exp)
obs = user.user_artifacts(artifact_type='BIOM')
exp = {qdb.study.Study(1): [qdb.artifact.Artifact(4),
qdb.artifact.Artifact(5),
qdb.artifact.Artifact(6)]}
self.assertEqual(obs, exp)

if __name__ == "__main__":
main()
42 changes: 42 additions & 0 deletions qiita_db/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
from re import sub
from datetime import datetime

from future.utils import viewitems

from qiita_core.exceptions import (IncorrectEmailError, IncorrectPasswordError,
IncompetentQiitaDeveloperError)
from qiita_core.qiita_settings import qiita_config
Expand Down Expand Up @@ -475,6 +477,46 @@ def unread_messages(self):
return qdb.sql_connection.TRN.execute_fetchindex()

# ------- methods ---------
def user_artifacts(self, artifact_type=None):
"""Returns the artifacts owned by the user, grouped by study
Parameters
----------
artifact_type : str, optional
The artifact type to retrieve. Default: retrieve all artfact types
Returns
-------
dict of {qiita_db.study.Study: list of qiita_db.artifact.Artifact}
The artifacts owned by the user
"""
with qdb.sql_connection.TRN:
sql_args = [self.id, qiita_config.portal]
sql_a_type = ""
if artifact_type:
sql_a_type = " AND artifact_type = %s"
sql_args.append(artifact_type)

sql = """SELECT study_id, array_agg(
artifact_id ORDER BY artifact_id)
FROM qiita.study
JOIN qiita.study_portal USING (study_id)
JOIN qiita.portal_type USING (portal_type_id)
JOIN qiita.study_artifact USING (study_id)
JOIN qiita.artifact USING (artifact_id)
JOIN qiita.artifact_type USING (artifact_type_id)
WHERE email = %s AND portal = %s{0}
GROUP BY study_id
ORDER BY study_id""".format(sql_a_type)
qdb.sql_connection.TRN.add(sql, sql_args)
db_res = dict(qdb.sql_connection.TRN.execute_fetchindex())
res = {}
for s_id, artifact_ids in viewitems(db_res):
res[qdb.study.Study(s_id)] = [
qdb.artifact.Artifact(a_id) for a_id in artifact_ids]

return res

def change_password(self, oldpass, newpass):
"""Changes the password from oldpass to newpass
Expand Down
86 changes: 51 additions & 35 deletions qiita_pet/handlers/api_proxy/artifact.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ def artifact_get_req(user_id, artifact_id):

@execute_as_transaction
def artifact_post_req(user_id, filepaths, artifact_type, name,
prep_template_id):
prep_template_id, artifact_id=None):
"""Creates the initial artifact for the prep template
Parameters
Expand All @@ -250,6 +250,8 @@ def artifact_post_req(user_id, filepaths, artifact_type, name,
Name to give the artifact
prep_template_id : int or str castable to int
Prep template to attach the artifact to
artifact_id : int or str castable to int, optional
The id of the imported artifact
Returns
-------
Expand All @@ -267,41 +269,55 @@ def artifact_post_req(user_id, filepaths, artifact_type, name,
if access_error:
return access_error

uploads_path = get_mountpoint('uploads')[0][1]
path_builder = partial(join, uploads_path, str(study_id))
cleaned_filepaths = []
for ftype, file_list in viewitems(filepaths):
# JavaScript sends us this list as a comma-separated list
for fp in file_list.split(','):
# JavaScript will send this value as an empty string if the
# list of files was empty. In such case, the split will generate
# a single element containing the empty string. Check for that case
# here and, if fp is not the empty string, proceed to check if
# the file exists
if fp:
# Check if filepath being passed exists for study
full_fp = path_builder(fp)
exists = check_fp(study_id, full_fp)
if exists['status'] != 'success':
return {'status': 'error',
'message': 'File does not exist: %s' % fp}
cleaned_filepaths.append((full_fp, ftype))

# This should never happen, but it doesn't hurt to actually have
# a explicit check, in case there is something odd with the JS
if not cleaned_filepaths:
return {'status': 'error',
'message': "Can't create artifact, no files provided."}
if artifact_id:
# if the artifact id has been provided, import the artifact
try:
artifact = Artifact.copy(Artifact(artifact_id), prep)
except Exception as e:
# We should hit this exception rarely (that's why it is an
# exception) since at this point we have done multiple checks.
# However, it can occur in weird cases, so better let the GUI know
# that this failed
return {'status': 'error',
'message': "Error creating artifact: %s" % str(e)}

try:
artifact = Artifact.create(cleaned_filepaths, artifact_type, name=name,
prep_template=prep)
except Exception as e:
# We should hit this exception rarely (that's why it is an exception)
# since at this point we have done multiple checks. However, it can
# occur in weird cases, so better let the GUI know that this failed
return {'status': 'error',
'message': "Error creating artifact: %s" % str(e)}
else:
uploads_path = get_mountpoint('uploads')[0][1]
path_builder = partial(join, uploads_path, str(study_id))
cleaned_filepaths = []
for ftype, file_list in viewitems(filepaths):
# JavaScript sends us this list as a comma-separated list
for fp in file_list.split(','):
# JavaScript will send this value as an empty string if the
# list of files was empty. In such case, the split will
# generate a single element containing the empty string. Check
# for that case here and, if fp is not the empty string,
# proceed to check if the file exists
if fp:
# Check if filepath being passed exists for study
full_fp = path_builder(fp)
exists = check_fp(study_id, full_fp)
if exists['status'] != 'success':
return {'status': 'error',
'message': 'File does not exist: %s' % fp}
cleaned_filepaths.append((full_fp, ftype))

# This should never happen, but it doesn't hurt to actually have
# a explicit check, in case there is something odd with the JS
if not cleaned_filepaths:
return {'status': 'error',
'message': "Can't create artifact, no files provided."}

try:
artifact = Artifact.create(cleaned_filepaths, artifact_type,
name=name, prep_template=prep)
except Exception as e:
# We should hit this exception rarely (that's why it is an
# exception) since at this point we have done multiple checks.
# However, it can occur in weird cases, so better let the GUI know
# that this failed
return {'status': 'error',
'message': "Error creating artifact: %s" % str(e)}

return {'status': 'success',
'message': '',
Expand Down
23 changes: 21 additions & 2 deletions qiita_pet/handlers/api_proxy/studies.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from __future__ import division
from collections import defaultdict

from future.utils import viewitems

from qiita_db.user import User
from qiita_db.study import Study
from qiita_db.metadata_template.prep_template import PrepTemplate
Expand Down Expand Up @@ -180,7 +182,7 @@ def study_prep_get_req(study_id, user_id):
'info': prep_info}


def study_files_get_req(study_id, prep_template_id, artifact_type):
def study_files_get_req(user_id, study_id, prep_template_id, artifact_type):
"""Returns the uploaded files for the study id categorized by artifact_type
It retrieves the files uploaded for the given study and tries to do a
Expand All @@ -189,6 +191,8 @@ def study_files_get_req(study_id, prep_template_id, artifact_type):
Parameters
----------
user_id : str
The id of the user making the request
study_id : int
The study id
prep_template_id : int
Expand Down Expand Up @@ -242,8 +246,23 @@ def study_files_get_req(study_id, prep_template_id, artifact_type):
# because selected is initialized to the empty list
file_types.insert(0, (first[0], first[1], selected))

# Create a list of artifacts that the user has access to, in case that
# he wants to import the files from another artifact
user = User(user_id)
artifact_options = []
user_artifacts = user.user_artifacts(artifact_type=artifact_type)
study = Study(study_id)
if study not in user_artifacts:
user_artifacts[study] = study.artifacts(artifact_type=artifact_type)
for study, artifacts in viewitems(user_artifacts):
study_label = "%s (%d)" % (study.title, study.id)
for a in artifacts:
artifact_options.append(
(a.id, "%s - %s (%d)" % (study_label, a.name, a.id)))

return {'status': 'success',
'message': '',
'remaining': remaining,
'file_types': file_types,
'num_prefixes': num_prefixes}
'num_prefixes': num_prefixes,
'artifacts': artifact_options}
27 changes: 27 additions & 0 deletions qiita_pet/handlers/api_proxy/tests/test_artifact.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,26 @@ def test_artifact_post_req(self):
a = Artifact(new_artifact_id)
self._files_to_remove.extend([fp for _, fp, _ in a.filepaths])

# Test importing an artifact
# Create new prep template to attach artifact to
pt = npt.assert_warns(
QiitaDBWarning, PrepTemplate.create,
pd.DataFrame({'new_col': {'1.SKD6.640190': 1}}), Study(1), '16S')
self._files_to_remove.extend([fp for _, fp in pt.get_filepaths()])

new_artifact_id_2 = get_count('qiita.artifact') + 1
obs = artifact_post_req(
'test@foo.bar', {}, 'FASTQ', 'New Test Artifact 2', pt.id,
new_artifact_id)
exp = {'status': 'success',
'message': '',
'artifact': new_artifact_id_2}
self.assertEqual(obs, exp)
# Instantiate the artifact to make sure it was made and
# to clean the environment
a = Artifact(new_artifact_id)
self._files_to_remove.extend([fp for _, fp, _ in a.filepaths])

def test_artifact_post_req_error(self):
# Create a new prep template to attach the artifact to
pt = npt.assert_warns(
Expand Down Expand Up @@ -368,6 +388,13 @@ def test_artifact_post_req_error(self):
"has an artifact associated"}
self.assertEqual(obs, exp)

# Exception
obs = artifact_post_req(user_id, {}, artifact_type, name, 1, 1)
exp = {'status': 'error',
'message': "Error creating artifact: Prep template 1 already "
"has an artifact associated"}
self.assertEqual(obs, exp)

def test_artifact_status_put_req(self):
obs = artifact_status_put_req(1, 'test@foo.bar', 'sandbox')
exp = {'status': 'success',
Expand Down

0 comments on commit 588f63c

Please sign in to comment.