Skip to content

Commit ce85a1b

Browse files
committed
Also have a proper undelete analysis that restores from backup. Plus show analysis result files in home table, small fix of delete_analysis
1 parent 7c2a3e4 commit ce85a1b

File tree

4 files changed

+101
-14
lines changed

4 files changed

+101
-14
lines changed

src/backend/analysis/tests.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1312,3 +1312,61 @@ def test_delete_fail_backup(self):
13121312
self.assertFalse(self.ana.deleted)
13131313
self.assertFalse(jm.Job.objects.exclude(pk=job.pk).filter(funcname='create_pdc_archive',
13141314
kwargs={'sfloc_id': self.anasfile_sfl.pk, 'isdir': False}).exists())
1315+
1316+
1317+
class TestUndeleteAnalysis(BaseTest):
1318+
url = '/analysis/undelete/'
1319+
1320+
def test_fail_request(self):
1321+
resp = self.cl.get(self.url)
1322+
self.assertEqual(resp.status_code, 405)
1323+
# wrong dict keys
1324+
resp = self.cl.post(self.url, content_type='application/json', data={'hello': 'test'})
1325+
self.assertEqual(resp.status_code, 400)
1326+
1327+
# No analysis
1328+
resp = self.cl.post(self.url, content_type='application/json',
1329+
data={'item_id': self.ana.pk + 1000})
1330+
self.assertEqual(resp.status_code, 400)
1331+
self.assertIn('Analysis does not exist', resp.json()['error'])
1332+
1333+
# analysis not deleted
1334+
resp = self.cl.post(self.url, content_type='application/json', data={'item_id': self.ana.pk})
1335+
self.assertEqual(resp.status_code, 403)
1336+
self.assertIn('Analysis is not deleted', resp.json()['error'])
1337+
1338+
# Not allowed wrong user
1339+
user = User.objects.create(username='wrong', email='wrong2')
1340+
self.user.is_staff = False
1341+
self.user.save()
1342+
self.ana.user = user
1343+
self.ana.deleted = True
1344+
self.ana.save()
1345+
resp = self.cl.post(self.url, content_type='application/json', data={'item_id': self.ana.pk})
1346+
self.assertEqual(resp.status_code, 403)
1347+
self.assertIn('User is not authorized to undelete', resp.json()['error'])
1348+
1349+
def test_undelete_with_backup_ok(self):
1350+
self.ana.deleted = True
1351+
self.ana.save()
1352+
rm.PDCBackedupFile.objects.create(storedfile=self.anasfile, success=True)
1353+
self.anasfile.deleted = True
1354+
self.anasfile.save()
1355+
self.anasfile_sfl.active = False
1356+
self.anasfile_sfl.save()
1357+
resp = self.cl.post(self.url, content_type='application/json', data={'item_id': self.ana.pk})
1358+
self.assertEqual(resp.status_code, 200)
1359+
self.ana.refresh_from_db()
1360+
self.assertFalse(self.ana.deleted)
1361+
self.assertTrue(jm.Job.objects.filter(funcname='restore_from_pdc_archive',
1362+
kwargs={'sfloc_id': self.anasfile_sfl.pk}).exists())
1363+
1364+
def test_delete_without_backup(self):
1365+
self.ana.deleted = True
1366+
self.ana.save()
1367+
resp = self.cl.post(self.url, content_type='application/json', data={'item_id': self.ana.pk})
1368+
self.assertEqual(resp.status_code, 409)
1369+
self.assertTrue(self.ana.deleted)
1370+
self.ana.refresh_from_db()
1371+
self.assertTrue(self.ana.deleted)
1372+
self.assertFalse(jm.Job.objects.filter(funcname='restore_from_pdc_archive').exists())

src/backend/analysis/views.py

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1305,26 +1305,51 @@ def undelete_analysis(request):
13051305
req = json.loads(request.body.decode('utf-8'))
13061306
try:
13071307
analysis = am.Analysis.objects.get(pk=req['item_id'])
1308-
except am.Analysis.DoesNotExist:
1309-
return JsonResponse({'error': 'Analysis does not exist'}, status=403)
1308+
except (am.Analysis.DoesNotExist, KeyError):
1309+
return JsonResponse({'error': 'Analysis does not exist or wrongly specified'}, status=400)
13101310
if not analysis.deleted:
13111311
return JsonResponse({'error': 'Analysis is not deleted, cant undelete it'}, status=403)
1312-
if analysis.user == request.user or request.user.is_staff:
1313-
analysis.deleted = False
1314-
analysis.save()
1315-
am.AnalysisDeleted.objects.filter(analysis=analysis).delete()
1316-
return JsonResponse({})
1317-
else:
1312+
if not analysis.success_completed:
1313+
return JsonResponse({'error': 'Analysis was never completed, cant undelete it'}, status=403)
1314+
if analysis.user != request.user and not request.user.is_staff:
13181315
return JsonResponse({'error': 'User is not authorized to undelete this analysis'}, status=403)
13191316

1317+
backedup_sfs = rm.StoredFile.objects.filter(analysisresultfile__analysis=analysis,
1318+
pdcbackedupfile__deleted=False)
1319+
if not backedup_sfs.count():
1320+
return JsonResponse({'error': 'There are no backed up result files, this analysis cannot be '
1321+
'restored'}, status=409)
1322+
nr_resfiles = analysis.analysisresultfile_set.count()
1323+
#if backedup_sfs.count() < nr_resfiles:
1324+
# TODO warn in case of fewer files than needed (need to impl warning in frontend, takes
1325+
# no messages now if not error
1326+
1327+
restore_sflpk = []
1328+
for sf in backedup_sfs.values('pk'):
1329+
if sfls := rm.StoredFileLoc.objects.filter(sfile_id=sf['pk'],
1330+
servershare__fileservershare__server__can_backup=True).values('pk'):
1331+
# Assume delivery area for user, otherwise first available
1332+
if not (sfl := sfls.filter(servershare__function=rm.ShareFunction.ANALYSIS_DELIVERY).first()):
1333+
sfl = sfls.first()
1334+
restore_sflpk.append(sfl['pk'])
1335+
if joberr := check_job_error('restore_from_pdc_archive', sfloc_id=sfl['pk']):
1336+
return JsonResponse({'error': 'Error trying to restore backed up result files, '
1337+
'cannot restore'}, status=409)
1338+
for r_sflpk in restore_sflpk:
1339+
create_job_without_check('restore_from_pdc_archive', sfloc_id=r_sflpk)
1340+
rm.StoredFileLoc.objects.filter(pk__in=restore_sflpk).update(active=True)
1341+
backedup_sfs.update(deleted=False)
1342+
analysis.deleted = False
1343+
analysis.purged = False
1344+
analysis.save()
1345+
am.AnalysisDeleted.objects.filter(analysis=analysis).delete()
1346+
return JsonResponse({})
1347+
13201348

13211349
@login_required
13221350
def delete_analysis(request):
1323-
# FIXME analysis can be properly deleted if its backed up
1324-
# right now this is only mark for deletion and it was for a previous
1325-
# setup where delete meant gone for ever
1326-
# There is a lot of old analyses that should be backed up or purged properly
1327-
# Maybe delete all those where no project is active anymore in delete_expired?
1351+
'''Sets deleted and queues delete jobs after checking if result files need
1352+
backing up'''
13281353
if request.method != 'POST':
13291354
return JsonResponse({'error': 'Must use POST'}, status=405)
13301355
req = json.loads(request.body.decode('utf-8'))
@@ -1356,7 +1381,7 @@ def do_analysis_deletion(analysis):
13561381
# Back up files if that for some reason (old analysis) hasnt happened earlier
13571382
backup_jobs = []
13581383
for sf in rm.StoredFile.objects.filter(analysisresultfile__analysis=analysis).exclude(
1359-
pdcbackedupfile__success=True).values('pk', 'filetype__is_folder'):
1384+
pdcbackedupfile__deleted=False).values('pk', 'filetype__is_folder'):
13601385
sfl_pks = [x['pk'] for x in rm.StoredFileLoc.objects.filter(sfile_id=sf['pk']).values('pk')]
13611386
do_backup = True
13621387
for exist_job in jm.Job.objects.filter(funcname='create_pdc_archive',

src/backend/home/views.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,8 @@ def populate_analysis(analyses, user):
461461
infns = afv.union(adsi).values('pk', 'rawfile_id')
462462
infn_ds = dsmodels.Dataset.objects.filter(datasetrawfile__rawfile__in=[x['rawfile_id']
463463
for x in infns]).distinct('pk').values('pk')
464+
outfns = filemodels.StoredFile.objects.filter(analysisresultfile__analysis_id=ana['pk']
465+
).values('pk')
464466
nfs = {'name': anmodels.Analysis.get_fullname(ananame),
465467
'jobstate': ana['nextflowsearch__job__state'],
466468
'jobid': ana['nextflowsearch__job_id'],
@@ -492,6 +494,7 @@ def populate_analysis(analyses, user):
492494
'purged': ana['purged'],
493495
'dset_ids': [x['pk'] for x in infn_ds],
494496
'fn_ids': [x['pk'] for x in infns],
497+
'outfiles': [x['pk'] for x in outfns],
495498
'mstulosq': tulos_filt,
496499
'actions': get_ana_actions(ana, user),
497500
**nfs,

src/frontend/home/src/Analysis.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const tablefields = [
1818
{id: 'name', name: 'Analysis name', type: 'str', multi: false},
1919
{id: 'files', name: '', help: 'Input files', type: 'icon', icon: 'database', multi: false, links: 'fn_ids', linkroute: '#/files'},
2020
{id: 'datasets', name: '', help: 'Datasets', type: 'icon', icon: 'clipboard-list', multi: false, links: 'dset_ids', linkroute: '#/datasets'},
21+
{id: 'outfiles', name: '', help: 'Result files', type: 'icon', icon: 'file-export', multi: false, links: 'outfiles', linkroute: '#/files'},
2122
{id: 'mstulos', name: '', help: 'ResultsDB', type: 'icon', icon: 'chart-bar', multi: false, links: 'mstulosq', linkroute: '/mstulos/', qparam: 'q'},
2223
{id: 'wf', name: 'Workflow', type: 'str', multi: false, links: 'wflink', linkroute: false},
2324
{id: 'usr', name: 'Users', type: 'str', multi: false},

0 commit comments

Comments
 (0)