Exemple #1
0
    def ConvertIssueApprovalsTemplate(self, mc, request):
        """Update an issue's existing approvals structure to match the one of the
       given template."""

        if not request.issue_ref.local_id or not request.issue_ref.project_name:
            raise exceptions.InputException('Param `issue_ref.local_id` empty')
        if not request.template_name:
            raise exceptions.InputException('Param `template_name` empty')

        project, issue, config = self._GetProjectIssueAndConfig(
            mc, request.issue_ref, use_cache=False)

        with work_env.WorkEnv(mc, self.services) as we:
            we.ConvertIssueApprovalsTemplate(config,
                                             issue,
                                             request.template_name,
                                             request.comment_content,
                                             send_email=request.send_email)
            related_refs = we.GetRelatedIssueRefs([issue])

        with mc.profiler.Phase('making user views'):
            users_involved_in_issue = tracker_bizobj.UsersInvolvedInIssues(
                [issue])
            users_by_id = framework_views.MakeAllUserViews(
                mc.cnxn, self.services.user, users_involved_in_issue)
            framework_views.RevealAllEmailsToMembers(mc.auth, project,
                                                     users_by_id)

        with mc.profiler.Phase('converting to response objects'):
            response = issues_pb2.ConvertIssueApprovalsTemplateResponse()
            response.issue.CopyFrom(
                converters.ConvertIssue(issue, users_by_id, related_refs,
                                        config))
        return response
Exemple #2
0
    def HandleRequest(self, mr):
        """Process the task to process an issue date action.

    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.
    """
        issue_id = mr.GetPositiveIntParam('issue_id')
        hostport = framework_helpers.GetHostPort()
        issue = self.services.issue.GetIssue(mr.cnxn, issue_id)
        project = self.services.project.GetProject(mr.cnxn, issue.project_id)
        config = self.services.config.GetProjectConfig(mr.cnxn,
                                                       issue.project_id)
        pings = self._CalculateIssuePings(issue, config)
        if not pings:
            logging.warning('Issue %r has no dates to ping afterall?',
                            issue_id)
            return
        comment = self._CreatePingComment(mr.cnxn, issue, pings, hostport)

        users_by_id = framework_views.MakeAllUserViews(
            mr.cnxn, self.services.user,
            tracker_bizobj.UsersInvolvedInIssues([issue]), [comment.user_id])
        logging.info('users_by_id is %r', users_by_id)
        tasks = self._MakeEmailTasks(mr.cnxn, issue, project, config, comment,
                                     hostport, users_by_id, pings)

        notified = notify_helpers.AddAllEmailTasks(tasks)
        return {
            'notified': notified,
        }
Exemple #3
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
 def testUsersInvolvedInIssues_Normal(self):
   issue1 = tracker_pb2.Issue(
       reporter_id=111L, owner_id=222L, cc_ids=[222L, 333L])
   issue2 = tracker_pb2.Issue(
       reporter_id=333L, owner_id=444L, derived_cc_ids=[222L, 444L])
   issue2.field_values = [tracker_pb2.FieldValue(user_id=555L)]
   self.assertEqual(
       set([0L, 111L, 222L, 333L, 444L, 555L]),
       tracker_bizobj.UsersInvolvedInIssues([issue1, issue2]))
    def HandleRequest(self, mr):
        """Build up a dictionary of data values to use when rendering the page.

    Args:
      mr: commonly used info parsed from the request.

    Returns:
      Dict of values used by EZT for rendering the page.
    """
        if not mr.start and not mr.num:
            issues = self.services.issue.GetAllIssuesInProject(
                mr.cnxn, mr.project.project_id)
        else:
            local_id_range = range(mr.start, mr.start + mr.num)
            issues = self.services.issue.GetIssuesByLocalIDs(
                mr.cnxn, mr.project.project_id, local_id_range)
        user_id_set = tracker_bizobj.UsersInvolvedInIssues(issues)

        comments_dict = self.services.issue.GetCommentsForIssues(
            mr.cnxn, [issue.issue_id for issue in issues])
        for comment_list in comments_dict.itervalues():
            user_id_set.update(
                tracker_bizobj.UsersInvolvedInCommentList(comment_list))

        starrers_dict = self.services.issue_star.LookupItemsStarrers(
            mr.cnxn, [issue.issue_id for issue in issues])
        for starrer_id_list in starrers_dict.itervalues():
            user_id_set.update(starrer_id_list)

        # The value 0 indicates "no user", e.g., that an issue has no owner.
        # We don't need to create a User row to represent that.
        user_id_set.discard(0)
        email_dict = self.services.user.LookupUserEmails(mr.cnxn, user_id_set)

        issues_json = [
            self._MakeIssueJSON(mr, issue, email_dict,
                                comments_dict.get(issue.issue_id, []),
                                starrers_dict.get(issue.issue_id, []))
            for issue in issues if not issue.deleted
        ]

        json_data = {
            'metadata': {
                'version': 1,
                'when': int(time.time()),
                'who': mr.auth.email,
                'project': mr.project_name,
                'start': mr.start,
                'num': mr.num,
            },
            'issues': issues_json,
            # This list could be derived from the 'issues', but we provide it for
            # ease of processing.
            'emails': email_dict.values(),
        }
        return json_data
Exemple #6
0
    def ListHotlistIssues(self, mc, request):
        """Get the issues on the specified hotlist."""
        # TODO(ehmaldonado): This probably doesn't work, since we need to check
        # the permissions for each issue in their own project, and we're not doing
        # that.
        hotlist_id = converters.IngestHotlistRef(mc.cnxn, self.services.user,
                                                 self.services.features,
                                                 request.hotlist_ref)

        with work_env.WorkEnv(mc, self.services) as we:
            hotlist_items = we.GetHotlist(hotlist_id).items
            issue_ids = [item.issue_id for item in hotlist_items]
            issues = we.GetIssuesDict(issue_ids)

            projects = we.GetProjectsByName(
                [issue.project_name for issue in issues.values()])
            configs = we.GetProjectConfigs(
                [project.project_id for project in projects.values()])
            configs = {
                project.project_name: configs[project.project_id]
                for project in projects.values()
            }
            related_refs = we.GetRelatedIssueRefs(iter(issues.values()))

        with mc.profiler.Phase('making user views'):
            users_involved = set(item.adder_id for item in hotlist_items)
            users_involved.update(
                tracker_bizobj.UsersInvolvedInIssues(iter(issues.values())))
            users_by_id = framework_views.MakeAllUserViews(
                mc.cnxn, self.services.user, users_involved)
            framework_views.RevealAllEmailsToMembers(mc.auth, None,
                                                     users_by_id)

        hotlist_items = [
            hotlist_item for hotlist_item in hotlist_items
            if hotlist_item.issue_id in issues
        ]

        start, max_items = converters.IngestPagination(request.pagination)
        pagination = paginate.ArtifactPagination(hotlist_items, max_items,
                                                 start, None, None)

        result = features_pb2.ListHotlistIssuesResponse(items=[
            converters.ConvertHotlistItem(hotlist_item, issues, users_by_id,
                                          related_refs, configs)
            for hotlist_item in pagination.visible_results
        ])
        return result
Exemple #7
0
    def ListReferencedIssues(self, mc, request):
        """Return the specified issues in a response proto."""
        if not request.issue_refs:
            return issues_pb2.ListReferencedIssuesResponse()

        for issue_ref in request.issue_refs:
            if not issue_ref.project_name:
                raise exceptions.InputException(
                    'Param `project_name` required.')
            if not issue_ref.local_id:
                raise exceptions.InputException('Param `local_id` required.')

        default_project_name = request.issue_refs[0].project_name
        ref_tuples = [(ref.project_name, ref.local_id)
                      for ref in request.issue_refs]
        with work_env.WorkEnv(mc, self.services) as we:
            open_issues, closed_issues = we.ListReferencedIssues(
                ref_tuples, default_project_name)
            all_issues = open_issues + closed_issues
            all_project_ids = [issue.project_id for issue in all_issues]
            related_refs = we.GetRelatedIssueRefs(all_issues)
            configs = we.GetProjectConfigs(all_project_ids)

        with mc.profiler.Phase('making user views'):
            users_involved = tracker_bizobj.UsersInvolvedInIssues(all_issues)
            users_by_id = framework_views.MakeAllUserViews(
                mc.cnxn, self.services.user, users_involved)
            framework_views.RevealAllEmailsToMembers(mc.auth, None,
                                                     users_by_id)

        with mc.profiler.Phase('converting to response objects'):
            converted_open_issues = [
                converters.ConvertIssue(issue, users_by_id, related_refs,
                                        configs[issue.project_id])
                for issue in open_issues
            ]
            converted_closed_issues = [
                converters.ConvertIssue(issue, users_by_id, related_refs,
                                        configs[issue.project_id])
                for issue in closed_issues
            ]
            response = issues_pb2.ListReferencedIssuesResponse(
                open_refs=converted_open_issues,
                closed_refs=converted_closed_issues)

        return response
Exemple #8
0
    def GetIssue(self, mc, request):
        """Return the specified issue in a response proto."""
        issue_ref = request.issue_ref
        project, issue, config = self._GetProjectIssueAndConfig(
            mc, issue_ref, view_deleted=True, issue_required=False)

        # Code for getting where a moved issue was moved to.
        if issue is None:
            moved_to_ref = self.services.issue.GetCurrentLocationOfMovedIssue(
                mc.cnxn, project.project_id, issue_ref.local_id)
            moved_to_project_id, moved_to_id = moved_to_ref
            moved_to_project_name = None

            if moved_to_project_id is not None:
                with work_env.WorkEnv(mc, self.services) as we:
                    moved_to_project = we.GetProject(moved_to_project_id)
                    moved_to_project_name = moved_to_project.project_name
                return issues_pb2.IssueResponse(
                    moved_to_ref=converters.ConvertIssueRef((
                        moved_to_project_name, moved_to_id)))

            raise exceptions.NoSuchIssueException()

        if issue.deleted:
            return issues_pb2.IssueResponse(issue=issue_objects_pb2.Issue(
                is_deleted=True))

        with work_env.WorkEnv(mc, self.services) as we:
            related_refs = we.GetRelatedIssueRefs([issue])

        with mc.profiler.Phase('making user views'):
            users_involved_in_issue = tracker_bizobj.UsersInvolvedInIssues(
                [issue])
            users_by_id = framework_views.MakeAllUserViews(
                mc.cnxn, self.services.user, users_involved_in_issue)
            framework_views.RevealAllEmailsToMembers(mc.auth, project,
                                                     users_by_id)

        with mc.profiler.Phase('converting to response objects'):
            response = issues_pb2.IssueResponse()
            response.issue.CopyFrom(
                converters.ConvertIssue(issue, users_by_id, related_refs,
                                        config))

        return response
Exemple #9
0
def _IndexIssueBatch(cnxn, issues, user_service, issue_service, config_dict):
    """Internal method to (re)index the given batch of issues.

  Args:
    cnxn: connection to SQL database.
    issues: list of Issue PBs to index.
    user_service: interface to user data storage.
    issue_service: interface to issue data storage.
    config_dict: dict {project_id: config} for all the projects that
        the given issues are in.
  """
    user_ids = tracker_bizobj.UsersInvolvedInIssues(issues)
    comments_dict = issue_service.GetCommentsForIssues(
        cnxn, [issue.issue_id for issue in issues])
    for comments in comments_dict.values():
        user_ids.update([ic.user_id for ic in comments])

    users_by_id = framework_views.MakeAllUserViews(cnxn, user_service,
                                                   user_ids)
    _CreateIssueSearchDocuments(issues, comments_dict, users_by_id,
                                config_dict)
Exemple #10
0
def MakeViewsForUsersInIssues(cnxn, issue_list, user_service, omit_ids=None):
    """Lookup all the users involved in any of the given issues.

  Args:
    cnxn: connection to SQL database.
    issue_list: list of Issue PBs from a result query.
    user_service: Connection to User backend storage.
    omit_ids: a list of user_ids to omit, e.g., because we already have them.

  Returns:
    A dictionary {user_id: user_view,...} for all the users involved
    in the given issues.
  """
    issue_participant_id_set = tracker_bizobj.UsersInvolvedInIssues(issue_list)
    if omit_ids:
        issue_participant_id_set.difference_update(omit_ids)

    # TODO(jrobbins): consider caching View objects as well.
    users_by_id = framework_views.MakeAllUserViews(cnxn, user_service,
                                                   issue_participant_id_set)

    return users_by_id
Exemple #11
0
    def UpdateIssue(self, mc, request):
        """Apply a delta and comment to the specified issue, then return it."""
        project, issue, config = self._GetProjectIssueAndConfig(
            mc, request.issue_ref, use_cache=False)

        with work_env.WorkEnv(mc, self.services) as we:
            if request.HasField('delta'):
                delta = converters.IngestIssueDelta(mc.cnxn, self.services,
                                                    request.delta, config,
                                                    issue.phases)
            else:
                delta = tracker_pb2.IssueDelta()  # No changes specified.
            attachments = converters.IngestAttachmentUploads(request.uploads)
            we.UpdateIssue(issue,
                           delta,
                           request.comment_content,
                           send_email=request.send_email,
                           attachments=attachments,
                           is_description=request.is_description,
                           kept_attachments=list(request.kept_attachments))
            related_refs = we.GetRelatedIssueRefs([issue])

        with mc.profiler.Phase('making user views'):
            users_involved_in_issue = tracker_bizobj.UsersInvolvedInIssues(
                [issue])
            users_by_id = framework_views.MakeAllUserViews(
                mc.cnxn, self.services.user, users_involved_in_issue)
            framework_views.RevealAllEmailsToMembers(mc.auth, project,
                                                     users_by_id)

        with mc.profiler.Phase('converting to response objects'):
            response = issues_pb2.IssueResponse()
            response.issue.CopyFrom(
                converters.ConvertIssue(issue, users_by_id, related_refs,
                                        config))

        return response
Exemple #12
0
    def GetGridViewData(self, mr):
        """EZT template values to render a Table View of issues.

    Args:
      mr: commonly used info parsed from the request.

    Returns:
      Dictionary of page data for rendering of the Table View.
    """
        mr.ComputeColSpec(mr.hotlist)
        starred_iid_set = set(
            self.services.issue_star.LookupStarredItemIDs(
                mr.cnxn, mr.auth.user_id))
        issues_list = self.services.issue.GetIssues(
            mr.cnxn,
            [hotlist_issue.issue_id for hotlist_issue in mr.hotlist.items])
        allowed_issues = hotlist_helpers.FilterIssues(mr, issues_list,
                                                      self.services)
        issue_and_hotlist_users = tracker_bizobj.UsersInvolvedInIssues(
            allowed_issues
            or []).union(features_bizobj.UsersInvolvedInHotlists([mr.hotlist]))
        users_by_id = framework_views.MakeAllUserViews(
            mr.cnxn, self.services.user, issue_and_hotlist_users)
        hotlist_issues_project_ids = hotlist_helpers.GetAllProjectsOfIssues(
            [issue for issue in issues_list])
        config_list = hotlist_helpers.GetAllConfigsOfProjects(
            mr.cnxn, hotlist_issues_project_ids, self.services)
        harmonized_config = tracker_bizobj.HarmonizeConfigs(config_list)
        limit = settings.max_issues_in_grid
        grid_limited = len(allowed_issues) > limit
        lower_cols = mr.col_spec.lower().split()
        grid_x = (mr.x or harmonized_config.default_x_attr or '--').lower()
        grid_y = (mr.y or harmonized_config.default_y_attr or '--').lower()
        lower_cols.append(grid_x)
        lower_cols.append(grid_y)
        related_iids = set()
        for issue in allowed_issues:
            if 'blockedon' in lower_cols:
                related_iids.update(issue.blocked_on_iids)
            if 'blocking' in lower_cols:
                related_iids.update(issue.blocking_iids)
            if 'mergedinto' in lower_cols:
                related_iids.add(issue.merged_into)
        related_issues_list = self.services.issue.GetIssues(
            mr.cnxn, list(related_iids))
        related_issues = {
            issue.issue_id: issue
            for issue in related_issues_list
        }

        hotlist_context_dict = {
            hotlist_issue.issue_id: {
                'adder_id': hotlist_issue.adder_id,
                'date_added':
                timestr.FormatRelativeDate(hotlist_issue.date_added),
                'note': hotlist_issue.note
            }
            for hotlist_issue in mr.hotlist.items
        }

        grid_view_data = grid_view_helpers.GetGridViewData(
            mr,
            allowed_issues,
            harmonized_config,
            users_by_id,
            starred_iid_set,
            grid_limited,
            related_issues,
            hotlist_context_dict=hotlist_context_dict)

        url_params = [(name, mr.GetParam(name))
                      for name in framework_helpers.RECOGNIZED_PARAMS]
        # We are passing in None for the project_name in ArtifactPagination
        # because we are not operating under any project.
        grid_view_data.update({
            'pagination':
            paginate.ArtifactPagination(
                allowed_issues,
                mr.GetPositiveIntParam(
                    'num', features_constants.DEFAULT_RESULTS_PER_PAGE),
                mr.GetPositiveIntParam('start'),
                None,
                urls.HOTLIST_ISSUES,
                total_count=len(allowed_issues),
                url_params=url_params)
        })

        return grid_view_data
Exemple #13
0
  def HandleRequest(self, mr):
    """Process the task to notify users after an issue blocking 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.
    """
    issue_ids = mr.GetIntListParam('issue_ids')
    hostport = mr.GetParam('hostport')
    if not issue_ids:
      return {
          'params': {},
          'notified': [],
          'message': 'Cannot proceed without a valid issue IDs.',
      }

    old_owner_ids = mr.GetIntListParam('old_owner_ids')
    comment_text = mr.GetParam('comment_text')
    commenter_id = mr.GetPositiveIntParam('commenter_id')
    amendments = mr.GetParam('amendments')
    send_email = bool(mr.GetIntParam('send_email'))
    params = dict(
        issue_ids=issue_ids, commenter_id=commenter_id, hostport=hostport,
        old_owner_ids=old_owner_ids, comment_text=comment_text,
        send_email=send_email, amendments=amendments)

    logging.info('bulk edit params are %r', params)
    issues = self.services.issue.GetIssues(mr.cnxn, issue_ids)
    # TODO(jrobbins): For cross-project bulk edits, prefetch all relevant
    # projects and configs and pass a dict of them to subroutines.  For
    # now, all issue must be in the same project.
    project_id = issues[0].project_id
    project = self.services.project.GetProject(mr.cnxn, project_id)
    config = self.services.config.GetProjectConfig(mr.cnxn, project_id)
    issues = [issue for issue in issues if not issue.is_spam]
    anon_perms = permissions.GetPermissions(None, set(), project)

    users_by_id = framework_views.MakeAllUserViews(
        mr.cnxn, self.services.user, [commenter_id])
    ids_in_issues = {}
    starrers = {}

    non_private_issues = []
    for issue, old_owner_id in zip(issues, old_owner_ids):
      # TODO(jrobbins): use issue_id consistently rather than local_id.
      starrers[issue.local_id] = self.services.issue_star.LookupItemStarrers(
          mr.cnxn, issue.issue_id)
      named_ids = set()  # users named in user-value fields that notify.
      for fd in config.field_defs:
        named_ids.update(notify_reasons.ComputeNamedUserIDsToNotify(
            issue.field_values, fd))
      direct, indirect = self.services.usergroup.ExpandAnyUserGroups(
          mr.cnxn, list(issue.cc_ids) + list(issue.derived_cc_ids) +
          [issue.owner_id, old_owner_id, issue.derived_owner_id] +
          list(named_ids))
      ids_in_issues[issue.local_id] = set(starrers[issue.local_id])
      ids_in_issues[issue.local_id].update(direct)
      ids_in_issues[issue.local_id].update(indirect)
      ids_in_issue_needing_views = (
          ids_in_issues[issue.local_id] |
          tracker_bizobj.UsersInvolvedInIssues([issue]))
      new_ids_in_issue = [user_id for user_id in ids_in_issue_needing_views
                          if user_id not in users_by_id]
      users_by_id.update(
          framework_views.MakeAllUserViews(
              mr.cnxn, self.services.user, new_ids_in_issue))

      anon_can_view = permissions.CanViewIssue(
          set(), anon_perms, project, issue)
      if anon_can_view:
        non_private_issues.append(issue)

    commenter_view = users_by_id[commenter_id]
    omit_addrs = {commenter_view.email}

    tasks = []
    if send_email:
      email_tasks = self._BulkEditEmailTasks(
          mr.cnxn, issues, old_owner_ids, omit_addrs, project,
          non_private_issues, users_by_id, ids_in_issues, starrers,
          commenter_view, hostport, comment_text, amendments, config)
      tasks = email_tasks

    notified = notify_helpers.AddAllEmailTasks(tasks)
    return {
        'params': params,
        'notified': notified,
        }
 def testUsersInvolvedInIssues_Empty(self):
   self.assertEqual(set(), tracker_bizobj.UsersInvolvedInIssues([]))
Exemple #15
0
  def GatherPageData(self, mr):
    """Build up a dictionary of data values to use when rendering the page.

    Args:
      mr: commonly used info parsed from the request.

    Returns:
      Dict of values used by EZT for rendering the page.
    """
    if mr.local_id is None:
      self.abort(404, 'no issue specified')
    with work_env.WorkEnv(mr, self.services) as we:
      # Signed in users could edit the issue, so it must be fresh.
      use_cache = not mr.auth.user_id
      issue = we.GetIssueByLocalID(
          mr.project_id, mr.local_id, use_cache=use_cache)

      # We give no explanation of missing issues on the peek page.
      if issue.deleted:
        self.abort(404, 'issue not found')

      star_cnxn = sql.MonorailConnection()
      star_promise = framework_helpers.Promise(
          we.IsIssueStarred, issue, cnxn=star_cnxn)

      config = we.GetProjectConfig(mr.project_id)
      comments = we.ListIssueComments(issue)

    descriptions, visible_comments, cmnt_pagination = PaginateComments(
        mr, issue, comments, config, self.services)

    with mr.profiler.Phase('making user proxies'):
      involved_user_ids = tracker_bizobj.UsersInvolvedInIssues([issue])
      group_ids = self.services.usergroup.DetermineWhichUserIDsAreGroups(
          mr.cnxn, involved_user_ids)
      comment_user_ids = tracker_bizobj.UsersInvolvedInCommentList(
          descriptions + visible_comments)
      users_by_id = framework_views.MakeAllUserViews(
          mr.cnxn, self.services.user, involved_user_ids,
          comment_user_ids, group_ids=group_ids)
      framework_views.RevealAllEmailsToMembers(mr.auth, mr.project, users_by_id)

    (issue_view, description_views,
     comment_views) = self._MakeIssueAndCommentViews(
         mr, issue, users_by_id, descriptions, visible_comments, config,
         issue_reporters=[], comment_reporters=[])

    with mr.profiler.Phase('getting starring info'):
      starred = star_promise.WaitAndGetValue()
      star_cnxn.Close()
      permit_edit = permissions.CanEditIssue(
          mr.auth.effective_ids, mr.perms, mr.project, issue)

    mr.ComputeColSpec(config)
    restrict_to_known = config.restrict_to_known

    page_perms = self.MakePagePerms(
        mr, issue,
        permissions.CREATE_ISSUE,
        permissions.SET_STAR,
        permissions.EDIT_ISSUE,
        permissions.EDIT_ISSUE_SUMMARY,
        permissions.EDIT_ISSUE_STATUS,
        permissions.EDIT_ISSUE_OWNER,
        permissions.EDIT_ISSUE_CC,
        permissions.DELETE_ISSUE,
        permissions.ADD_ISSUE_COMMENT,
        permissions.DELETE_OWN,
        permissions.DELETE_ANY,
        permissions.VIEW_INBOUND_MESSAGES)
    page_perms.EditIssue = ezt.boolean(permit_edit)

    prevent_restriction_removal = (
        mr.project.only_owners_remove_restrictions and
        not framework_bizobj.UserOwnsProject(
            mr.project, mr.auth.effective_ids))

    cmd_slots, default_slot_num = self.services.features.GetRecentCommands(
        mr.cnxn, mr.auth.user_id, mr.project_id)
    cmd_slot_views = [
        template_helpers.EZTItem(
            slot_num=slot_num, command=command, comment=comment)
        for slot_num, command, comment in cmd_slots]

    previous_locations = self.GetPreviousLocations(mr, issue)

    return {
        'issue_tab_mode': 'issueDetail',
        'issue': issue_view,
        'description': description_views,
        'comments': comment_views,
        'labels': issue.labels,
        'num_detail_rows': len(comment_views) + 4,
        'noisy': ezt.boolean(tracker_helpers.IsNoisy(
            len(comment_views), issue.star_count)),

        'cmnt_pagination': cmnt_pagination,
        'colspec': mr.col_spec,
        'searchtip': 'You can jump to any issue by number',
        'starred': ezt.boolean(starred),

        'pagegen': str(int(time.time() * 1000000)),

        'restrict_to_known': ezt.boolean(restrict_to_known),
        'prevent_restriction_removal': ezt.boolean(
            prevent_restriction_removal),

        'statuses_offer_merge': config.statuses_offer_merge,
        'page_perms': page_perms,
        'cmd_slots': cmd_slot_views,
        'default_slot_num': default_slot_num,
        'quick_edit_submit_url': tracker_helpers.FormatRelativeIssueURL(
            issue.project_name, urls.ISSUE_PEEK + '.do', id=issue.local_id),
        'previous_locations': previous_locations,
        # for template issue-meta-part shared by issuedetail servlet
        'user_remaining_hotlists': [],
        'user_issue_hotlists': [],
        'involved_users_issue_hotlists': [],
        'remaining_issue_hotlists': [],
        }
Exemple #16
0
  def HandleRequest(self, mr):
    """Process the task to notify users after an issue 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.
    """
    issue_id = mr.GetPositiveIntParam('issue_id')
    if not issue_id:
      return {
          'params': {},
          'notified': [],
          'message': 'Cannot proceed without a valid issue ID.',
      }
    commenter_id = mr.GetPositiveIntParam('commenter_id')
    seq_num = mr.seq
    omit_ids = [commenter_id]
    hostport = mr.GetParam('hostport')
    old_owner_id = mr.GetPositiveIntParam('old_owner_id')
    send_email = bool(mr.GetIntParam('send_email'))
    comment_id = mr.GetPositiveIntParam('comment_id')
    params = dict(
        issue_id=issue_id, commenter_id=commenter_id,
        seq_num=seq_num, hostport=hostport, old_owner_id=old_owner_id,
        omit_ids=omit_ids, send_email=send_email, comment_id=comment_id)

    logging.info('issue change params are %r', params)
    # TODO(jrobbins): Re-enable the issue cache for notifications after
    # the stale issue defect (monorail:2514) is 100% resolved.
    issue = self.services.issue.GetIssue(mr.cnxn, issue_id, use_cache=False)
    project = self.services.project.GetProject(mr.cnxn, issue.project_id)
    config = self.services.config.GetProjectConfig(mr.cnxn, issue.project_id)

    if issue.is_spam:
      # Don't send email for spam issues.
      return {
          'params': params,
          'notified': [],
      }

    all_comments = self.services.issue.GetCommentsForIssue(
        mr.cnxn, issue.issue_id)
    if comment_id:
      logging.info('Looking up comment by comment_id')
      for c in all_comments:
        if c.id == comment_id:
          comment = c
          logging.info('Comment was found by comment_id')
          break
      else:
        raise ValueError('Comment %r was not found' % comment_id)
    else:
      logging.info('Looking up comment by seq_num')
      comment = all_comments[seq_num]

    # Only issues that any contributor could view sent to mailing lists.
    contributor_could_view = permissions.CanViewIssue(
        set(), permissions.CONTRIBUTOR_ACTIVE_PERMISSIONSET,
        project, issue)
    starrer_ids = self.services.issue_star.LookupItemStarrers(
        mr.cnxn, issue.issue_id)
    users_by_id = framework_views.MakeAllUserViews(
        mr.cnxn, self.services.user,
        tracker_bizobj.UsersInvolvedInIssues([issue]), [old_owner_id],
        tracker_bizobj.UsersInvolvedInComment(comment),
        issue.cc_ids, issue.derived_cc_ids, starrer_ids, omit_ids)

    # Make followup tasks to send emails
    tasks = []
    if send_email:
      tasks = self._MakeEmailTasks(
          mr.cnxn, project, issue, config, old_owner_id, users_by_id,
          all_comments, comment, starrer_ids, contributor_could_view,
          hostport, omit_ids, mr.perms)

    notified = notify_helpers.AddAllEmailTasks(tasks)

    return {
        'params': params,
        'notified': notified,
        }
Exemple #17
0
    def HandleRequest(self, mr):
        """Build up a dictionary of data values to use when rendering the page.

    Args:
      mr: commonly used info parsed from the request.

    Returns:
      Dict of values used by EZT for rendering the page.
    """
        if mr.query or mr.can != 1:
            with work_env.WorkEnv(mr, self.services) as we:
                url_params = []
                pipeline = we.ListIssues(mr.query, [mr.project.project_name],
                                         mr.auth.user_id, mr.num, mr.start,
                                         url_params, mr.can, mr.group_by_spec,
                                         mr.sort_spec, False)
            issues = pipeline.allowed_results
        # no user query and mr.can == 1 (we want all issues)
        elif not mr.start and not mr.num:
            issues = self.services.issue.GetAllIssuesInProject(
                mr.cnxn, mr.project.project_id)
        else:
            local_id_range = list(range(mr.start, mr.start + mr.num))
            issues = self.services.issue.GetIssuesByLocalIDs(
                mr.cnxn, mr.project.project_id, local_id_range)

        user_id_set = tracker_bizobj.UsersInvolvedInIssues(issues)

        comments_dict = self.services.issue.GetCommentsForIssues(
            mr.cnxn, [issue.issue_id for issue in issues])
        for comment_list in comments_dict.values():
            user_id_set.update(
                tracker_bizobj.UsersInvolvedInCommentList(comment_list))

        starrers_dict = self.services.issue_star.LookupItemsStarrers(
            mr.cnxn, [issue.issue_id for issue in issues])
        for starrer_id_list in starrers_dict.values():
            user_id_set.update(starrer_id_list)

        # The value 0 indicates "no user", e.g., that an issue has no owner.
        # We don't need to create a User row to represent that.
        user_id_set.discard(0)
        email_dict = self.services.user.LookupUserEmails(mr.cnxn,
                                                         user_id_set,
                                                         ignore_missed=True)

        issues_json = [
            self._MakeIssueJSON(mr, issue, email_dict,
                                comments_dict.get(issue.issue_id, []),
                                starrers_dict.get(issue.issue_id, []))
            for issue in issues if not issue.deleted
        ]

        json_data = {
            'metadata': {
                'version': 1,
                'when': int(time.time()),
                'who': mr.auth.email,
                'project': mr.project_name,
                'start': mr.start,
                'num': mr.num,
            },
            'issues': issues_json,
            # This list could be derived from the 'issues', but we provide it for
            # ease of processing.
            'emails': list(email_dict.values()),
        }
        return json_data
Exemple #18
0
def GetSortedHotlistIssues(mr, hotlist_issues, issues_list, harmonized_config,
                           profiler, services):
    with profiler.Phase('Checking issue permissions and getting ranks'):

        allowed_issues = FilterIssues(mr, issues_list, services)
        allowed_iids = [issue.issue_id for issue in allowed_issues]
        # The values for issues in a hotlist are specific to the hotlist
        # (rank, adder, added) without invalidating the keys, an issue will retain
        # the rank value it has in one hotlist when navigating to another hotlist.
        sorting.InvalidateArtValuesKeys(
            mr.cnxn, [issue.issue_id for issue in allowed_issues])
        sorted_ranks = sorted([
            hotlist_issue.rank for hotlist_issue in hotlist_issues
            if hotlist_issue.issue_id in allowed_iids
        ])
        friendly_ranks = {
            rank: friendly
            for friendly, rank in enumerate(sorted_ranks, 1)
        }
        issue_adders = framework_views.MakeAllUserViews(
            mr.cnxn, services.user,
            [hotlist_issue.adder_id for hotlist_issue in hotlist_issues])
        hotlist_issues_context = {
            hotlist_issue.issue_id: {
                'issue_rank': friendly_ranks[hotlist_issue.rank],
                'adder_id': hotlist_issue.adder_id,
                'date_added':
                timestr.FormatAbsoluteDate(hotlist_issue.date_added),
                'note': hotlist_issue.note
            }
            for hotlist_issue in hotlist_issues
            if hotlist_issue.issue_id in allowed_iids
        }

    with profiler.Phase('Making user views'):
        issues_users_by_id = framework_views.MakeAllUserViews(
            mr.cnxn, services.user,
            tracker_bizobj.UsersInvolvedInIssues(allowed_issues or []))
        issues_users_by_id.update(issue_adders)

    with profiler.Phase('Sorting issues'):
        sortable_fields = tracker_helpers.SORTABLE_FIELDS.copy()
        sortable_fields.update({
            'rank':
            lambda issue: hotlist_issues_context[issue.issue_id]['issue_rank'],
            'adder':
            lambda issue: hotlist_issues_context[issue.issue_id]['adder_id'],
            'added':
            lambda issue: hotlist_issues_context[issue.issue_id]['date_added'],
            'note':
            lambda issue: hotlist_issues_context[issue.issue_id]['note']
        })
        sortable_postproc = tracker_helpers.SORTABLE_FIELDS_POSTPROCESSORS.copy(
        )
        sortable_postproc.update({
            'adder': lambda user_view: user_view.email,
        })
        if not mr.sort_spec:
            mr.sort_spec = 'rank'
        sorted_issues = sorting.SortArtifacts(mr,
                                              allowed_issues,
                                              harmonized_config,
                                              sortable_fields,
                                              sortable_postproc,
                                              users_by_id=issues_users_by_id,
                                              tie_breakers=['rank', 'id'])
        return sorted_issues, hotlist_issues_context, issues_users_by_id