Ejemplo n.º 1
0
def ConvertApproval(approval_value, users_by_id, config, phase=None):
    """Use the given ApprovalValue to create a protoc Approval."""
    approval_name = ''
    fd = tracker_bizobj.FindFieldDefByID(approval_value.approval_id, config)
    if fd:
        approval_name = fd.field_name
    else:
        logging.info(
            'Ignoring approval value referencing a non-existing field: %r',
            approval_value)
        return None

    field_ref = ConvertFieldRef(approval_value.approval_id, approval_name,
                                tracker_pb2.FieldTypes.APPROVAL_TYPE, None)
    approver_refs = ConvertUserRefs(approval_value.approver_ids, [],
                                    users_by_id, False)
    setter_ref = ConvertUserRef(approval_value.setter_id, None, users_by_id)

    status = ConvertApprovalStatus(approval_value.status)
    set_on = approval_value.set_on

    phase_ref = issue_objects_pb2.PhaseRef()
    if phase:
        phase_ref.phase_name = phase.name

    result = issue_objects_pb2.Approval(field_ref=field_ref,
                                        approver_refs=approver_refs,
                                        status=status,
                                        set_on=set_on,
                                        setter_ref=setter_ref,
                                        phase_ref=phase_ref)
    return result
Ejemplo n.º 2
0
def ValidateCustomFields(mr, services, field_values, config, errors):
  """Validate each of the given fields and report problems in errors object."""
  for fv in field_values:
    fd = tracker_bizobj.FindFieldDefByID(fv.field_id, config)
    if fd:
      err_msg = ValidateCustomField(mr, mr.project, services, fd, fv)
      if err_msg:
        errors.SetCustomFieldError(fv.field_id, err_msg)
Ejemplo n.º 3
0
def ConvertApprovalDef(approval_def, users_by_id, config, include_admin_info):
    """Convert a protorpc ApprovalDef into a protoc ApprovalDef."""
    field_def = tracker_bizobj.FindFieldDefByID(approval_def.approval_id,
                                                config)
    field_ref = ConvertFieldRef(field_def.field_id, field_def.field_name,
                                field_def.field_type, None)
    if not include_admin_info:
        return project_objects_pb2.ApprovalDef(field_ref=field_ref)

    approver_refs = ConvertUserRefs(approval_def.approver_ids, [], users_by_id,
                                    False)
    return project_objects_pb2.ApprovalDef(field_ref=field_ref,
                                           approver_refs=approver_refs,
                                           survey=approval_def.survey)
Ejemplo n.º 4
0
def ConvertComment(issue, comment, config, users_by_id, comment_reporters,
                   description_nums, user_id, perms):
    """Convert a protorpc IssueComment to a protoc Comment."""
    commenter = users_by_id[comment.user_id]

    can_delete = permissions.CanDeleteComment(comment, commenter, user_id,
                                              perms)
    can_flag, is_flagged = permissions.CanFlagComment(comment, commenter,
                                                      comment_reporters,
                                                      user_id, perms)
    can_view = permissions.CanViewComment(comment, commenter, user_id, perms)
    can_view_inbound_message = permissions.CanViewInboundMessage(
        comment, user_id, perms)

    is_deleted = bool(comment.deleted_by or is_flagged or commenter.banned)

    result = issue_objects_pb2.Comment(project_name=issue.project_name,
                                       local_id=issue.local_id,
                                       sequence_num=comment.sequence,
                                       is_deleted=is_deleted,
                                       can_delete=can_delete,
                                       is_spam=is_flagged,
                                       can_flag=can_flag,
                                       timestamp=comment.timestamp)

    if can_view:
        result.commenter.CopyFrom(
            ConvertUserRef(comment.user_id, None, users_by_id))
        result.content = comment.content
        if comment.inbound_message and can_view_inbound_message:
            result.inbound_message = comment.inbound_message
        result.amendments.extend([
            ConvertAmendment(amend, users_by_id)
            for amend in comment.amendments
        ])
        result.attachments.extend([
            ConvertAttachment(attach, issue.project_name)
            for attach in comment.attachments
        ])

    if comment.id in description_nums:
        result.description_num = description_nums[comment.id]

    fd = tracker_bizobj.FindFieldDefByID(comment.approval_id, config)
    if fd:
        result.approval_ref.field_name = fd.field_name

    return result
Ejemplo n.º 5
0
def ConvertFieldDef(field_def, user_choices, users_by_id, config,
                    include_admin_info):
    """Convert a protorpc FieldDef into a protoc FieldDef."""
    parent_approval_name = None
    if field_def.approval_id:
        parent_fd = tracker_bizobj.FindFieldDefByID(field_def.approval_id,
                                                    config)
        if parent_fd:
            parent_approval_name = parent_fd.field_name
    field_ref = ConvertFieldRef(field_def.field_id, field_def.field_name,
                                field_def.field_type, parent_approval_name)

    enum_choices = []
    if field_def.field_type == tracker_pb2.FieldTypes.ENUM_TYPE:
        masked_labels = tracker_helpers.LabelsMaskedByFields(
            config, [field_def.field_name], True)
        enum_choices = [
            project_objects_pb2.LabelDef(label=label.name,
                                         docstring=label.docstring,
                                         deprecated=(label.commented == '#'))
            for label in masked_labels
        ]

    if not include_admin_info:
        return project_objects_pb2.FieldDef(
            field_ref=field_ref,
            docstring=field_def.docstring,
            # Display full email address for user choices.
            user_choices=ConvertUserRefs(user_choices, [], users_by_id, True),
            enum_choices=enum_choices)

    admin_refs = ConvertUserRefs(field_def.admin_ids, [], users_by_id, False)
    # TODO(jrobbins): validation, permission granting, and notification options.

    return project_objects_pb2.FieldDef(
        field_ref=field_ref,
        applicable_type=field_def.applicable_type,
        is_required=field_def.is_required,
        is_niche=field_def.is_niche,
        is_multivalued=field_def.is_multivalued,
        docstring=field_def.docstring,
        admin_refs=admin_refs,
        is_phase_field=field_def.is_phase_field,
        enum_choices=enum_choices)
Ejemplo n.º 6
0
  def __init__(self, art, col=None, users_by_id=None, config=None, **_kw):
    explicit_values = []
    derived_values = []
    for fv in art.field_values:
      # TODO(jrobbins): for cross-project search this could be a list.
      fd = tracker_bizobj.FindFieldDefByID(fv.field_id, config)
      if not fd:
        # TODO(jrobbins): This can happen if an issue with a custom
        # field value is moved to a different project.
        logging.warn('Issue ID %r has undefined field value %r',
                     art.issue_id, fv)
      elif fd.field_name.lower() == col:
        val = tracker_bizobj.GetFieldValue(fv, users_by_id)
        if fv.derived:
          derived_values.append(val)
        else:
          explicit_values.append(val)

    TableCell.__init__(self, CELL_TYPE_ATTR, explicit_values,
                       derived_values=derived_values)
Ejemplo n.º 7
0
    def __init__(self, art, col=None, users_by_id=None, config=None, **_kw):
        explicit_values = []
        derived_values = []
        cell_type = CELL_TYPE_ATTR
        phase_names_by_id = {
            phase.phase_id: phase.name.lower()
            for phase in art.phases
        }
        phase_name = None
        # Check if col represents a phase field value in the form <phase>.<field>
        if '.' in col:
            phase_name, col = col.split('.', 1)
        for fv in art.field_values:
            # TODO(jrobbins): for cross-project search this could be a list.
            fd = tracker_bizobj.FindFieldDefByID(fv.field_id, config)
            if not fd:
                # TODO(jrobbins): This can happen if an issue with a custom
                # field value is moved to a different project.
                logging.warn('Issue ID %r has undefined field value %r',
                             art.issue_id, fv)
            elif fd.field_name.lower() == col and (phase_names_by_id.get(
                    fv.phase_id) == phase_name):
                if fd.field_type == tracker_pb2.FieldTypes.URL_TYPE:
                    cell_type = CELL_TYPE_URL
                if fd.field_type == tracker_pb2.FieldTypes.STR_TYPE:
                    self.NOWRAP = ezt.boolean(False)
                val = tracker_bizobj.GetFieldValue(fv, users_by_id)
                if fv.derived:
                    derived_values.append(val)
                else:
                    explicit_values.append(val)

        TableCell.__init__(self,
                           cell_type,
                           explicit_values,
                           derived_values=derived_values)
Ejemplo n.º 8
0
def ComputeUnshownColumns(results, shown_columns, config, built_in_cols):
    """Return a list of unshown columns that the user could add.

  Args:
    results: list of search result PBs. Each must have labels.
    shown_columns: list of column names to be used in results table.
    config: harmonized config for the issue search, including all
        well known labels and custom fields.
    built_in_cols: list of other column names that are built into the tool.
      E.g., star count, or creation date.

  Returns:
    List of column names to append to the "..." menu.
  """
    unshown_set = set()  # lowercases column names
    unshown_list = []  # original-case column names
    shown_set = {col.lower() for col in shown_columns}
    labels_already_seen = set()  # whole labels, original case

    def _MaybeAddLabel(label_name):
        """Add the key part of the given label if needed."""
        if label_name.lower() in labels_already_seen:
            return
        labels_already_seen.add(label_name.lower())
        if '-' in label_name:
            col, _value = label_name.split('-', 1)
            _MaybeAddCol(col)

    def _MaybeAddCol(col):
        if col.lower() not in shown_set and col.lower() not in unshown_set:
            unshown_list.append(col)
            unshown_set.add(col.lower())

    # The user can always add any of the default columns.
    for col in config.default_col_spec.split():
        _MaybeAddCol(col)

    # The user can always add any of the built-in columns.
    for col in built_in_cols:
        _MaybeAddCol(col)

    # The user can add a column for any well-known labels
    for wkl in config.well_known_labels:
        _MaybeAddLabel(wkl.label)

    phase_names = set(
        itertools.chain.from_iterable((phase.name.lower()
                                       for phase in result.phases)
                                      for result in results))
    # The user can add a column for any custom field
    field_ids_alread_seen = set()
    for fd in config.field_defs:
        field_lower = fd.field_name.lower()
        field_ids_alread_seen.add(fd.field_id)
        if fd.is_phase_field:
            for name in phase_names:
                phase_field_col = name + '.' + field_lower
                if (phase_field_col not in shown_set
                        and phase_field_col not in unshown_set):
                    unshown_list.append(phase_field_col)
                    unshown_set.add(phase_field_col)
        elif field_lower not in shown_set and field_lower not in unshown_set:
            unshown_list.append(fd.field_name)
            unshown_set.add(field_lower)

        if fd.field_type == tracker_pb2.FieldTypes.APPROVAL_TYPE:
            approval_lower_approver = (field_lower +
                                       tracker_constants.APPROVER_COL_SUFFIX)
            if (approval_lower_approver not in shown_set
                    and approval_lower_approver not in unshown_set):
                unshown_list.append(fd.field_name +
                                    tracker_constants.APPROVER_COL_SUFFIX)
                unshown_set.add(approval_lower_approver)

    # The user can add a column for any key-value label or field in the results.
    for r in results:
        for label_name in tracker_bizobj.GetLabels(r):
            _MaybeAddLabel(label_name)
        for field_value in r.field_values:
            if field_value.field_id not in field_ids_alread_seen:
                field_ids_alread_seen.add(field_value.field_id)
                fd = tracker_bizobj.FindFieldDefByID(field_value.field_id,
                                                     config)
                if fd:  # could be None for a foreign field, which we don't display.
                    field_lower = fd.field_name.lower()
                    if field_lower not in shown_set and field_lower not in unshown_set:
                        unshown_list.append(fd.field_name)
                        unshown_set.add(field_lower)

    return sorted(unshown_list)
Ejemplo n.º 9
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
Ejemplo n.º 10
0
 def testFindFieldDefByID_Empty(self):
   config = tracker_pb2.ProjectIssueConfig()
   self.assertIsNone(tracker_bizobj.FindFieldDefByID(1, config))
Ejemplo n.º 11
0
 def testFindFieldDefByID_Normal(self):
   config = tracker_bizobj.MakeDefaultProjectIssueConfig(789)
   fd = tracker_pb2.FieldDef(field_id=1)
   config.field_defs = [fd]
   self.assertEqual(fd, tracker_bizobj.FindFieldDefByID(1, config))
   self.assertIsNone(tracker_bizobj.FindFieldDefByID(99, config))
Ejemplo n.º 12
0
 def testFindFieldDefByID_Default(self):
   config = tracker_bizobj.MakeDefaultProjectIssueConfig(789)
   self.assertIsNone(tracker_bizobj.FindFieldDefByID(1, config))
Ejemplo n.º 13
0
    def __init__(self, field_def, config, user_views=None, approval_def=None):
        super(FieldDefView, self).__init__(field_def)

        self.type_name = str(field_def.field_type)

        self.choices = []
        if field_def.field_type == tracker_pb2.FieldTypes.ENUM_TYPE:
            self.choices = tracker_helpers.LabelsMaskedByFields(
                config, [field_def.field_name], trim_prefix=True)

        self.approvers = []
        self.survey = ''
        self.survey_questions = []
        if (approval_def and field_def.field_type
                == tracker_pb2.FieldTypes.APPROVAL_TYPE):
            self.approvers = [
                user_views.get(approver_id)
                for approver_id in approval_def.approver_ids
            ]
            if approval_def.survey:
                self.survey = approval_def.survey
                self.survey_questions = self.survey.split('\n')

        self.docstring_short = template_helpers.FitUnsafeText(
            field_def.docstring, 200)
        self.validate_help = None

        if field_def.is_required:
            self.importance = 'required'
        elif field_def.is_niche:
            self.importance = 'niche'
        else:
            self.importance = 'normal'

        if field_def.min_value is not None:
            self.min_value = field_def.min_value
            self.validate_help = 'Value must be >= %d' % field_def.min_value
        else:
            self.min_value = None  # Otherwise it would default to 0

        if field_def.max_value is not None:
            self.max_value = field_def.max_value
            self.validate_help = 'Value must be <= %d' % field_def.max_value
        else:
            self.max_value = None  # Otherwise it would default to 0

        if field_def.min_value is not None and field_def.max_value is not None:
            self.validate_help = 'Value must be between %d and %d' % (
                field_def.min_value, field_def.max_value)

        if field_def.regex:
            self.validate_help = 'Value must match regex: %s' % field_def.regex

        if field_def.needs_member:
            self.validate_help = 'Value must be a project member'

        if field_def.needs_perm:
            self.validate_help = (
                'Value must be a project member with permission %s' %
                field_def.needs_perm)

        self.date_action_str = str(field_def.date_action
                                   or 'no_action').lower()

        self.admins = []
        if user_views:
            self.admins = [
                user_views.get(admin_id) for admin_id in field_def.admin_ids
            ]

        if field_def.approval_id:
            self.is_approval_subfield = ezt.boolean(True)
            self.parent_approval_name = tracker_bizobj.FindFieldDefByID(
                field_def.approval_id, config).field_name
        else:
            self.is_approval_subfield = ezt.boolean(False)

        self.is_phase_field = ezt.boolean(field_def.is_phase_field)
Ejemplo n.º 14
0
  def HandleRequest(self, mr):
    """Process the task to notify users after an approval change.

    Args:
      mr: common information parsed from the HTTP request.

    Returns:
      Results dictionary in JSON format which is useful just for debugging.
      The main goal is the side-effect of sending emails.
    """

    send_email = bool(mr.GetIntParam('send_email'))
    issue_id = mr.GetPositiveIntParam('issue_id')
    approval_id = mr.GetPositiveIntParam('approval_id')
    comment_id = mr.GetPositiveIntParam('comment_id')
    hostport = mr.GetParam('hostport')

    params = dict(
        temporary='',
        hostport=hostport,
        issue_id=issue_id
        )
    logging.info('approval change params are %r', params)

    issue, approval_value = self.services.issue.GetIssueApproval(
        mr.cnxn, issue_id, approval_id, use_cache=False)
    project = self.services.project.GetProject(mr.cnxn, issue.project_id)
    config = self.services.config.GetProjectConfig(mr.cnxn, issue.project_id)

    approval_fd = tracker_bizobj.FindFieldDefByID(approval_id, config)
    if approval_fd is None:
      raise exceptions.NoSuchFieldDefException()

    # GetCommentsForIssue will fill the sequence for all comments, while
    # other method for getting a single comment will not.
    # The comment sequence is especially useful for Approval issues with
    # many comment sections.
    comment = None
    all_comments = self.services.issue.GetCommentsForIssue(mr.cnxn, issue_id)
    for c in all_comments:
      if c.id == comment_id:
        comment = c
        break
    if not comment:
      raise exceptions.NoSuchCommentException()

    field_user_ids = set()
    relevant_fds = [fd for fd in config.field_defs if
                    not fd.approval_id or
                    fd.approval_id is approval_value.approval_id]
    for fd in relevant_fds:
      field_user_ids.update(
          notify_reasons.ComputeNamedUserIDsToNotify(issue.field_values, fd))
    users_by_id = framework_views.MakeAllUserViews(
        mr.cnxn, self.services.user, [issue.owner_id],
        approval_value.approver_ids,
        tracker_bizobj.UsersInvolvedInComment(comment),
        list(field_user_ids))

    tasks = []
    if send_email:
      tasks = self._MakeApprovalEmailTasks(
          hostport, issue, project, approval_value, approval_fd.field_name,
          comment, users_by_id, list(field_user_ids), mr.perms)

    notified = notify_helpers.AddAllEmailTasks(tasks)

    return {
        'params': params,
        'notified': notified,
        'tasks': tasks,
        }