blob: 57ab3bb27155beb7990b9309c460e55e5cb36072 [file] [log] [blame]
# Copyright 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
"""Classes that generate value cells in the issue list table."""
from __future__ import division
from __future__ import print_function
from __future__ import absolute_import
import logging
import time
from third_party import ezt
from framework import framework_constants
from framework import table_view_helpers
from framework import urls
from tracker import tracker_bizobj
from tracker import tracker_helpers
# pylint: disable=unused-argument
class TableCellNote(table_view_helpers.TableCell):
"""TableCell subclass specifically for showing a hotlist issue's note."""
def __init__(self, issue, note=None, **_kw):
if note:
display_note = [note]
else:
display_note = []
table_view_helpers.TableCell.__init__(
self, table_view_helpers.CELL_TYPE_NOTE, display_note)
class TableCellDateAdded(table_view_helpers.TableCell):
"""TableCell subclass specifically for showing the date added of an issue."""
def __init__(self, issue, date_added=None, **_kw):
table_view_helpers.TableCell.__init__(
self, table_view_helpers.CELL_TYPE_ATTR, [date_added])
class TableCellAdderID(table_view_helpers.TableCell):
"""TableCell subclass specifically for showing an issue's adder_id."""
def __init__(self, issue, adder_id=None, users_by_id=None, **_kw):
if adder_id:
display_name = [users_by_id[adder_id].display_name]
else:
display_name = [None]
table_view_helpers.TableCell.__init__(
self, table_view_helpers.CELL_TYPE_ATTR,
display_name)
class TableCellRank(table_view_helpers.TableCell):
"""TableCell subclass specifically for showing issue rank."""
def __init__(self, issue, issue_rank=None, **_kw):
table_view_helpers.TableCell.__init__(
self, table_view_helpers.CELL_TYPE_ATTR, [issue_rank])
class TableCellID(table_view_helpers.TableCell):
"""TableCell subclass specifically for showing issue IDs."""
def __init__(self, issue, **_kw):
table_view_helpers.TableCell.__init__(
self, table_view_helpers.CELL_TYPE_ID, [str(issue.local_id)])
class TableCellStatus(table_view_helpers.TableCell):
"""TableCell subclass specifically for showing issue status values."""
def __init__(self, issue, **_kws):
values = []
derived_values = []
if issue.status:
values = [issue.status]
if issue.derived_status:
derived_values = [issue.derived_status]
table_view_helpers.TableCell.__init__(
self, table_view_helpers.CELL_TYPE_ATTR, values,
derived_values=derived_values)
class TableCellOwner(table_view_helpers.TableCell):
"""TableCell subclass specifically for showing issue owner name."""
def __init__(self, issue, users_by_id=None, **_kw):
values = []
derived_values = []
if issue.owner_id:
values = [users_by_id[issue.owner_id].display_name]
if issue.derived_owner_id:
derived_values = [users_by_id[issue.derived_owner_id].display_name]
table_view_helpers.TableCell.__init__(
self, table_view_helpers.CELL_TYPE_ATTR, values,
derived_values=derived_values)
class TableCellReporter(table_view_helpers.TableCell):
"""TableCell subclass specifically for showing issue reporter name."""
def __init__(self, issue, users_by_id=None, **_kw):
try:
values = [users_by_id[issue.reporter_id].display_name]
except KeyError:
logging.info('issue reporter %r not found', issue.reporter_id)
values = ['deleted?']
table_view_helpers.TableCell.__init__(
self, table_view_helpers.CELL_TYPE_ATTR, values)
class TableCellCc(table_view_helpers.TableCell):
"""TableCell subclass specifically for showing issue Cc user names."""
def __init__(self, issue, users_by_id=None, **_kw):
values = [users_by_id[cc_id].display_name
for cc_id in issue.cc_ids]
derived_values = [users_by_id[cc_id].display_name
for cc_id in issue.derived_cc_ids]
table_view_helpers.TableCell.__init__(
self, table_view_helpers.CELL_TYPE_ATTR, values,
derived_values=derived_values)
class TableCellAttachments(table_view_helpers.TableCell):
"""TableCell subclass specifically for showing issue attachment count."""
def __init__(self, issue, **_kw):
table_view_helpers.TableCell.__init__(
self, table_view_helpers.CELL_TYPE_ATTR, [issue.attachment_count],
align='right')
class TableCellOpened(table_view_helpers.TableCellDate):
"""TableCell subclass specifically for showing issue opened date."""
def __init__(self, issue, **_kw):
table_view_helpers.TableCellDate.__init__(self, issue.opened_timestamp)
class TableCellClosed(table_view_helpers.TableCellDate):
"""TableCell subclass specifically for showing issue closed date."""
def __init__(self, issue, **_kw):
table_view_helpers.TableCellDate.__init__(self, issue.closed_timestamp)
class TableCellModified(table_view_helpers.TableCellDate):
"""TableCell subclass specifically for showing issue modified date."""
def __init__(self, issue, **_kw):
table_view_helpers.TableCellDate.__init__(self, issue.modified_timestamp)
class TableCellOwnerModified(table_view_helpers.TableCellDate):
"""TableCell subclass specifically for showing owner modified age."""
def __init__(self, issue, **_kw):
table_view_helpers.TableCellDate.__init__(
self, issue.owner_modified_timestamp, days_only=True)
class TableCellStatusModified(table_view_helpers.TableCellDate):
"""TableCell subclass specifically for showing status modified age."""
def __init__(self, issue, **_kw):
table_view_helpers.TableCellDate.__init__(
self, issue.status_modified_timestamp, days_only=True)
class TableCellComponentModified(table_view_helpers.TableCellDate):
"""TableCell subclass specifically for showing component modified age."""
def __init__(self, issue, **_kw):
table_view_helpers.TableCellDate.__init__(
self, issue.component_modified_timestamp, days_only=True)
class TableCellOwnerLastVisit(table_view_helpers.TableCellDate):
"""TableCell subclass specifically for showing owner last visit days ago."""
def __init__(self, issue, users_by_id=None, **_kw):
owner_view = users_by_id.get(issue.owner_id or issue.derived_owner_id)
last_visit = None
if owner_view:
last_visit = owner_view.user.last_visit_timestamp
table_view_helpers.TableCellDate.__init__(
self, last_visit, days_only=True)
def _make_issue_view(default_pn, config, viewable_iids_set, ref_issue):
viewable = ref_issue.issue_id in viewable_iids_set
return {
"id": tracker_bizobj.FormatIssueRef(
(ref_issue.project_name, ref_issue.local_id),
default_project_name=default_pn),
"href": tracker_helpers.FormatRelativeIssueURL(
ref_issue.project_name,
urls.ISSUE_DETAIL,
id=ref_issue.local_id),
"closed": ezt.boolean(viewable and
not tracker_helpers.MeansOpenInProject(
ref_issue.status, config)),
"title": ref_issue.summary if viewable else ""}
class TableCellBlockedOn(table_view_helpers.TableCell):
"""TableCell subclass for listing issues the current issue is blocked on."""
def __init__(self, issue, related_issues=None, **_kw):
ref_issues = [related_issues[iid] for iid in issue.blocked_on_iids
if iid in related_issues]
values = [_make_issue_view(issue.project_name, _kw["config"],
_kw["viewable_iids_set"], ref_issue)
for ref_issue in ref_issues]
values.sort(key=lambda x: (x["closed"], x["id"]))
table_view_helpers.TableCell.__init__(
self, table_view_helpers.CELL_TYPE_ISSUES, values, sort_values=False)
class TableCellBlocking(table_view_helpers.TableCell):
"""TableCell subclass for listing issues the current issue is blocking."""
def __init__(self, issue, related_issues=None, **_kw):
ref_issues = [related_issues[iid] for iid in issue.blocking_iids
if iid in related_issues]
values = [_make_issue_view(issue.project_name, _kw["config"],
_kw["viewable_iids_set"], ref_issue)
for ref_issue in ref_issues]
values.sort(key=lambda x: (x.get("closed", False), x["id"]))
table_view_helpers.TableCell.__init__(
self, table_view_helpers.CELL_TYPE_ISSUES, values, sort_values=False)
class TableCellBlocked(table_view_helpers.TableCell):
"""TableCell subclass for showing whether an issue is blocked."""
def __init__(self, issue, **_kw):
if issue.blocked_on_iids:
value = 'Yes'
else:
value = 'No'
table_view_helpers.TableCell.__init__(
self, table_view_helpers.CELL_TYPE_ATTR, [value])
class TableCellMergedInto(table_view_helpers.TableCell):
"""TableCell subclass for showing whether an issue is blocked."""
def __init__(self, issue, related_issues=None, **_kw):
if issue.merged_into:
ref_issue = related_issues[issue.merged_into]
values = [_make_issue_view(issue.project_name, _kw["config"],
_kw["viewable_iids_set"], ref_issue)]
else: # Note: None means not merged into any issue.
values = []
table_view_helpers.TableCell.__init__(
self, table_view_helpers.CELL_TYPE_ISSUES, values)
class TableCellComponent(table_view_helpers.TableCell):
"""TableCell subclass for showing components."""
def __init__(self, issue, config=None, **_kw):
explicit_paths = []
for component_id in issue.component_ids:
cd = tracker_bizobj.FindComponentDefByID(component_id, config)
if cd:
explicit_paths.append(cd.path)
derived_paths = []
for component_id in issue.derived_component_ids:
cd = tracker_bizobj.FindComponentDefByID(component_id, config)
if cd:
derived_paths.append(cd.path)
table_view_helpers.TableCell.__init__(
self, table_view_helpers.CELL_TYPE_ATTR, explicit_paths,
derived_values=derived_paths)
class TableCellAllLabels(table_view_helpers.TableCell):
"""TableCell subclass specifically for showing all labels on an issue."""
def __init__(self, issue, **_kw):
values = []
derived_values = []
if issue.labels:
values = issue.labels[:]
if issue.derived_labels:
derived_values = issue.derived_labels[:]
table_view_helpers.TableCell.__init__(
self, table_view_helpers.CELL_TYPE_ATTR, values,
derived_values=derived_values)
# This maps column names to factories/constructors that make table cells.
# Subclasses can override this mapping, so any additions to this mapping
# should also be added to subclasses.
CELL_FACTORIES = {
'id': TableCellID,
'project': table_view_helpers.TableCellProject,
'component': TableCellComponent,
'summary': table_view_helpers.TableCellSummary,
'status': TableCellStatus,
'owner': TableCellOwner,
'reporter': TableCellReporter,
'cc': TableCellCc,
'stars': table_view_helpers.TableCellStars,
'attachments': TableCellAttachments,
'opened': TableCellOpened,
'closed': TableCellClosed,
'modified': TableCellModified,
'blockedon': TableCellBlockedOn,
'blocking': TableCellBlocking,
'blocked': TableCellBlocked,
'mergedinto': TableCellMergedInto,
'ownermodified': TableCellOwnerModified,
'statusmodified': TableCellStatusModified,
'componentmodified': TableCellComponentModified,
'ownerlastvisit': TableCellOwnerLastVisit,
'rank': TableCellRank,
'added': TableCellDateAdded,
'adder': TableCellAdderID,
'note': TableCellNote,
'alllabels': TableCellAllLabels,
}
# Time format that spreadsheets seem to understand.
# E.g.: "May 19 2008 13:30:23". Tested with MS Excel 2003,
# OpenOffice.org, NeoOffice, and Google Spreadsheets.
CSV_DATE_TIME_FMT = '%b %d, %Y %H:%M:%S'
def TimeStringForCSV(timestamp):
"""Return a timestamp in a format that spreadsheets understand."""
return time.strftime(CSV_DATE_TIME_FMT, time.gmtime(timestamp))
class TableCellOpenedCSV(table_view_helpers.TableCell):
"""TableCell subclass specifically for showing issue opened date."""
def __init__(self, issue, **_kw):
date_str = TimeStringForCSV(issue.opened_timestamp)
table_view_helpers.TableCell.__init__(
self, table_view_helpers.CELL_TYPE_UNFILTERABLE, [date_str])
class TableCellOpenedTimestamp(table_view_helpers.TableCell):
"""TableCell subclass specifically for showing issue opened timestamp."""
def __init__(self, issue, **_kw):
table_view_helpers.TableCell.__init__(
self, table_view_helpers.CELL_TYPE_UNFILTERABLE,
[issue.opened_timestamp])
class TableCellModifiedCSV(table_view_helpers.TableCell):
"""TableCell subclass specifically for showing issue modified date."""
def __init__(self, issue, **_kw):
values = []
if issue.modified_timestamp:
values = [TimeStringForCSV(issue.modified_timestamp)]
table_view_helpers.TableCell.__init__(
self, table_view_helpers.CELL_TYPE_UNFILTERABLE, values)
class TableCellModifiedTimestamp(table_view_helpers.TableCell):
"""TableCell subclass specifically for showing issue modified timestamp."""
def __init__(self, issue, **_kw):
table_view_helpers.TableCell.__init__(
self, table_view_helpers.CELL_TYPE_UNFILTERABLE,
[issue.modified_timestamp])
class TableCellClosedCSV(table_view_helpers.TableCell):
"""TableCell subclass specifically for showing issue closed date."""
def __init__(self, issue, **_kw):
values = []
if issue.closed_timestamp:
values = [TimeStringForCSV(issue.closed_timestamp)]
table_view_helpers.TableCell.__init__(
self, table_view_helpers.CELL_TYPE_UNFILTERABLE, values)
class TableCellClosedTimestamp(table_view_helpers.TableCell):
"""TableCell subclass specifically for showing issue closed timestamp."""
def __init__(self, issue, **_kw):
table_view_helpers.TableCell.__init__(
self, table_view_helpers.CELL_TYPE_UNFILTERABLE,
[issue.closed_timestamp])
class TableCellOwnerModifiedCSV(table_view_helpers.TableCell):
"""TableCell subclass specifically for showing owner modified date."""
def __init__(self, issue, **_kw):
values = []
if issue.modified_timestamp:
values = [TimeStringForCSV(issue.owner_modified_timestamp)]
table_view_helpers.TableCell.__init__(
self, table_view_helpers.CELL_TYPE_UNFILTERABLE, values)
class TableCellOwnerModifiedTimestamp(table_view_helpers.TableCell):
"""TableCell subclass specifically for showing owner modified timestamp."""
def __init__(self, issue, **_kw):
table_view_helpers.TableCell.__init__(
self, table_view_helpers.CELL_TYPE_UNFILTERABLE,
[issue.owner_modified_timestamp])
class TableCellStatusModifiedCSV(table_view_helpers.TableCell):
"""TableCell subclass specifically for showing status modified date."""
def __init__(self, issue, **_kw):
values = []
if issue.modified_timestamp:
values = [TimeStringForCSV(issue.status_modified_timestamp)]
table_view_helpers.TableCell.__init__(
self, table_view_helpers.CELL_TYPE_UNFILTERABLE, values)
class TableCellStatusModifiedTimestamp(table_view_helpers.TableCell):
"""TableCell subclass specifically for showing status modified timestamp."""
def __init__(self, issue, **_kw):
table_view_helpers.TableCell.__init__(
self, table_view_helpers.CELL_TYPE_UNFILTERABLE,
[issue.status_modified_timestamp])
class TableCellComponentModifiedCSV(table_view_helpers.TableCell):
"""TableCell subclass specifically for showing component modified date."""
def __init__(self, issue, **_kw):
values = []
if issue.modified_timestamp:
values = [TimeStringForCSV(issue.component_modified_timestamp)]
table_view_helpers.TableCell.__init__(
self, table_view_helpers.CELL_TYPE_UNFILTERABLE, values)
class TableCellComponentModifiedTimestamp(table_view_helpers.TableCell):
"""TableCell subclass for showing component modified timestamp."""
def __init__(self, issue, **_kw):
table_view_helpers.TableCell.__init__(
self, table_view_helpers.CELL_TYPE_UNFILTERABLE,
[issue.component_modified_timestamp])
class TableCellOwnerLastVisitDaysAgo(table_view_helpers.TableCell):
"""TableCell subclass specifically for showing owner last visit days ago."""
def __init__(self, issue, users_by_id=None, **_kw):
owner_view = users_by_id.get(issue.owner_id or issue.derived_owner_id)
last_visit_days_ago = None
if owner_view and owner_view.user.last_visit_timestamp:
secs_ago = int(time.time()) - owner_view.user.last_visit_timestamp
last_visit_days_ago = secs_ago // framework_constants.SECS_PER_DAY
table_view_helpers.TableCell.__init__(
self, table_view_helpers.CELL_TYPE_UNFILTERABLE, [last_visit_days_ago])
# Maps column names to factories/constructors that make table cells.
# Uses the defaults in issuelist.py but changes the factory for the
# summary cell to properly escape the data for CSV files.
CSV_CELL_FACTORIES = CELL_FACTORIES.copy()
CSV_CELL_FACTORIES.update({
'opened': TableCellOpenedCSV,
'openedtimestamp': TableCellOpenedTimestamp,
'closed': TableCellClosedCSV,
'closedtimestamp': TableCellClosedTimestamp,
'modified': TableCellModifiedCSV,
'modifiedtimestamp': TableCellModifiedTimestamp,
'ownermodified': TableCellOwnerModifiedCSV,
'ownermodifiedtimestamp': TableCellOwnerModifiedTimestamp,
'statusmodified': TableCellStatusModifiedCSV,
'statusmodifiedtimestamp': TableCellStatusModifiedTimestamp,
'componentmodified': TableCellComponentModifiedCSV,
'componentmodifiedtimestamp': TableCellComponentModifiedTimestamp,
'ownerlastvisitdaysago': TableCellOwnerLastVisitDaysAgo,
})