def testFormatIssueRef(self):
    self.assertEqual('', tracker_bizobj.FormatIssueRef(None))

    self.assertEqual(
        'p:1', tracker_bizobj.FormatIssueRef(('p', 1)))

    self.assertEqual(
        '1', tracker_bizobj.FormatIssueRef((None, 1)))
Example #2
0
  def _ProcessUpstreamIssue(
      self, cnxn, upstream_issue, upstream_project, upstream_config,
      issue, omit_ids, hostport, commenter_view):
    """Compute notifications for one upstream issue that is now blocking."""
    upstream_detail_url = framework_helpers.FormatAbsoluteURLForDomain(
        hostport, upstream_issue.project_name, urls.ISSUE_DETAIL,
        id=upstream_issue.local_id)
    logging.info('upstream_detail_url = %r', upstream_detail_url)
    detail_url = framework_helpers.FormatAbsoluteURLForDomain(
        hostport, issue.project_name, urls.ISSUE_DETAIL,
        id=issue.local_id)

    # Only issues that any contributor could view are sent to mailing lists.
    contributor_could_view = permissions.CanViewIssue(
        set(), permissions.CONTRIBUTOR_ACTIVE_PERMISSIONSET,
        upstream_project, upstream_issue)

    # Now construct the e-mail to send

    # Note: we purposely do not notify users who starred an issue
    # about changes in blocking.
    users_by_id = framework_views.MakeAllUserViews(
        cnxn, self.services.user,
        tracker_bizobj.UsersInvolvedInIssues([upstream_issue]), omit_ids)

    is_blocking = upstream_issue.issue_id in issue.blocked_on_iids

    email_data = {
        'issue': tracker_views.IssueView(
            upstream_issue, users_by_id, upstream_config),
        'summary': upstream_issue.summary,
        'detail_url': upstream_detail_url,
        'is_blocking': ezt.boolean(is_blocking),
        'downstream_issue_ref': tracker_bizobj.FormatIssueRef(
            (None, issue.local_id)),
        'downstream_issue_url': detail_url,
        }

    # TODO(jrobbins): Generate two versions of email body: members
    # vesion has other member full email addresses exposed.  But, don't
    # expose too many as we iterate through upstream projects.
    body_link_only = self.link_only_email_template.GetResponse(
        {'detail_url': upstream_detail_url, 'was_created': ezt.boolean(False)})
    body = self.email_template.GetResponse(email_data)

    omit_addrs = {users_by_id[omit_id].email for omit_id in omit_ids}

    # Get the transitive set of owners and Cc'd users, and their UserView's.
    # Give each user a bullet-list of all the reasons that apply for that user.
    # Starrers are not notified of blocking changes to reduce noise.
    group_reason_list = notify_reasons.ComputeGroupReasonList(
        cnxn, self.services, upstream_project, upstream_issue,
        upstream_config, users_by_id, omit_addrs, contributor_could_view)
    one_issue_email_tasks = notify_helpers.MakeBulletedEmailWorkItems(
        group_reason_list, upstream_issue, body_link_only, body, body,
        upstream_project, hostport, commenter_view, detail_url)

    return one_issue_email_tasks
Example #3
0
 def __init__(self, issue, related_issues=None, **_kw):
     ref_issues = [
         related_issues[iid] for iid in issue.blocking_iids
         if iid in related_issues
     ]
     default_pn = issue.project_name
     # TODO(jrobbins): in cross-project searches, leave default_pn = None.
     values = [
         tracker_bizobj.FormatIssueRef(
             (ref_issue.project_name, ref_issue.local_id),
             default_project_name=default_pn) for ref_issue in ref_issues
     ]
     table_view_helpers.TableCell.__init__(
         self, table_view_helpers.CELL_TYPE_ATTR, values)
Example #4
0
    def __init__(self, issue, related_issues=None, **_kw):
        if issue.merged_into:
            ref_issue = related_issues[issue.merged_into]
            ref = ref_issue.project_name, ref_issue.local_id
            default_pn = issue.project_name
            # TODO(jrobbins): in cross-project searches, leave default_pn = None.
            values = [
                tracker_bizobj.FormatIssueRef(ref,
                                              default_project_name=default_pn)
            ]
        else:  # Note: None means not merged into any issue.
            values = []

        table_view_helpers.TableCell.__init__(
            self, table_view_helpers.CELL_TYPE_ATTR, values)
Example #5
0
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 ""
    }
Example #6
0
def ExtractUniqueValues(columns,
                        artifact_list,
                        users_by_id,
                        config,
                        related_issues,
                        hotlist_context_dict=None):
    """Build a nested list of unique values so the user can auto-filter.

  Args:
    columns: a list of lowercase column name strings, which may contain
        combined columns like "priority/pri".
    artifact_list: a list of artifacts in the complete set of search results.
    users_by_id: dict mapping user_ids to UserViews.
    config: ProjectIssueConfig PB for the current project.
    related_issues: dict {issue_id: issue} of pre-fetched related issues.
    hotlist_context_dict: dict for building a hotlist grid table

  Returns:
    [EZTItem(col1, colname1, [val11, val12,...]), ...]
    A list of EZTItems, each of which has a col_index, column_name,
    and a list of unique values that appear in that column.
  """
    column_values = {col_name: {} for col_name in columns}

    # For each combined column "a/b/c", add entries that point from "a" back
    # to "a/b/c", from "b" back to "a/b/c", and from "c" back to "a/b/c".
    combined_column_parts = collections.defaultdict(list)
    for col in columns:
        if '/' in col:
            for col_part in col.split('/'):
                combined_column_parts[col_part].append(col)

    unique_labels = set()
    for art in artifact_list:
        unique_labels.update(tracker_bizobj.GetLabels(art))

    for label in unique_labels:
        if '-' in label:
            col, val = label.split('-', 1)
            col = col.lower()
            if col in column_values:
                column_values[col][val.lower()] = val
            if col in combined_column_parts:
                for combined_column in combined_column_parts[col]:
                    column_values[combined_column][val.lower()] = val
        else:
            if 'summary' in column_values:
                column_values['summary'][label.lower()] = label

    # TODO(jrobbins): Consider refacting some of this to tracker_bizobj
    # or a new builtins.py to reduce duplication.
    if 'reporter' in column_values:
        for art in artifact_list:
            reporter_id = art.reporter_id
            if reporter_id and reporter_id in users_by_id:
                reporter_username = users_by_id[reporter_id].display_name
                column_values['reporter'][
                    reporter_username] = reporter_username

    if 'owner' in column_values:
        for art in artifact_list:
            owner_id = tracker_bizobj.GetOwnerId(art)
            if owner_id and owner_id in users_by_id:
                owner_username = users_by_id[owner_id].display_name
                column_values['owner'][owner_username] = owner_username

    if 'cc' in column_values:
        for art in artifact_list:
            cc_ids = tracker_bizobj.GetCcIds(art)
            for cc_id in cc_ids:
                if cc_id and cc_id in users_by_id:
                    cc_username = users_by_id[cc_id].display_name
                    column_values['cc'][cc_username] = cc_username

    if 'component' in column_values:
        for art in artifact_list:
            all_comp_ids = list(art.component_ids) + list(
                art.derived_component_ids)
            for component_id in all_comp_ids:
                cd = tracker_bizobj.FindComponentDefByID(component_id, config)
                if cd:
                    column_values['component'][cd.path] = cd.path

    if 'stars' in column_values:
        for art in artifact_list:
            star_count = art.star_count
            column_values['stars'][star_count] = star_count

    if 'status' in column_values:
        for art in artifact_list:
            status = tracker_bizobj.GetStatus(art)
            if status:
                column_values['status'][status.lower()] = status

    if 'project' in column_values:
        for art in artifact_list:
            project_name = art.project_name
            column_values['project'][project_name] = project_name

    if 'mergedinto' in column_values:
        for art in artifact_list:
            if art.merged_into and art.merged_into != 0:
                merged_issue = related_issues[art.merged_into]
                merged_issue_ref = tracker_bizobj.FormatIssueRef(
                    (merged_issue.project_name, merged_issue.local_id))
                column_values['mergedinto'][
                    merged_issue_ref] = merged_issue_ref

    if 'blocked' in column_values:
        for art in artifact_list:
            if art.blocked_on_iids:
                column_values['blocked']['is_blocked'] = 'Yes'
            else:
                column_values['blocked']['is_not_blocked'] = 'No'

    if 'blockedon' in column_values:
        for art in artifact_list:
            if art.blocked_on_iids:
                for blocked_on_iid in art.blocked_on_iids:
                    blocked_on_issue = related_issues[blocked_on_iid]
                    blocked_on_ref = tracker_bizobj.FormatIssueRef(
                        (blocked_on_issue.project_name,
                         blocked_on_issue.local_id))
                    column_values['blockedon'][blocked_on_ref] = blocked_on_ref

    if 'blocking' in column_values:
        for art in artifact_list:
            if art.blocking_iids:
                for blocking_iid in art.blocking_iids:
                    blocking_issue = related_issues[blocking_iid]
                    blocking_ref = tracker_bizobj.FormatIssueRef(
                        (blocking_issue.project_name, blocking_issue.local_id))
                    column_values['blocking'][blocking_ref] = blocking_ref

    if 'added' in column_values:
        for art in artifact_list:
            if hotlist_context_dict and hotlist_context_dict[art.issue_id]:
                issue_dict = hotlist_context_dict[art.issue_id]
                date_added = issue_dict['date_added']
                column_values['added'][date_added] = date_added

    if 'adder' in column_values:
        for art in artifact_list:
            if hotlist_context_dict and hotlist_context_dict[art.issue_id]:
                issue_dict = hotlist_context_dict[art.issue_id]
                adder_id = issue_dict['adder_id']
                adder = users_by_id[adder_id].display_name
                column_values['adder'][adder] = adder

    if 'note' in column_values:
        for art in artifact_list:
            if hotlist_context_dict and hotlist_context_dict[art.issue_id]:
                issue_dict = hotlist_context_dict[art.issue_id]
                note = issue_dict['note']
                if issue_dict['note']:
                    column_values['note'][note] = note

    if 'attachments' in column_values:
        for art in artifact_list:
            attachment_count = art.attachment_count
            column_values['attachments'][attachment_count] = attachment_count

    # Add all custom field values if the custom field name is a shown column.
    field_id_to_col = {}
    for art in artifact_list:
        for fv in art.field_values:
            field_col, field_type = field_id_to_col.get(
                fv.field_id, (None, None))
            if field_col == 'NOT_SHOWN':
                continue
            if field_col is None:
                fd = tracker_bizobj.FindFieldDefByID(fv.field_id, config)
                if not fd:
                    field_id_to_col[fv.field_id] = 'NOT_SHOWN', None
                    continue
                field_col = fd.field_name.lower()
                field_type = fd.field_type
                if field_col not in column_values:
                    field_id_to_col[fv.field_id] = 'NOT_SHOWN', None
                    continue
                field_id_to_col[fv.field_id] = field_col, field_type

            if field_type == tracker_pb2.FieldTypes.ENUM_TYPE:
                continue  # Already handled by label parsing
            elif field_type == tracker_pb2.FieldTypes.INT_TYPE:
                val = fv.int_value
            elif field_type == tracker_pb2.FieldTypes.STR_TYPE:
                val = fv.str_value
            elif field_type == tracker_pb2.FieldTypes.USER_TYPE:
                user = users_by_id.get(fv.user_id)
                val = user.email if user else framework_constants.NO_USER_NAME
            elif field_type == tracker_pb2.FieldTypes.DATE_TYPE:
                val = fv.int_value  # TODO(jrobbins): convert to date
            elif field_type == tracker_pb2.FieldTypes.BOOL_TYPE:
                val = 'Yes' if fv.int_value else 'No'

            column_values[field_col][val] = val

    # TODO(jrobbins): make the capitalization of well-known unique label and
    # status values match the way it is written in the issue config.

    # Return EZTItems for each column in left-to-right display order.
    result = []
    for i, col_name in enumerate(columns):
        # TODO(jrobbins): sort each set of column values top-to-bottom, by the
        # order specified in the project artifact config. For now, just sort
        # lexicographically to make expected output defined.
        sorted_col_values = sorted(column_values[col_name].values())
        result.append(
            template_helpers.EZTItem(col_index=i,
                                     column_name=col_name,
                                     filter_values=sorted_col_values))

    return result
Example #7
0
def GetArtifactAttr(art,
                    attribute_name,
                    users_by_id,
                    label_attr_values_dict,
                    config,
                    related_issues,
                    hotlist_issue_context=None):
    """Return the requested attribute values of the given artifact.

  Args:
    art: a tracked artifact with labels, local_id, summary, stars, and owner.
    attribute_name: lowercase string name of attribute to get.
    users_by_id: dictionary of UserViews already created.
    label_attr_values_dict: dictionary {'key': [value, ...], }.
    config: ProjectIssueConfig PB for the current project.
    related_issues: dict {issue_id: issue} of pre-fetched related issues.
    hotlist_issue_context: dict of {hotlist_issue_field: field_value,..}

  Returns:
    A list of string attribute values, or [framework_constants.NO_VALUES]
    if the artifact has no value for that attribute.
  """
    if attribute_name == '--':
        return []
    if attribute_name == 'id':
        return [art.local_id]
    if attribute_name == 'summary':
        return [art.summary]
    if attribute_name == 'status':
        return [tracker_bizobj.GetStatus(art)]
    if attribute_name == 'stars':
        return [art.star_count]
    if attribute_name == 'attachments':
        return [art.attachment_count]
    # TODO(jrobbins): support blocking
    if attribute_name == 'project':
        return [art.project_name]
    if attribute_name == 'mergedinto':
        if art.merged_into and art.merged_into != 0:
            return [
                tracker_bizobj.FormatIssueRef(
                    (related_issues[art.merged_into].project_name,
                     related_issues[art.merged_into].local_id))
            ]
        else:
            return [framework_constants.NO_VALUES]
    if attribute_name == 'blocked':
        return ['Yes' if art.blocked_on_iids else 'No']
    if attribute_name == 'blockedon':
        if not art.blocked_on_iids:
            return [framework_constants.NO_VALUES]
        else:
            return [
                tracker_bizobj.FormatIssueRef(
                    (related_issues[blocked_on_iid].project_name,
                     related_issues[blocked_on_iid].local_id))
                for blocked_on_iid in art.blocked_on_iids
            ]
    if attribute_name == 'adder':
        if hotlist_issue_context:
            adder_id = hotlist_issue_context['adder_id']
            return [users_by_id[adder_id].display_name]
        else:
            return [framework_constants.NO_VALUES]
    if attribute_name == 'added':
        if hotlist_issue_context:
            return [hotlist_issue_context['date_added']]
        else:
            return [framework_constants.NO_VALUES]
    if attribute_name == 'reporter':
        return [users_by_id[art.reporter_id].display_name]
    if attribute_name == 'owner':
        owner_id = tracker_bizobj.GetOwnerId(art)
        if not owner_id:
            return [framework_constants.NO_VALUES]
        else:
            return [users_by_id[owner_id].display_name]
    if attribute_name == 'cc':
        cc_ids = tracker_bizobj.GetCcIds(art)
        if not cc_ids:
            return [framework_constants.NO_VALUES]
        else:
            return [users_by_id[cc_id].display_name for cc_id in cc_ids]
    if attribute_name == 'component':
        comp_ids = list(art.component_ids) + list(art.derived_component_ids)
        if not comp_ids:
            return [framework_constants.NO_VALUES]
        else:
            paths = []
            for comp_id in comp_ids:
                cd = tracker_bizobj.FindComponentDefByID(comp_id, config)
                if cd:
                    paths.append(cd.path)
            return paths

    # Check to see if it is a field. Process as field only if it is not an enum
    # type because enum types are stored as key-value labels.
    fd = tracker_bizobj.FindFieldDef(attribute_name, config)
    if fd and fd.field_type != tracker_pb2.FieldTypes.ENUM_TYPE:
        values = []
        for fv in art.field_values:
            if fv.field_id == fd.field_id:
                value = tracker_bizobj.GetFieldValueWithRawValue(
                    fd.field_type, fv, users_by_id, None)
                values.append(value)
        return values

    # Since it is not a built-in attribute or a field, it must be a key-value
    # label.
    return label_attr_values_dict.get(attribute_name,
                                      [framework_constants.NO_VALUES])
Example #8
0
  def __init__(
      self, issue, users_by_id, config, open_related=None,
      closed_related=None, all_related=None):
    """Store relevant values for later display by EZT.

    Args:
      issue: An Issue protocol buffer.
      users_by_id: dict {user_id: UserViews} for all users mentioned in issue.
      config: ProjectIssueConfig for this issue.
      open_related: dict of visible open issues that are related to this issue.
      closed_related: dict {issue_id: issue} of visible closed issues that
          are related to this issue.
      all_related: dict {issue_id: issue} of all blocked-on, blocking,
          or merged-into issues referenced from this issue, regardless of
          perms.
    """
    super(IssueView, self).__init__(issue)

    # The users involved in this issue must be present in users_by_id if
    # this IssueView is to be used on the issue detail or peek pages. But,
    # they can be absent from users_by_id if the IssueView is used as a
    # tile in the grid view.
    self.owner = users_by_id.get(issue.owner_id)
    self.derived_owner = users_by_id.get(issue.derived_owner_id)
    self.cc = [users_by_id.get(cc_id) for cc_id in issue.cc_ids
               if cc_id]
    self.derived_cc = [users_by_id.get(cc_id)
                       for cc_id in issue.derived_cc_ids
                       if cc_id]
    self.status = framework_views.StatusView(issue.status, config)
    self.derived_status = framework_views.StatusView(
        issue.derived_status, config)
    # If we don't have a config available, we don't need to access is_open, so
    # let it be True.
    self.is_open = ezt.boolean(
        not config or
        tracker_helpers.MeansOpenInProject(
            tracker_bizobj.GetStatus(issue), config))

    self.components = sorted(
        [ComponentValueView(component_id, config, False)
         for component_id in issue.component_ids
         if tracker_bizobj.FindComponentDefByID(component_id, config)] +
        [ComponentValueView(component_id, config, True)
         for component_id in issue.derived_component_ids
         if tracker_bizobj.FindComponentDefByID(component_id, config)],
        key=lambda cvv: cvv.path)

    self.fields = [
        MakeFieldValueView(
            fd, config, issue.labels, issue.derived_labels, issue.field_values,
            users_by_id)
        # TODO(jrobbins): field-level view restrictions, display options
        for fd in config.field_defs
        if not fd.is_deleted]
    self.fields = sorted(
        self.fields, key=lambda f: (f.applicable_type, f.field_name))

    field_names = [fd.field_name.lower() for fd in config.field_defs
                   if not fd.is_deleted]  # TODO(jrobbins): restricts
    self.labels = [
        framework_views.LabelView(label, config)
        for label in tracker_bizobj.NonMaskedLabels(issue.labels, field_names)]
    self.derived_labels = [
        framework_views.LabelView(label, config)
        for label in issue.derived_labels
        if not tracker_bizobj.LabelIsMaskedByField(label, field_names)]
    self.restrictions = _RestrictionsView(issue)

    # TODO(jrobbins): sort by order of labels in project config

    self.short_summary = issue.summary[:tracker_constants.SHORT_SUMMARY_LENGTH]

    if issue.closed_timestamp:
      self.closed = timestr.FormatAbsoluteDate(issue.closed_timestamp)
    else:
      self.closed = ''

    blocked_on_iids = issue.blocked_on_iids
    blocking_iids = issue.blocking_iids

    # Note that merged_into_str and blocked_on_str includes all issue
    # references, even those referring to issues that the user can't view,
    # so open_related and closed_related cannot be used.
    if all_related is not None:
      all_blocked_on_refs = [
          (all_related[ref_iid].project_name, all_related[ref_iid].local_id)
          for ref_iid in issue.blocked_on_iids]
      all_blocked_on_refs.extend([
          (r.project, r.issue_id) for r in issue.dangling_blocked_on_refs])
      self.blocked_on_str = ', '.join(
          tracker_bizobj.FormatIssueRef(
              ref, default_project_name=issue.project_name)
          for ref in all_blocked_on_refs)
      all_blocking_refs = [
          (all_related[ref_iid].project_name, all_related[ref_iid].local_id)
          for ref_iid in issue.blocking_iids]
      all_blocking_refs.extend([
          (r.project, r.issue_id) for r in issue.dangling_blocking_refs])
      self.blocking_str = ', '.join(
          tracker_bizobj.FormatIssueRef(
              ref, default_project_name=issue.project_name)
          for ref in all_blocking_refs)
      if issue.merged_into:
        merged_issue = all_related[issue.merged_into]
        merged_into_ref = merged_issue.project_name, merged_issue.local_id
      else:
        merged_into_ref = None
      self.merged_into_str = tracker_bizobj.FormatIssueRef(
          merged_into_ref, default_project_name=issue.project_name)

    self.blocked_on = []
    self.has_dangling = ezt.boolean(self.dangling_blocked_on_refs)
    self.blocking = []
    current_project_name = issue.project_name

    if open_related is not None and closed_related is not None:
      self.merged_into = IssueRefView(
          current_project_name, issue.merged_into,
          open_related, closed_related)

      self.blocked_on = [
          IssueRefView(current_project_name, iid, open_related, closed_related)
          for iid in blocked_on_iids]
      self.blocked_on.extend(
          [DanglingIssueRefView(ref.project, ref.issue_id)
           for ref in issue.dangling_blocked_on_refs])
      self.blocked_on = [irv for irv in self.blocked_on if irv.visible]
      # TODO(jrobbins): sort by irv project_name and local_id

      self.blocking = [
          IssueRefView(current_project_name, iid, open_related, closed_related)
          for iid in blocking_iids]
      self.blocking.extend(
          [DanglingIssueRefView(ref.project, ref.issue_id)
           for ref in issue.dangling_blocking_refs])
      self.blocking = [irv for irv in self.blocking if irv.visible]
      # TODO(jrobbins): sort by irv project_name and local_id

    self.multiple_blocked_on = ezt.boolean(len(self.blocked_on) >= 2)
    self.detail_relative_url = tracker_helpers.FormatRelativeIssueURL(
        issue.project_name, urls.ISSUE_DETAIL, id=issue.local_id)