diff --git a/api/src/Page/Download.php b/api/src/Page/Download.php index df6ce88eb..d7531a4c4 100644 --- a/api/src/Page/Download.php +++ b/api/src/Page/Download.php @@ -87,7 +87,7 @@ function _download_visit() if ($filesystem->exists($data)) { $response = new BinaryFileResponse($data); $response->headers->set("Content-Type", "application/octet-stream"); - $this->_set_disposition_attachment($response, $this->arg('visit') . '_download.zip'); + Utils::setDispositionAttachment($response, $this->arg('visit') . '_download.zip'); $response->send(); } else $this->_error('There doesnt seem to be a data archive available for this visit'); @@ -309,7 +309,7 @@ function _csv_report() WHERE dcg.sessionid=:1 ORDER BY dc.starttime", array($vis['SESSIONID'])); $this->app->response->headers->set("Content-type", "application/vnd.ms-excel"); - $this->_set_disposition_attachment($this->app->response, $vis['ST'] . "_" . $vis['BEAMLINENAME'] . "_" . $this->arg('visit') . ".csv"); + Utils::setDispositionAttachment($this->app->response, $vis['ST'] . "_" . $vis['BEAMLINENAME'] . "_" . $this->arg('visit') . ".csv"); print "Image prefix,Beamline,Run no,Start Time,Sample Name,Protein Acronym,# images, Wavelength (angstrom), Distance (mm), Exp. Time (sec), Phi start (deg), Phi range (deg), Xbeam (mm), Ybeam (mm), Detector resol. (angstrom), Comments\n"; foreach ($rows as $r) { $r['COMMENTS'] = '"' . $r['COMMENTS'] . '"'; @@ -561,25 +561,12 @@ function set_mime_content($response, $filename, $prefix = null) $response->headers->set("Content-Type", "application/octet-stream"); } if ($this->has_arg('download') && $this->arg('download') < 3) { - $this->_set_disposition_inline($response); + Utils::setDispositionInline($response); } else { - $this->_set_disposition_attachment($response, $saved_filename); + Utils::setDispositionAttachment($response, $saved_filename); } } - function _set_disposition_attachment($response, $filename) { - $response->headers->set("Content-Disposition", - (new ResponseHeaderBag())->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $filename) - ); - } - - function _set_disposition_inline($response) { - $response->headers->set("Content-Disposition", - (new ResponseHeaderBag())->makeDisposition(ResponseHeaderBag::DISPOSITION_INLINE, '') - ); - } - - # ------------------------------------------------------------------------ # Download Data function _download() @@ -609,7 +596,7 @@ function _download() if ($filesystem->exists($data)) { $response = new BinaryFileResponse($data); $response->headers->set("Content-Type", "application/octet-stream"); - $this->_set_disposition_attachment($response, $this->arg('id') . '_download.zip'); + Utils::setDispositionAttachment($response, $this->arg('id') . '_download.zip'); $response->send(); } else { error_log("Download file " . $data . " not found"); diff --git a/api/src/Page/Processing.php b/api/src/Page/Processing.php index 4abff179b..67e0ec777 100644 --- a/api/src/Page/Processing.php +++ b/api/src/Page/Processing.php @@ -4,11 +4,12 @@ use SynchWeb\Page; use SynchWeb\Downstream\DownstreamProcessing; +use SynchWeb\Utils; class Processing extends Page { public static $dispatch = array( array('/:id', 'get', '_results'), - array('/visit/:visit', 'get', '_results_for_visit'), + array('/visit/:visit(/csv/:csv)', 'get', '_results_for_visit'), array('/status', 'post', '_statuses'), array('/messages/status', 'post', '_ap_message_status'), @@ -43,6 +44,7 @@ class Processing extends Page { 'cloudrunid' => '(\w|\-)+', 'AUTOPROCPROGRAMATTACHMENTID' => '\d+', 'DATACOLLECTIONFILEATTACHMENTID' => '\d+', + 'csv' => '\d+', ); /** @@ -476,12 +478,11 @@ function _results_for_visit() { $jobs = $this->db->pq( "SELECT dc.datacollectionid as id, CONCAT(dc.imageprefix, '_', dc.datacollectionnumber) as prefix, - ".self::EVTOA."/dc.wavelength as energy, - dc.resolution, smp.name as sample, smp.blsampleid, + ".self::EVTOA."/dc.wavelength as energy, + dc.resolution, app.processingprograms as pipeline, - app.autoprocprogramid as aid, REPLACE(ap.spacegroup,' ','') as sg, ap.refinedcell_a as cell_a, ap.refinedcell_b as cell_b, @@ -489,12 +490,12 @@ function _results_for_visit() { ap.refinedcell_alpha as cell_al, ap.refinedcell_beta as cell_be, ap.refinedcell_gamma as cell_ga, - apssover.resolutionlimithigh as overallrhigh, apssover.resolutionlimitlow as overallrlow, - apssinner.resolutionlimithigh as innerrhigh, + apssover.resolutionlimithigh as overallrhigh, apssinner.resolutionlimitlow as innerrlow, - apssouter.resolutionlimithigh as outerrhigh, + apssinner.resolutionlimithigh as innerrhigh, apssouter.resolutionlimitlow as outerrlow, + apssouter.resolutionlimithigh as outerrhigh, apssover.completeness as overallcompleteness, apssinner.completeness as innercompleteness, apssouter.completeness as outercompleteness, @@ -503,7 +504,8 @@ function _results_for_visit() { apssouter.anomalouscompleteness as anomoutercompleteness, apssinner.rmeasalliplusiminus as innerrmeas, apssouter.cchalf as outercchalf, - apssinner.ccanomalous as innerccanom + apssinner.ccanomalous as innerccanom, + app.autoprocprogramid as aid FROM datacollection dc LEFT OUTER JOIN blsample smp ON dc.blsampleid = smp.blsampleid INNER JOIN processingjob pj ON dc.datacollectionid = pj.datacollectionid @@ -534,12 +536,14 @@ function _results_for_visit() { $data = array_slice($data, $start, $pp); // Add classes to highlight fields in red/yellow/green - foreach ($data as &$d) { - foreach (array('OVERALL', 'INNER', 'OUTER') as $s) { - $c = $d[$s.'COMPLETENESS']; - $d[$s.'COMPLETENESSCLASS'] = $c > 95 ? 'active' : ($c > 80 ? 'minor' : 'inactive'); - $c = $d['ANOM'.$s.'COMPLETENESS']; - $d['ANOM'.$s.'COMPLETENESSCLASS'] = $c > 95 ? 'active' : ($c > 80 ? 'minor' : 'inactive'); + if (!$this->has_arg('csv')) { + foreach ($data as &$d) { + foreach (array('OVERALL', 'INNER', 'OUTER') as $s) { + $c = $d[$s.'COMPLETENESS']; + $d[$s.'COMPLETENESSCLASS'] = $c > 95 ? 'active' : ($c > 80 ? 'minor' : 'inactive'); + $c = $d['ANOM'.$s.'COMPLETENESS']; + $d['ANOM'.$s.'COMPLETENESSCLASS'] = $c > 95 ? 'active' : ($c > 80 ? 'minor' : 'inactive'); + } } } @@ -561,13 +565,23 @@ function _results_for_visit() { foreach ($nf as $nff => $cols) { foreach ($cols as $c) { foreach ($data as &$d) { - $d[$c] = number_format($d[$c], $nff); + $d[$c] = number_format($d[$c], $nff, ".", ""); } } } - $this->_output(array('total' => $tot, 'data' => $data)); - + if ($this->has_arg('csv')) { + $this->app->response->headers->set("Content-type", "text/csv"); + Utils::setDispositionAttachment($this->app->response, $this->arg('visit') . "_summary.csv"); + if (!empty($data)) { + print implode(',', array_keys($data[0])) . "\n"; + } + foreach ($data as $r) { + print implode(',', array_values($r)) . "\n"; + } + } else { + $this->_output(array('total' => $tot, 'data' => $data)); + } } /** diff --git a/api/src/Utils.php b/api/src/Utils.php index 2fe30b5a8..1bafae4f7 100644 --- a/api/src/Utils.php +++ b/api/src/Utils.php @@ -2,6 +2,7 @@ namespace SynchWeb; +use Symfony\Component\HttpFoundation\ResponseHeaderBag; use InvalidArgumentException; class Utils @@ -52,4 +53,17 @@ public static function filterParamFromUrl($url, $param): string $redirect_url = preg_replace('/(&|\?)'.preg_quote($param).'=[^&]*$/', '', $url); return preg_replace('/(&|\?)'.preg_quote($param).'=[^&]*&/', '$1', $redirect_url); } + + public static function setDispositionAttachment($response, $filename) + { + $response->headers->set('Content-Disposition', + (new ResponseHeaderBag())->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $filename) + ); + } + + public static function setDispositionInline($response) { + $response->headers->set("Content-Disposition", + (new ResponseHeaderBag())->makeDisposition(ResponseHeaderBag::DISPOSITION_INLINE, '') + ); + } } diff --git a/client/src/js/modules/dc/views/summary.js b/client/src/js/modules/dc/views/summary.js index 193ecec5f..00867d6d4 100644 --- a/client/src/js/modules/dc/views/summary.js +++ b/client/src/js/modules/dc/views/summary.js @@ -23,12 +23,19 @@ define(['backbone', className: 'content', template: template, + templateHelpers: function() { + return { + APIURL: app.apiurl, + } + }, + regions: { wrap: '.wrapper', }, events: { 'click a.dll': utils.signHandler, + 'click a.csv': 'downloadCSV', 'change @ui.pipeline': 'changePipeline', 'change @ui.sg': 'changeSpaceGroup', 'change @ui.minres': 'changeResolution', @@ -56,6 +63,37 @@ define(['backbone', this.visit = options.model.get('VISIT') }, + downloadCSV: function(e) { + e.preventDefault() + + let a = e.target + while (a && a.tagName !== 'A') a = a.parentNode + if (!a) return + + const originalHref = a.href + const url = new URL(originalHref) + + const skip = ['currentPage', 'pageSize', 'totalPages', 'totalRecords', 'sortKey', 'order'] + + for (const [key, val] of Object.entries(this.collection.queryParams)) { + if (skip.includes(key)) continue + if (val == null || typeof val === 'function' || typeof val === 'object') continue + url.searchParams.set(key, val) + } + + if (this.collection.state.sortKey) { + url.searchParams.set('sort_by', this.collection.state.sortKey) + url.searchParams.set('order', this.collection.state.order === 1 ? 'desc' : 'asc') + } + url.searchParams.set('per_page', 9999) + + a.href = url.pathname + url.search + + utils.signHandler(e) + + a.href = originalHref + }, + updateData: function() { this.collection.state['currentPage'] = 1 this.collection.fetch() diff --git a/client/src/js/templates/dc/summary.html b/client/src/js/templates/dc/summary.html index 165e5e3ea..18626ac34 100644 --- a/client/src/js/templates/dc/summary.html +++ b/client/src/js/templates/dc/summary.html @@ -3,6 +3,7 @@