Index: appengine/monorail/tracker/issuelistcsv.py |
diff --git a/appengine/monorail/tracker/issuelistcsv.py b/appengine/monorail/tracker/issuelistcsv.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..f00c6eebcbe271c9b72634031e236b578e392f96 |
--- /dev/null |
+++ b/appengine/monorail/tracker/issuelistcsv.py |
@@ -0,0 +1,85 @@ |
+# Copyright 2016 The Chromium Authors. All rights reserved. |
+# Use of this source code is govered by a BSD-style |
+# license that can be found in the LICENSE file or at |
+# https://developers.google.com/open-source/licenses/bsd |
+ |
+"""Implemention of the issue list output as a CSV file.""" |
+ |
+import settings |
+from framework import framework_helpers |
+from framework import permissions |
+from framework import urls |
+from tracker import issuelist |
+from tracker import tablecell |
+from tracker import tracker_constants |
+ |
+ |
+class IssueListCsv(issuelist.IssueList): |
+ """IssueListCsv provides to the user a list of issues as a CSV document. |
+ |
+ Overrides the standard IssueList servlet but uses a different EZT template |
+ to provide the same content as the IssueList only as CSV. Adds the HTTP |
+ header to offer the result as a download. |
+ """ |
+ |
+ _PAGE_TEMPLATE = 'tracker/issue-list-csv.ezt' |
+ _DEFAULT_RESULTS_PER_PAGE = settings.max_artifact_search_results_per_page |
+ |
+ def GatherPageData(self, mr): |
+ if not mr.auth.user_id: |
+ raise permissions.PermissionException( |
+ 'Anonymous users are not allowed to download issue list CSV') |
+ |
+ # Sets headers to allow the response to be downloaded. |
+ self.content_type = 'text/csv; charset=UTF-8' |
+ download_filename = '%s-issues.csv' % mr.project_name |
+ self.response.headers.add( |
+ 'Content-Disposition', 'attachment; filename=%s' % download_filename) |
+ self.response.headers.add('X-Content-Type-Options', 'nosniff') |
+ |
+ # Rewrite the colspec to add some extra columns that make the CSV |
+ # file more complete. |
+ with self.profiler.Phase('finishing config work'): |
+ config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id) |
+ |
+ mr.ComputeColSpec(config) |
+ mr.col_spec = _RewriteColspec(mr.col_spec) |
+ page_data = issuelist.IssueList.GatherPageData(self, mr) |
+ |
+ # CSV files are at risk for PDF content sniffing by Acrobat Reader. |
+ page_data['prevent_sniffing'] = True |
+ |
+ # If we're truncating the results, add a URL to the next page of results |
+ page_data['next_csv_link'] = None |
+ pagination = page_data['pagination'] |
+ if pagination.next_url: |
+ page_data['next_csv_link'] = framework_helpers.FormatAbsoluteURL( |
+ mr, urls.ISSUE_LIST_CSV, start=pagination.last) |
+ page_data['item_count'] = pagination.last - pagination.start + 1 |
+ |
+ return page_data |
+ |
+ def GetCellFactories(self): |
+ return tablecell.CSV_CELL_FACTORIES |
+ |
+ |
+# Whenever the user request one of these columns, we replace it with the |
+# list of alternate columns. In effect, we split the requested column |
+# into two CSV columns. |
+_CSV_COLS_TO_REPLACE = { |
+ 'summary': ['Summary', 'AllLabels'], |
+ 'opened': ['Opened', 'OpenedTimestamp'], |
+ 'closed': ['Closed', 'ClosedTimestamp'], |
+ 'modified': ['Modified', 'ModifiedTimestamp'], |
+ } |
+ |
+ |
+def _RewriteColspec(col_spec): |
+ """Rewrite the given colspec to expand special CSV columns.""" |
+ new_cols = [] |
+ |
+ for col in col_spec.split(): |
+ rewriten_cols = _CSV_COLS_TO_REPLACE.get(col.lower(), [col]) |
+ new_cols.extend(rewriten_cols) |
+ |
+ return ' '.join(new_cols) |