Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
25 changes: 25 additions & 0 deletions qiita_db/artifact.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class Artifact(qdb.base.QiitaObject):
-------
create
delete
being_deleted_by

See Also
--------
Expand Down Expand Up @@ -1412,6 +1413,30 @@ def merging_scheme(self):

return ', '.join(merging_schemes), ', '.join(parent_softwares)

@property
def being_deleted_by(self):
"""The running job that is deleting this artifact

Returns
-------
qiita_db.processing_job.ProcessingJob
The running job that is deleting this artifact, None if it
doesn't exist
"""

with qdb.sql_connection.TRN:
sql = """
SELECT processing_job_id FROM qiita.artifact_processing_job
LEFT JOIN qiita.processing_job using (processing_job_id)
LEFT JOIN qiita.processing_job_status using (
processing_job_status_id)
LEFT JOIN qiita.software_command using (command_id)
WHERE artifact_id = %s AND name = 'delete_artifact' AND
processing_job_status = 'running'"""
qdb.sql_connection.TRN.add(sql, [self.id])
res = qdb.sql_connection.TRN.execute_fetchindex()
return qdb.processing_job.ProcessingJob(res[0][0]) if res else None

def jobs(self, cmd=None, status=None, show_hidden=False):
"""Jobs that used this artifact as input

Expand Down
3 changes: 3 additions & 0 deletions qiita_db/processing_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,9 @@ def create(cls, user, parameters, force=False):
TTRN.add(sql, [artifact_info, job_id])
else:
pending[artifact_info[0]][pname] = artifact_info[1]
elif pname == 'artifact':
Copy link
Contributor

Choose a reason for hiding this comment

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

Would you mind describing what's going on here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure, the create_command method in patch 58.py adds the delete_artifact and other Qiita internal commands; however, the artifact_id is in the parameter "artifact" vs. the parameter description (*); thus, if we want to add these commands to the database we need this special case.

(*) all regular jobs, except internal Qiita, have their parameters in this format: parameter_name: [parameter_type, value]; so normally, we just check that parameter_type is artifact but for internal jobs we need to check the actual parameter.

TTRN.add(sql, [parameters.values[pname], job_id])

if pending:
sql = """UPDATE qiita.processing_job
SET pending = %s
Expand Down
39 changes: 39 additions & 0 deletions qiita_db/test/test_artifact.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from biom.util import biom_open

from qiita_core.util import qiita_test_checker
from qiita_core.testing import wait_for_processing_job
import qiita_db as qdb


Expand Down Expand Up @@ -1114,6 +1115,44 @@ def test_delete_with_jobs(self):
# Check that the job still exists, so we cap keep track of system usage
qdb.processing_job.ProcessingJob(job.id)

def test_being_deleted_by(self):
test = qdb.artifact.Artifact.create(
self.filepaths_root, "FASTQ", prep_template=self.prep_template)
uploads_fp = join(qdb.util.get_mountpoint("uploads")[0][1],
str(test.study.id))
self._clean_up_files.extend(
[join(uploads_fp, basename(x['fp'])) for x in test.filepaths])

# verifying that there are no jobs in the list
self.assertIsNone(test.being_deleted_by)

# creating new deleting job
qiita_plugin = qdb.software.Software.from_name_and_version(
'Qiita', 'alpha')
cmd = qiita_plugin.get_command('delete_artifact')
params = qdb.software.Parameters.load(
cmd, values_dict={'artifact': test.id})
job = qdb.processing_job.ProcessingJob.create(
qdb.user.User('test@foo.bar'), params, True)
job._set_status('running')

# verifying that there is a job and is the same than above
self.assertEqual(job, test.being_deleted_by)

# let's set it as error and now we should not have it anymore
job._set_error('Killed by admin')
self.assertIsNone(test.being_deleted_by)

# now, let's actually remove
job = qdb.processing_job.ProcessingJob.create(
qdb.user.User('test@foo.bar'), params, True)
job.submit()
# let's wait for job
wait_for_processing_job(job.id)

with self.assertRaises(qdb.exceptions.QiitaDBUnknownIDError):
qdb.artifact.Artifact(test.id)

def test_delete_as_output_job(self):
fd, fp = mkstemp(suffix='_table.biom')
self._clean_up_files.append(fp)
Expand Down
7 changes: 7 additions & 0 deletions qiita_pet/handlers/api_proxy/prep_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from qiita_core.qiita_settings import r_client
from qiita_pet.handlers.api_proxy.util import check_access, check_fp
from qiita_pet.util import get_network_nodes_edges
from qiita_db.artifact import Artifact
from qiita_db.metadata_template.util import load_template_to_dataframe
from qiita_db.util import convert_to_id, get_files_from_uploads_folders
from qiita_db.study import Study
Expand Down Expand Up @@ -660,11 +661,17 @@ def prep_template_graph_get_req(prep_id, user_id):
G = artifact.descendants_with_jobs

nodes, edges, wf_id = get_network_nodes_edges(G, full_access)
# nodes returns [node_type, node_name, element_id]; here we are looking
# for the node_type == artifact, and check by the element/artifact_id if
# it's being deleted
artifacts_being_deleted = [a[2] for a in nodes if a[0] == 'artifact' and
Artifact(a[2]).being_deleted_by is not None]

return {'edges': edges,
'nodes': nodes,
'workflow': wf_id,
'status': 'success',
'artifacts_being_deleted': artifacts_being_deleted,
'message': ''}


Expand Down
34 changes: 21 additions & 13 deletions qiita_pet/handlers/artifact_handlers/base_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ def artifact_summary_get_request(user, artifact_id):
'job': job_info,
'artifact_timestamp': artifact.timestamp.strftime(
"%Y-%m-%d %H:%m"),
'being_deleted': artifact.being_deleted_by is not None,
'errored_summary_jobs': errored_summary_jobs}


Expand Down Expand Up @@ -392,23 +393,30 @@ def artifact_post_req(user, artifact_id):
artifact = Artifact(artifact_id)
check_artifact_access(user, artifact)

analysis = artifact.analysis
being_deleted_by = artifact.being_deleted_by

if analysis:
# Do something when deleting in the analysis part to keep track of it
redis_key = "analysis_%s" % analysis.id
else:
pt_id = artifact.prep_templates[0].id
redis_key = PREP_TEMPLATE_KEY_FORMAT % pt_id
if being_deleted_by is None:
analysis = artifact.analysis

qiita_plugin = Software.from_name_and_version('Qiita', 'alpha')
cmd = qiita_plugin.get_command('delete_artifact')
params = Parameters.load(cmd, values_dict={'artifact': artifact_id})
job = ProcessingJob.create(user, params, True)
if analysis:
# Do something when deleting in the analysis part to keep
# track of it
redis_key = "analysis_%s" % analysis.id
else:
pt_id = artifact.prep_templates[0].id
redis_key = PREP_TEMPLATE_KEY_FORMAT % pt_id

r_client.set(redis_key, dumps({'job_id': job.id, 'is_qiita_job': True}))
qiita_plugin = Software.from_name_and_version('Qiita', 'alpha')
cmd = qiita_plugin.get_command('delete_artifact')
params = Parameters.load(cmd, values_dict={'artifact': artifact_id})
job = ProcessingJob.create(user, params, True)

job.submit()
r_client.set(
redis_key, dumps({'job_id': job.id, 'is_qiita_job': True}))

job.submit()
else:
job = being_deleted_by

return {'job': job.id}

Expand Down
14 changes: 7 additions & 7 deletions qiita_pet/handlers/artifact_handlers/tests/test_base_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def test_artifact_summary_get_request(self):
'2125826711', '58B')]
exp = {'name': 'Raw data 1',
'artifact_id': 1,
'artifact_type': 'FASTQ',
'artifact_type': 'FASTQ', 'being_deleted': False,
'artifact_timestamp': '2012-10-01 09:10',
'visibility': 'private',
'editable': True,
Expand All @@ -124,7 +124,7 @@ def test_artifact_summary_get_request(self):
obs = artifact_summary_get_request(user, 1)
exp = {'name': 'Raw data 1',
'artifact_id': 1,
'artifact_type': 'FASTQ',
'artifact_type': 'FASTQ', 'being_deleted': False,
'artifact_timestamp': '2012-10-01 09:10',
'visibility': 'private',
'editable': True,
Expand Down Expand Up @@ -154,7 +154,7 @@ def test_artifact_summary_get_request(self):
obs = artifact_summary_get_request(user, 1)
exp = {'name': 'Raw data 1',
'artifact_id': 1,
'artifact_type': 'FASTQ',
'artifact_type': 'FASTQ', 'being_deleted': False,
'artifact_timestamp': '2012-10-01 09:10',
'visibility': 'private',
'editable': True,
Expand All @@ -177,7 +177,7 @@ def test_artifact_summary_get_request(self):
obs = artifact_summary_get_request(demo_u, 1)
exp = {'name': 'Raw data 1',
'artifact_id': 1,
'artifact_type': 'FASTQ',
'artifact_type': 'FASTQ', 'being_deleted': False,
'artifact_timestamp': '2012-10-01 09:10',
'visibility': 'public',
'editable': False,
Expand All @@ -195,7 +195,7 @@ def test_artifact_summary_get_request(self):
obs = artifact_summary_get_request(user, 1)
exp = {'name': 'Raw data 1',
'artifact_id': 1,
'artifact_type': 'FASTQ',
'artifact_type': 'FASTQ', 'being_deleted': False,
'artifact_timestamp': '2012-10-01 09:10',
'visibility': 'sandbox',
'editable': True,
Expand All @@ -219,7 +219,7 @@ def test_artifact_summary_get_request(self):
(5, '1_seqs.demux (preprocessed demux)', '', '0B')]
exp = {'name': 'Demultiplexed 1',
'artifact_id': 2,
'artifact_type': 'Demultiplexed',
'artifact_type': 'Demultiplexed', 'being_deleted': False,
'artifact_timestamp': '2012-10-01 10:10',
'visibility': 'private',
'editable': True,
Expand Down Expand Up @@ -261,7 +261,7 @@ def test_artifact_summary_get_request(self):
obs = artifact_summary_get_request(user, 8)
exp = {'name': 'noname',
'artifact_id': 8,
'artifact_type': 'BIOM',
'artifact_type': 'BIOM', 'being_deleted': False,
# this value changes on build so copy from obs
'artifact_timestamp': obs['artifact_timestamp'],
'visibility': 'sandbox',
Expand Down
6 changes: 5 additions & 1 deletion qiita_pet/static/js/networkVue.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ Vue.component('processing-graph', {
// Clean up the div
$("#processing-results").empty();
// Update the artifact node to mark that it is being deleted
var node = vm.nodes_ds.get(artifactId);
var node = vm.nodes_ds.get(artifactId.toString());
var node_info = vm.colorScheme['deleting'];
node.group = 'deleting';
node.color = node_info;
Expand Down Expand Up @@ -985,6 +985,10 @@ Vue.component('processing-graph', {
// Format node list data
for(var i = 0; i < data.nodes.length; i++) {
var node_info = vm.colorScheme[data.nodes[i][4]];
if (data.artifacts_being_deleted.includes(data.nodes[i][2])) {
data.nodes[i][0] = 'deleting'
node_info = vm.colorScheme['deleting']
}
// forcing a string
data.nodes[i][2] = data.nodes[i][2].toString()
vm.nodes.push({id: data.nodes[i][2], shape: node_info['shape'], label: formatNodeLabel(data.nodes[i][3]), type: data.nodes[i][1], group: data.nodes[i][0], color: node_info, status: data.nodes[i][4]});
Expand Down
31 changes: 18 additions & 13 deletions qiita_pet/templates/artifact_ajax/artifact_summary.html
Original file line number Diff line number Diff line change
Expand Up @@ -133,23 +133,28 @@ <h4>
{% end %}
{% end %}
<i id='summary-title'>{{name}}</i><i> (ID: {{artifact_id}}) Visibility: {{visibility}}</i>
{% if editable %}
<a class="btn btn-default btn-sm" data-toggle="modal" data-target="#update-artifact-name"><span class="glyphicon glyphicon-pencil"></span> Edit name</a>
{% end %}

{% if artifact_type == 'BIOM' and not is_from_analysis %}
<input type="button" class="btn btn-default btn-sm" value="Add to Analysis" onclick="send_samples_to_analysis(this, [{{artifact_id}}]);">
{% if being_deleted %}
<h4 style="color: #FF2222;">This artifact is being deleted</h4>
{% else %}
<a class="btn btn-default btn-sm" id="process-btn"><span class="glyphicon glyphicon-play"></span> Process</a>
{% end %}
{% if editable %}
<a class="btn btn-default btn-sm" data-toggle="modal" data-target="#update-artifact-name"><span class="glyphicon glyphicon-pencil"></span> Edit name</a>
{% end %}

{% if editable %}
<a class="btn btn-danger btn-sm" onclick="if (confirm('Are you sure you want to delete artifact {{artifact_id}}?')){ processingNetwork.$refs.procGraph.deleteArtifact({{artifact_id}}) };"><span class="glyphicon glyphicon-trash"></span> Delete</a>
{% raw buttons %}
{% end %}
{% if artifact_type == 'BIOM' and not is_from_analysis %}
<input type="button" class="btn btn-default btn-sm" value="Add to Analysis" onclick="send_samples_to_analysis(this, [{{artifact_id}}]);">
{% else %}
<a class="btn btn-default btn-sm" id="process-btn"><span class="glyphicon glyphicon-play"></span> Process</a>
{% end %}

{% if processing_info %}
<button class="btn btn-default btn-sm" data-toggle="collapse" data-target="#processing-info"><span class="glyphicon glyphicon-eye-open"></span> Show processing information</a>
{% if editable %}
<a class="btn btn-danger btn-sm" onclick="if (confirm('Are you sure you want to delete artifact {{artifact_id}}?')){ processingNetwork.$refs.procGraph.deleteArtifact({{artifact_id}}) };"><span class="glyphicon glyphicon-trash"></span> Delete</a>
{% raw buttons %}
{% end %}

{% if processing_info %}
<button class="btn btn-default btn-sm" data-toggle="collapse" data-target="#processing-info"><span class="glyphicon glyphicon-eye-open"></span> Show processing information</a>
{% end %}
{% end %}
</h4>
{% if processing_info %}
Expand Down
3 changes: 3 additions & 0 deletions qiita_pet/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ def get_network_nodes_edges(graph, full_access, nodes=None, edges=None):
# n[1] is the object
for n in graph.nodes():
if n[0] == 'job':
# ignoring internal Jobs
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this need a new test case?

Copy link
Member Author

Choose a reason for hiding this comment

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

This if is needed as a consequence of now adding the internal jobs to the database; my guess is that originally we didn't have it cause we didn't store them. In other words, the test for these method would need to change if we didn't have this if (as now the jobs will be returned here) AKA it's being tested here.

if n[1].command.software.name == 'Qiita':
continue
atype = 'job'
name = n[1].command.name
status = n[1].status
Expand Down