Exemplo n.º 1
0
    def GatherPageData(self, mr):
        """Parse the attachment ID from the request and serve its content.

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

    Returns:
      Dict of values used by EZT for rendering almost the page.
    """
        with mr.profiler.Phase('get issue, comment, and attachment'):
            try:
                attachment, issue = tracker_helpers.GetAttachmentIfAllowed(
                    mr, self.services)
            except exceptions.NoSuchIssueException:
                webapp2.abort(404, 'issue not found')
            except exceptions.NoSuchAttachmentException:
                webapp2.abort(404, 'attachment not found')
            except exceptions.NoSuchCommentException:
                webapp2.abort(404, 'comment not found')

        content = []
        if attachment.gcs_object_id:
            bucket_name = app_identity.get_default_gcs_bucket_name()
            full_path = '/' + bucket_name + attachment.gcs_object_id
            logging.info("reading gcs: %s" % full_path)
            with cloudstorage.open(full_path, 'r') as f:
                content = f.read()

        filesize = len(content)

        # This servlet only displays safe textual attachments. The user should
        # not have been given a link to this servlet for any other kind.
        if not attachment_helpers.IsViewableText(attachment.mimetype,
                                                 filesize):
            self.abort(400, 'not a text file')

        u_text, is_binary, too_large = filecontent.DecodeFileContents(content)
        lines = prettify.PrepareSourceLinesForHighlighting(
            u_text.encode('utf8'))

        config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
        granted_perms = tracker_bizobj.GetGrantedPerms(issue,
                                                       mr.auth.effective_ids,
                                                       config)
        page_perms = self.MakePagePerms(mr,
                                        issue,
                                        permissions.DELETE_ISSUE,
                                        permissions.CREATE_ISSUE,
                                        granted_perms=granted_perms)

        page_data = {
            'issue_tab_mode': 'issueDetail',
            'local_id': issue.local_id,
            'filename': attachment.filename,
            'filesize': template_helpers.BytesKbOrMb(filesize),
            'file_lines': lines,
            'is_binary': ezt.boolean(is_binary),
            'too_large': ezt.boolean(too_large),
            'code_reviews': None,
            'page_perms': page_perms,
        }
        if is_binary or too_large:
            page_data['should_prettify'] = ezt.boolean(False)
        else:
            page_data.update(
                prettify.BuildPrettifyData(len(lines), attachment.filename))

        return page_data
Exemplo n.º 2
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.
    """
        with self.profiler.Phase('getting issues'):
            if not mr.local_id_list:
                raise monorailrequest.InputException()
            requested_issues = self.services.issue.GetIssuesByLocalIDs(
                mr.cnxn, mr.project_id, sorted(mr.local_id_list))

        with self.profiler.Phase('filtering issues'):
            # TODO(jrobbins): filter out issues that the user cannot edit and
            # provide that as feedback rather than just siliently ignoring them.
            open_issues, closed_issues = (
                tracker_helpers.GetAllowedOpenedAndClosedIssues(
                    mr, [issue.issue_id for issue in requested_issues],
                    self.services))
            issues = open_issues + closed_issues

        if not issues:
            self.abort(404, 'no issues found')

        config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
        type_label_set = {
            lab.lower()
            for lab in issues[0].labels if lab.lower().startswith('type-')
        }
        for issue in issues[1:]:
            new_type_set = {
                lab.lower()
                for lab in issue.labels if lab.lower().startswith('type-')
            }
            type_label_set &= new_type_set

        field_views = [
            tracker_views.MakeFieldValueView(fd, config, type_label_set, [],
                                             [], {})
            # TODO(jrobbins): field-level view restrictions, display options
            # TODO(jrobbins): custom fields in templates supply values to view.
            for fd in config.field_defs if not fd.is_deleted
        ]
        # Explicitly set all field views to not required. We do not want to force
        # users to have to set it for issues missing required fields.
        # See https://bugs.chromium.org/p/monorail/issues/detail?id=500 for more
        # details.
        for fv in field_views:
            fv.field_def.is_required_bool = None

        with self.profiler.Phase('making issue proxies'):
            issue_views = [
                template_helpers.EZTItem(
                    local_id=issue.local_id,
                    summary=issue.summary,
                    closed=ezt.boolean(issue in closed_issues))
                for issue in issues
            ]

        num_seconds = (int(len(issue_views) * self._SECONDS_PER_UPDATE) +
                       self._SECONDS_OVERHEAD)

        page_perms = self.MakePagePerms(mr, None, permissions.CREATE_ISSUE,
                                        permissions.DELETE_ISSUE)

        return {
            'issue_tab_mode': 'issueBulkEdit',
            'issues': issue_views,
            'num_issues': len(issue_views),
            'show_progress':
            ezt.boolean(num_seconds > self._SLOWNESS_THRESHOLD),
            'num_seconds': num_seconds,
            'initial_blocked_on': '',
            'initial_blocking': '',
            'initial_comment': '',
            'initial_status': '',
            'initial_owner': '',
            'initial_merge_into': '',
            'initial_cc': '',
            'initial_components': '',
            'labels': [],
            'fields': field_views,
            'restrict_to_known': ezt.boolean(config.restrict_to_known),
            'page_perms': page_perms,
            'statuses_offer_merge': config.statuses_offer_merge,
        }
Exemplo n.º 3
0
  def GatherBaseData(self, mr, nonce):
    """Return a dict of info used on almost all pages."""
    project = mr.project

    project_summary = ''
    project_alert = None
    project_read_only = False
    project_home_page = ''
    project_thumbnail_url = ''
    if project:
      project_summary = project.summary
      project_alert = _CalcProjectAlert(project)
      project_read_only = project.read_only_reason
      project_home_page = project.home_page
      project_thumbnail_url = tracker_views.LogoView(project).thumbnail_url

    with work_env.WorkEnv(mr, self.services) as we:
      is_project_starred = False
      project_view = None
      if mr.project:
        if permissions.UserCanViewProject(
            mr.auth.user_pb, mr.auth.effective_ids, mr.project):
          is_project_starred = we.IsProjectStarred(mr.project_id)
          # TODO(jrobbins): should this be a ProjectView?
          project_view = template_helpers.PBProxy(mr.project)

    grid_x_attr = None
    grid_y_attr = None
    hotlist_view = None
    if mr.hotlist:
      users_by_id = framework_views.MakeAllUserViews(
          mr.cnxn, self.services.user,
          features_bizobj.UsersInvolvedInHotlists([mr.hotlist]))
      hotlist_view = hotlist_views.HotlistView(
          mr.hotlist, mr.perms, mr.auth, mr.viewed_user_auth.user_id,
          users_by_id, self.services.hotlist_star.IsItemStarredBy(
            mr.cnxn, mr.hotlist.hotlist_id, mr.auth.user_id))
      grid_x_attr = mr.x.lower()
      grid_y_attr = mr.y.lower()

    app_version = os.environ.get('CURRENT_VERSION_ID')

    viewed_username = None
    if mr.viewed_user_auth.user_view:
      viewed_username = mr.viewed_user_auth.user_view.username

    issue_entry_url = 'entry'
    config = None
    if mr.project_id and self.services.config:
      with mr.profiler.Phase('getting config'):
        config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
      grid_x_attr = (mr.x or config.default_x_attr).lower()
      grid_y_attr = (mr.y or config.default_y_attr).lower()
      issue_entry_url = _LoginOrIssueEntryURL(mr, config)

    viewing_self = mr.auth.user_id == mr.viewed_user_auth.user_id
    offer_saved_queries_subtab = (
        viewing_self or mr.auth.user_pb and mr.auth.user_pb.is_site_admin)

    login_url = _SafeCreateLoginURL(mr)
    logout_url = _SafeCreateLogoutURL(mr)
    logout_url_goto_home = users.create_logout_url('/')
    version_base = _VersionBaseURL(mr.request)

    base_data = {
        # EZT does not have constants for True and False, so we pass them in.
        'True': ezt.boolean(True),
        'False': ezt.boolean(False),

        'local_mode': ezt.boolean(settings.local_mode),

        'site_name': settings.site_name,
        'show_search_metadata': ezt.boolean(False),
        'page_template': self._PAGE_TEMPLATE,
        'main_tab_mode': self._MAIN_TAB_MODE,
        'project_summary': project_summary,
        'project_home_page': project_home_page,
        'project_thumbnail_url': project_thumbnail_url,

        'hotlist_id': mr.hotlist_id,
        'hotlist': hotlist_view,

        'hostport': mr.request.host,
        'absolute_base_url': '%s://%s' % (mr.request.scheme, mr.request.host),
        'project_home_url': None,
        'link_rel_canonical': None,  # For specifying <link rel="canonical">
        'projectname': mr.project_name,
        'project': project_view,
        'project_is_restricted': ezt.boolean(_ProjectIsRestricted(mr)),
        'offer_contributor_list': ezt.boolean(
            permissions.CanViewContributorList(mr, mr.project)),
        'logged_in_user': mr.auth.user_view,
        'form_token': None,  # Set to a value below iff the user is logged in.
        'form_token_path': None,
        'token_expires_sec': None,
        'xhr_token': None,  # Set to a value below iff the user is logged in.
        'flag_spam_token': None,
        'nonce': nonce,
        'perms': mr.perms,
        'warnings': mr.warnings,
        'errors': mr.errors,

        'viewed_username': viewed_username,
        'viewed_user': mr.viewed_user_auth.user_view,
        'viewed_user_pb': template_helpers.PBProxy(
            mr.viewed_user_auth.user_pb),
        'viewing_self': ezt.boolean(viewing_self),
        'viewed_user_id': mr.viewed_user_auth.user_id,
        'offer_saved_queries_subtab': ezt.boolean(offer_saved_queries_subtab),

        'currentPageURL': mr.current_page_url,
        'currentPageURLEncoded': mr.current_page_url_encoded,
        'login_url': login_url,
        'logout_url': logout_url,
        'logout_url_goto_home': logout_url_goto_home,
        'continue_issue_id': mr.continue_issue_id,
        'feedback_email': settings.feedback_email,
        'category_css': None,  # Used to specify a category of stylesheet
        'category2_css': None, # specify a 2nd category of stylesheet if needed.
        'page_css': None,  # Used to add a stylesheet to a specific page.

        'can': mr.can,
        'query': mr.query,
        'colspec': None,
        'sortspec': mr.sort_spec,

        # Options for issuelist display
        'grid_x_attr': grid_x_attr,
        'grid_y_attr': grid_y_attr,
        'grid_cell_mode': mr.cells,
        'grid_mode': None,
        'list_mode': None,
        'chart_mode': None,

        'issue_entry_url': issue_entry_url,
        'is_cross_project': ezt.boolean(False),

        # for project search (some also used in issue search)
        'start': mr.start,
        'num': mr.num,
        'groupby': mr.group_by_spec,
        'q_field_size': (
            min(framework_constants.MAX_ARTIFACT_SEARCH_FIELD_SIZE,
                max(framework_constants.MIN_ARTIFACT_SEARCH_FIELD_SIZE,
                    len(mr.query) + framework_constants.AUTOSIZE_STEP))),
        'mode': None,  # Display mode, e.g., grid mode.
        'ajah': mr.ajah,
        'table_title': mr.table_title,

        'alerts': alerts.AlertsView(mr),  # For alert.ezt
        'project_alert': project_alert,

        'title': None,  # First part of page title
        'title_summary': None,  # Appended to title on artifact detail pages

        # TODO(jrobbins): make sure that the templates use
        # project_read_only for project-mutative actions and if any
        # uses of read_only remain.
        'project_read_only': ezt.boolean(project_read_only),
        'site_read_only': ezt.boolean(settings.read_only),
        'banner_time': servlet_helpers.GetBannerTime(settings.banner_time),
        'read_only': ezt.boolean(settings.read_only or project_read_only),
        'site_banner_message': settings.banner_message,
        'robots_no_index': None,
        'analytics_id': settings.analytics_id,

        'is_project_starred': ezt.boolean(is_project_starred),

        'version_base': version_base,
        'app_version': app_version,
        'viewing_user_page': ezt.boolean(False),
        'other_ui_path': None,

        'is_member': ezt.boolean(False),
        }

    if mr.project:
      base_data['project_home_url'] = '/p/%s' % mr.project_name

    # Always add xhr-xsrf token because even anon users need some
    # pRPC methods, e.g., autocomplete, flipper, and charts.
    base_data['token_expires_sec'] = xsrf.TokenExpiresSec()
    base_data['xhr_token'] = xsrf.GenerateToken(
        mr.auth.user_id, xsrf.XHR_SERVLET_PATH)
    # Always add other anti-xsrf tokens when the user is logged in.
    if mr.auth.user_id:
      form_token_path = self._FormHandlerURL(mr.request.path)
      base_data['form_token'] = xsrf.GenerateToken(
        mr.auth.user_id, form_token_path)
      base_data['form_token_path'] = form_token_path

    return base_data
Exemplo n.º 4
0
  def __init__(self, mr, template, user_service, config):
    super(IssueTemplateView, self).__init__(template)

    self.ownername = ''
    try:
      self.owner_view = framework_views.MakeUserView(
          mr.cnxn, user_service, template.owner_id)
    except user_svc.NoSuchUserException:
      self.owner_view = None
    if self.owner_view:
      self.ownername = self.owner_view.email

    self.admin_views = framework_views.MakeAllUserViews(
        mr.cnxn, user_service, template.admin_ids).values()
    self.admin_names = ', '.join(sorted([
        admin_view.email for admin_view in self.admin_views]))

    self.summary_must_be_edited = ezt.boolean(template.summary_must_be_edited)
    self.members_only = ezt.boolean(template.members_only)
    self.owner_defaults_to_member = ezt.boolean(
        template.owner_defaults_to_member)
    self.component_required = ezt.boolean(template.component_required)

    component_paths = []
    for component_id in template.component_ids:
      component_paths.append(
          tracker_bizobj.FindComponentDefByID(component_id, config).path)
    self.components = ', '.join(component_paths)

    self.can_view = ezt.boolean(permissions.CanViewTemplate(
        mr.auth.effective_ids, mr.perms, mr.project, template))
    self.can_edit = ezt.boolean(permissions.CanEditTemplate(
        mr.auth.effective_ids, mr.perms, mr.project, template))

    field_name_set = {fd.field_name.lower() for fd in config.field_defs
                      if not fd.is_deleted}  # TODO(jrobbins): restrictions
    non_masked_labels = [
        lab for lab in template.labels
        if not tracker_bizobj.LabelIsMaskedByField(lab, field_name_set)]

    for i, label in enumerate(non_masked_labels):
      setattr(self, 'label%d' % i, label)
    for i in range(len(non_masked_labels), framework_constants.MAX_LABELS):
      setattr(self, 'label%d' % i, '')

    field_user_views = MakeFieldUserViews(mr.cnxn, template, user_service)
    self.field_values = []
    for fv in template.field_values:
      self.field_values.append(template_helpers.EZTItem(
          field_id=fv.field_id,
          val=tracker_bizobj.GetFieldValue(fv, field_user_views),
          idx=len(self.field_values)))

    self.complete_field_values = [
        MakeFieldValueView(
            fd, config, template.labels, [], template.field_values,
            field_user_views)
        # TODO(jrobbins): field-level view restrictions, display options
        for fd in config.field_defs
        if not fd.is_deleted]

    # Templates only display and edit the first value of multi-valued fields, so
    # expose a single value, if any.
    # TODO(jrobbins): Fully support multi-valued fields in templates.
    for idx, field_value_view in enumerate(self.complete_field_values):
      field_value_view.idx = idx
      if field_value_view.values:
        field_value_view.val = field_value_view.values[0].val
      else:
        field_value_view.val = None
Exemplo n.º 5
0
 def __getattr__(self, perm_name):
     """Easy permission testing in EZT.  E.g., [if-any perms.format_drive]."""
     return ezt.boolean(self.HasPerm(perm_name, None, None))
Exemplo n.º 6
0
 def testGenericLanguage(self):
     prettify_data = prettify.BuildPrettifyData(123, 'trunk/src/hello.php')
     self.assertDictEqual(
         dict(should_prettify=ezt.boolean(True), prettify_class=''),
         prettify_data)
Exemplo n.º 7
0
 def testExactFilename(self):
     prettify_data = prettify.BuildPrettifyData(123, 'trunk/src/Makefile')
     self.assertDictEqual(
         dict(should_prettify=ezt.boolean(True), prettify_class='lang-sh'),
         prettify_data)
Exemplo n.º 8
0
    def __init__(self,
                 pb,
                 services,
                 mr,
                 prefetched_issues,
                 users_by_id,
                 autolink=None,
                 all_ref_artifacts=None,
                 ending=None,
                 highlight=None):
        """Constructs an ActivityView out of an Activity protocol buffer.

    Args:
      pb: an IssueComment or Activity protocol buffer.
      services: connections to backend services.
      mr: HTTP request info, used by the artifact autolink.
      prefetched_issues: dictionary of the issues for the comments being shown.
      users_by_id: dict {user_id: UserView} for all relevant users.
      autolink: Autolink instance.
      all_ref_artifacts: list of all artifacts in the activity stream.
      ending: ending type for activity titles, 'in_project' or 'by_user'
      highlight: what to highlight in the middle column on user updates pages
          i.e. 'project', 'user', or None
    """
        template_helpers.PBProxy.__init__(self, pb)

        activity_type = 'ProjectIssueUpdate'  # TODO(jrobbins): more types

        self.comment = None
        self.issue = None
        self.field_changed = None
        self.multiple_fields_changed = ezt.boolean(False)
        self.project = None
        self.user = None
        self.timestamp = time.time(
        )  # Bogus value makes bad ones highly visible.

        if isinstance(pb, tracker_pb2.IssueComment):
            self.timestamp = pb.timestamp
            issue = prefetched_issues[pb.issue_id]
            if self.timestamp == issue.opened_timestamp:
                issue_change_id = None  # This comment is the description.
            else:
                issue_change_id = pb.timestamp  # instead of seq num.

            self.comment = tracker_views.IssueCommentView(
                mr.project_name, pb, users_by_id, autolink, all_ref_artifacts,
                mr, issue)

            # TODO(jrobbins): pass effective_ids of the commenter so that he/she
            # can be identified as a project member or not.
            # TODO(jrobbins): Prefetch all needed projects and configs just like the
            # way that we batch-prefetch issues.
            config = services.config.GetProjectConfig(mr.cnxn,
                                                      issue.project_id)
            self.issue = tracker_views.IssueView(issue, users_by_id, config)
            self.user = self.comment.creator
            project = services.project.GetProject(mr.cnxn, issue.project_id)
            self.project_name = project.project_name
            self.project = project_views.ProjectView(project)

        else:
            logging.warn('unknown activity object %r', pb)

        nested_page_data = {
            'activity_type': activity_type,
            'issue_change_id': issue_change_id,
            'comment': self.comment,
            'issue': self.issue,
            'project': self.project,
            'user': self.user,
            'timestamp': self.timestamp,
            'ending_type': ending,
        }

        self.escaped_title = self._TITLE_TEMPLATE.GetResponse(
            nested_page_data).strip()
        self.escaped_body = self._BODY_TEMPLATE.GetResponse(
            nested_page_data).strip()

        if autolink is not None and all_ref_artifacts is not None:
            # TODO(jrobbins): actually parse the comment text.  Actually render runs.
            runs = autolink.MarkupAutolinks(
                mr, [template_helpers.TextRun(self.escaped_body)],
                all_ref_artifacts)
            self.escaped_body = ''.join(run.content for run in runs)

        self.date_bucket, self.date_relative = timestr.GetHumanScaleDate(
            self.timestamp)
        time_tuple = time.localtime(self.timestamp)
        self.date_tooltip = time.asctime(time_tuple)

        # We always highlight the user for starring activities
        if activity_type.startswith('UserStar'):
            self.highlight = 'user'
        else:
            self.highlight = highlight
Exemplo n.º 9
0
  def ProcessFormData(self, mr, post_data):
    """Process the issue entry form.

    Args:
      mr: commonly used info parsed from the request.
      post_data: The post_data dict for the current request.

    Returns:
      String URL to redirect the user to after processing.
    """
    config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
    parsed = tracker_helpers.ParseIssueRequest(
        mr.cnxn, post_data, self.services, mr.errors, mr.project_name)
    bounce_labels = parsed.labels[:]
    bounce_fields = tracker_views.MakeBounceFieldValueViews(
        parsed.fields.vals, config)
    field_helpers.ShiftEnumFieldsIntoLabels(
        parsed.labels, parsed.labels_remove, parsed.fields.vals,
        parsed.fields.vals_remove, config)
    field_values = field_helpers.ParseFieldValues(
        mr.cnxn, self.services.user, parsed.fields.vals, config)

    labels = _DiscardUnusedTemplateLabelPrefixes(parsed.labels)
    component_ids = tracker_helpers.LookupComponentIDs(
        parsed.components.paths, config, mr.errors)

    reporter_id = mr.auth.user_id
    self.CheckCaptcha(mr, post_data)

    if not parsed.summary.strip() or parsed.summary == PLACEHOLDER_SUMMARY:
      mr.errors.summary = 'Summary is required'

    if not parsed.comment.strip():
      mr.errors.comment = 'A description is required'

    if len(parsed.comment) > tracker_constants.MAX_COMMENT_CHARS:
      mr.errors.comment = 'Comment is too long'
    if len(parsed.summary) > tracker_constants.MAX_SUMMARY_CHARS:
      mr.errors.summary = 'Summary is too long'

    if _MatchesTemplate(parsed.comment, config):
      mr.errors.comment = 'Template must be filled out.'

    if parsed.users.owner_id is None:
      mr.errors.owner = 'Invalid owner username'
    else:
      valid, msg = tracker_helpers.IsValidIssueOwner(
          mr.cnxn, mr.project, parsed.users.owner_id, self.services)
      if not valid:
        mr.errors.owner = msg

    if None in parsed.users.cc_ids:
      mr.errors.cc = 'Invalid Cc username'

    field_helpers.ValidateCustomFields(
        mr, self.services, field_values, config, mr.errors)

    new_local_id = None

    if not mr.errors.AnyErrors():
      try:
        if parsed.attachments:
          new_bytes_used = tracker_helpers.ComputeNewQuotaBytesUsed(
              mr.project, parsed.attachments)
          self.services.project.UpdateProject(
              mr.cnxn, mr.project.project_id,
              attachment_bytes_used=new_bytes_used)

        template_content = ''
        for wkp in config.templates:
          if wkp.name == parsed.template_name:
            template_content = wkp.content
        marked_comment = _MarkupDescriptionOnInput(
            parsed.comment, template_content)
        has_star = 'star' in post_data and post_data['star'] == '1'

        new_local_id = self.services.issue.CreateIssue(
            mr.cnxn, self.services,
            mr.project_id, parsed.summary, parsed.status, parsed.users.owner_id,
            parsed.users.cc_ids, labels, field_values,
            component_ids, reporter_id, marked_comment,
            blocked_on=parsed.blocked_on.iids, blocking=parsed.blocking.iids,
            attachments=parsed.attachments)
        self.services.project.UpdateRecentActivity(
            mr.cnxn, mr.project.project_id)

        issue = self.services.issue.GetIssueByLocalID(
            mr.cnxn, mr.project_id, new_local_id)

        if has_star:
          self.services.issue_star.SetStar(
              mr.cnxn, self.services, config, issue.issue_id, reporter_id, True)

      except tracker_helpers.OverAttachmentQuota:
        mr.errors.attachments = 'Project attachment quota exceeded.'

      counts = {actionlimit.ISSUE_COMMENT: 1,
                actionlimit.ISSUE_ATTACHMENT: len(parsed.attachments)}
      self.CountRateLimitedActions(mr, counts)

    if mr.errors.AnyErrors():
      component_required = False
      for wkp in config.templates:
        if wkp.name == parsed.template_name:
          component_required = wkp.component_required
      self.PleaseCorrect(
          mr, initial_summary=parsed.summary, initial_status=parsed.status,
          initial_owner=parsed.users.owner_username,
          initial_cc=', '.join(parsed.users.cc_usernames),
          initial_components=', '.join(parsed.components.paths),
          initial_comment=parsed.comment, labels=bounce_labels,
          fields=bounce_fields,
          initial_blocked_on=parsed.blocked_on.entered_str,
          initial_blocking=parsed.blocking.entered_str,
          component_required=ezt.boolean(component_required))
      return

    # Initial description is comment 0.
    notify.PrepareAndSendIssueChangeNotification(
        issue.issue_id, mr.request.host, reporter_id, 0)

    notify.PrepareAndSendIssueBlockingNotification(
        issue.issue_id, mr.request.host, parsed.blocked_on.iids, reporter_id)

    # format a redirect url
    return framework_helpers.FormatAbsoluteURL(
        mr, urls.ISSUE_DETAIL, id=new_local_id)
Exemplo n.º 10
0
  def __init__(self, mr, services, config, template=None,
               load_all_templates=False):
    """Gather data for the issue section of a project admin page.

    Args:
      mr: MonorailRequest, including a database connection, the current
          project, and authenticated user IDs.
      services: Persist services with ProjectService, ConfigService,
          TemplateService and UserService included.
      config: ProjectIssueConfig for the current project..
      template (TemplateDef, optional): the current template.
      load_all_templates (boolean): default False. If true loads self.templates.

    Returns:
      Project info in a dict suitable for EZT.
    """
    super(ConfigView, self).__init__(config)
    self.open_statuses = []
    self.closed_statuses = []
    for wks in config.well_known_statuses:
      item = template_helpers.EZTItem(
          name=wks.status,
          name_padded=wks.status.ljust(20),
          commented='#' if wks.deprecated else '',
          docstring=wks.status_docstring)
      if tracker_helpers.MeansOpenInProject(wks.status, config):
        self.open_statuses.append(item)
      else:
        self.closed_statuses.append(item)

    is_member = framework_bizobj.UserIsInProject(
        mr.project, mr.auth.effective_ids)
    template_set = services.template.GetTemplateSetForProject(mr.cnxn,
        config.project_id)

    # Filter non-viewable templates
    self.template_names = []
    for _, template_name, members_only in template_set:
      if members_only and not is_member:
        continue
      self.template_names.append(template_name)

    if load_all_templates:
      templates = services.template.GetProjectTemplates(mr.cnxn,
          config.project_id)
      self.templates = [
          IssueTemplateView(mr, tmpl, services.user, config)
          for tmpl in templates]
      for index, template_view in enumerate(self.templates):
        template_view.index = index

    if template:
      self.template_view = IssueTemplateView(mr, template, services.user,
          config)

    self.field_names = [  # TODO(jrobbins): field-level controls
        fd.field_name for fd in config.field_defs if
        fd.field_type is tracker_pb2.FieldTypes.ENUM_TYPE and
        not fd.is_deleted]
    self.issue_labels = tracker_helpers.LabelsNotMaskedByFields(
        config, self.field_names)
    self.excl_prefixes = [
        prefix.lower() for prefix in config.exclusive_label_prefixes]
    self.restrict_to_known = ezt.boolean(config.restrict_to_known)

    self.default_col_spec = (
        config.default_col_spec or tracker_constants.DEFAULT_COL_SPEC)
Exemplo n.º 11
0
def GatherUpdatesData(services,
                      mr,
                      prof,
                      project_ids=None,
                      user_ids=None,
                      ending=None,
                      updates_page_url=None,
                      autolink=None,
                      highlight=None):
    """Gathers and returns updates data.

  Args:
    services: Connections to backend services.
    mr: HTTP request info, used by the artifact autolink.
    prof: The profiler to use.
    project_ids: List of project IDs we want updates for.
    user_ids: List of user IDs we want updates for.
    ending: Ending type for activity titles, 'in_project' or 'by_user'.
    updates_page_url: The URL that will be used to create pagination links from.
    autolink: Autolink instance.
    highlight: What to highlight in the middle column on user updates pages
        i.e. 'project', 'user', or None.
  """
    ascending = bool(mr.after)

    # num should be non-negative number
    num = mr.GetPositiveIntParam('num', UPDATES_PER_PAGE)
    num = min(num, MAX_UPDATES_PER_PAGE)

    updates_data = {
        'no_stars': None,
        'no_activities': None,
        'pagination': None,
        'updates_data': None,
        'ending_type': ending,
    }

    if not user_ids and not project_ids:
        updates_data['no_stars'] = ezt.boolean(True)
        return updates_data

    with prof.Phase('get activities'):
        # TODO(jrobbins): make this into a persist method.
        # TODO(jrobbins): this really needs permission checking in SQL, which will
        # be slow.
        where_conds = [('Issue.id = Comment.issue_id', [])]
        if project_ids is not None:
            cond_str = 'Comment.project_id IN (%s)' % sql.PlaceHolders(
                project_ids)
            where_conds.append((cond_str, project_ids))
        if user_ids is not None:
            cond_str = 'Comment.commenter_id IN (%s)' % sql.PlaceHolders(
                user_ids)
            where_conds.append((cond_str, user_ids))

        if project_ids:
            use_clause = 'USE INDEX (project_id) USE INDEX FOR ORDER BY (project_id)'
        elif user_ids:
            use_clause = (
                'USE INDEX (commenter_id) USE INDEX FOR ORDER BY (commenter_id)'
            )
        else:
            use_clause = ''

        if mr.before:
            where_conds.append(('created < %s', [mr.before]))
        if mr.after:
            where_conds.append(('created > %s', [mr.after]))
        if ascending:
            order_by = [('created', [])]
        else:
            order_by = [('created DESC', [])]

        comments = services.issue.GetComments(mr.cnxn,
                                              joins=[('Issue', [])],
                                              deleted_by=None,
                                              where=where_conds,
                                              use_clause=use_clause,
                                              order_by=order_by,
                                              limit=num + 1)

        # TODO(jrobbins): it would be better if we could just get the dict directly.
        prefetched_issues_list = services.issue.GetIssues(
            mr.cnxn, {c.issue_id
                      for c in comments})
        prefetched_issues = {
            issue.issue_id: issue
            for issue in prefetched_issues_list
        }
        needed_project_ids = {
            issue.project_id
            for issue in prefetched_issues_list
        }
        prefetched_projects = services.project.GetProjects(
            mr.cnxn, needed_project_ids)
        prefetched_configs = services.config.GetProjectConfigs(
            mr.cnxn, needed_project_ids)
        viewable_issues_list = tracker_helpers.FilterOutNonViewableIssues(
            mr.auth.effective_ids, mr.auth.user_pb, prefetched_projects,
            prefetched_configs, prefetched_issues_list)
        viewable_iids = {issue.issue_id for issue in viewable_issues_list}

        # Filter the comments based on permission to view the issue.
        # TODO(jrobbins): push permission checking in the query so that pagination
        # pages never become underfilled, or use backends to shard.
        # TODO(jrobbins): come back to this when I implement private comments.
        comments = [c for c in comments if c.issue_id in viewable_iids]

        if ascending:
            comments.reverse()

    amendment_user_ids = []
    for comment in comments:
        for amendment in comment.amendments:
            amendment_user_ids.extend(amendment.added_user_ids)
            amendment_user_ids.extend(amendment.removed_user_ids)

    users_by_id = framework_views.MakeAllUserViews(
        mr.cnxn, services.user, [c.user_id for c in comments],
        amendment_user_ids)
    framework_views.RevealAllEmailsToMembers(mr, users_by_id)

    num_results_returned = len(comments)
    displayed_activities = comments[:UPDATES_PER_PAGE]

    if not num_results_returned:
        updates_data['no_activities'] = ezt.boolean(True)
        return updates_data

    # Get all referenced artifacts first
    all_ref_artifacts = None
    if autolink is not None:
        content_list = []
        for activity in comments:
            content_list.append(activity.content)

        all_ref_artifacts = autolink.GetAllReferencedArtifacts(
            mr, content_list)

    # Now process content and gather activities
    today = []
    yesterday = []
    pastweek = []
    pastmonth = []
    thisyear = []
    older = []

    with prof.Phase('rendering activities'):
        for activity in displayed_activities:
            entry = ActivityView(activity,
                                 services,
                                 mr,
                                 prefetched_issues,
                                 users_by_id,
                                 autolink=autolink,
                                 all_ref_artifacts=all_ref_artifacts,
                                 ending=ending,
                                 highlight=highlight)

            if entry.date_bucket == 'Today':
                today.append(entry)
            elif entry.date_bucket == 'Yesterday':
                yesterday.append(entry)
            elif entry.date_bucket == 'Last 7 days':
                pastweek.append(entry)
            elif entry.date_bucket == 'Last 30 days':
                pastmonth.append(entry)
            elif entry.date_bucket == 'Earlier this year':
                thisyear.append(entry)
            elif entry.date_bucket == 'Before this year':
                older.append(entry)

    new_after = None
    new_before = None
    if displayed_activities:
        new_after = displayed_activities[0].timestamp
        new_before = displayed_activities[-1].timestamp

    prev_url = None
    next_url = None
    if updates_page_url:
        list_servlet_rel_url = updates_page_url.split('/')[-1]
        if displayed_activities and (mr.before or mr.after):
            prev_url = framework_helpers.FormatURL(mr,
                                                   list_servlet_rel_url,
                                                   after=new_after)
        if mr.after or len(comments) > UPDATES_PER_PAGE:
            next_url = framework_helpers.FormatURL(mr,
                                                   list_servlet_rel_url,
                                                   before=new_before)

    if prev_url or next_url:
        pagination = template_helpers.EZTItem(start=None,
                                              last=None,
                                              prev_url=prev_url,
                                              next_url=next_url,
                                              reload_url=None,
                                              visible=ezt.boolean(True),
                                              total_count=None)
    else:
        pagination = None

    updates_data.update({
        'no_activities':
        ezt.boolean(False),
        'pagination':
        pagination,
        'updates_data':
        template_helpers.EZTItem(today=today,
                                 yesterday=yesterday,
                                 pastweek=pastweek,
                                 pastmonth=pastmonth,
                                 thisyear=thisyear,
                                 older=older),
    })

    return updates_data
Exemplo n.º 12
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)
Exemplo n.º 13
0
    def GetTableViewData(self, mr, results, config, users_by_id,
                         starred_iid_set, related_issues):
        """EZT template values to render a Table View of issues.

    Args:
      mr: commonly used info parsed from the request.
      results: list of Issue PBs for the search results to be displayed.
      config: The ProjectIssueConfig PB for the current project.
      users_by_id: A dictionary {user_id: UserView} for all the users
      involved in results.
      starred_iid_set: Set of issues that the user has starred.
      related_issues: dict {issue_id: issue} of pre-fetched related issues.

    Returns:
      Dictionary of page data for rendering of the Table View.
    """
        # We need ordered_columns because EZT loops have no loop-counter available.
        # And, we use column number in the Javascript to hide/show columns.
        columns = mr.col_spec.split()
        ordered_columns = [
            template_helpers.EZTItem(col_index=i, name=col)
            for i, col in enumerate(columns)
        ]
        unshown_columns = table_view_helpers.ComputeUnshownColumns(
            results, columns, config, tracker_constants.OTHER_BUILT_IN_COLS)

        lower_columns = mr.col_spec.lower().split()
        lower_group_by = mr.group_by_spec.lower().split()
        table_data = _MakeTableData(results, starred_iid_set, lower_columns,
                                    lower_group_by, users_by_id,
                                    self.GetCellFactories(), related_issues,
                                    config)

        # Used to offer easy filtering of each unique value in each column.
        column_values = table_view_helpers.ExtractUniqueValues(
            lower_columns, results, users_by_id, config, related_issues)

        table_view_data = {
            'table_data':
            table_data,
            'column_values':
            column_values,
            # Put ordered_columns inside a list of exactly 1 panel so that
            # it can work the same as the dashboard initial panel list headers.
            'panels':
            [template_helpers.EZTItem(ordered_columns=ordered_columns)],
            'unshown_columns':
            unshown_columns,
            'cursor':
            mr.cursor or mr.preview,
            'preview':
            mr.preview,
            'default_colspec':
            tracker_constants.DEFAULT_COL_SPEC,
            'default_results_per_page':
            tracker_constants.DEFAULT_RESULTS_PER_PAGE,
            'csv_link':
            framework_helpers.FormatURL(
                [(name, mr.GetParam(name))
                 for name in framework_helpers.RECOGNIZED_PARAMS],
                'csv',
                num=settings.max_artifact_search_results_per_page),
            'preview_on_hover':
            ezt.boolean(_ShouldPreviewOnHover(mr.auth.user_pb)),
        }
        return table_view_data
Exemplo n.º 14
0
    def _MakeEmailTasks(self, cnxn, issue, project, config, comment,
                        starrer_ids, hostport, users_by_id, pings):
        """Return a list of dicts for tasks to notify people."""
        detail_url = framework_helpers.IssueCommentURL(
            hostport, project, issue.local_id, seq_num=comment.sequence)
        fields = sorted((field_def for (field_def, _date_value) in pings),
                        key=lambda fd: fd.field_name)
        email_data = {
            'issue': tracker_views.IssueView(issue, users_by_id, config),
            'summary': issue.summary,
            'ping_comment_content': comment.content,
            'detail_url': detail_url,
            'fields': fields,
        }

        # Generate three versions of email body with progressively more info.
        body_link_only = self.link_only_email_template.GetResponse({
            'detail_url':
            detail_url,
            'was_created':
            ezt.boolean(False)
        })
        body_for_non_members = self.email_template.GetResponse(email_data)
        framework_views.RevealAllEmails(users_by_id)
        body_for_members = self.email_template.GetResponse(email_data)
        logging.info('body for non-members is:\n%r' % body_for_non_members)
        logging.info('body for members is:\n%r' % body_for_members)

        contributor_could_view = permissions.CanViewIssue(
            set(), permissions.CONTRIBUTOR_ACTIVE_PERMISSIONSET, project,
            issue)

        group_reason_list = notify_reasons.ComputeGroupReasonList(
            cnxn,
            self.services,
            project,
            issue,
            config,
            users_by_id, [],
            contributor_could_view,
            starrer_ids=starrer_ids,
            commenter_in_project=True,
            include_subscribers=False,
            include_notify_all=False,
            starrer_pref_check_function=lambda u: u.notify_starred_ping)

        commenter_view = users_by_id[comment.user_id]
        email_tasks = notify_helpers.MakeBulletedEmailWorkItems(
            group_reason_list,
            issue,
            body_link_only,
            body_for_non_members,
            body_for_members,
            project,
            hostport,
            commenter_view,
            detail_url,
            seq_num=comment.sequence,
            subject_prefix='Follow up on issue ',
            compact_subject_prefix='Follow up ')

        return email_tasks
Exemplo n.º 15
0
    def GatherUnifiedSettingsPageData(cls, logged_in_user_id,
                                      settings_user_view, settings_user):
        """Gather EZT variables needed for the unified user settings form.

    Args:
      logged_in_user_id: The user ID of the acting user.
      settings_user_view: The UserView of the target user.
      settings_user: The User PB of the target user.

    Returns:
      A dictionary giving the names and values of all the variables to
      be exported to EZT to support the unified user settings form template.
    """
        def ActionLastReset(action_limit):
            """Return a formatted time string for the last action limit reset."""
            if action_limit:
                return time.asctime(
                    time.localtime(action_limit.reset_timestamp))
            return 'Never'

        def DefaultLifetimeLimit(action_type):
            """Return the deault lifetime limit for the give type of action."""
            return actionlimit.ACTION_LIMITS[action_type][3]

        def DefaultPeriodSoftLimit(action_type):
            """Return the deault period soft limit for the give type of action."""
            return actionlimit.ACTION_LIMITS[action_type][1]

        def DefaultPeriodHardLimit(action_type):
            """Return the deault period jard limit for the give type of action."""
            return actionlimit.ACTION_LIMITS[action_type][2]

        project_creation_lifetime_limit = (
            (settings_user.project_creation_limit
             and settings_user.project_creation_limit.lifetime_limit)
            or DefaultLifetimeLimit(actionlimit.PROJECT_CREATION))
        project_creation_soft_limit = (
            (settings_user.project_creation_limit
             and settings_user.project_creation_limit.period_soft_limit)
            or DefaultPeriodSoftLimit(actionlimit.PROJECT_CREATION))
        project_creation_hard_limit = (
            (settings_user.project_creation_limit
             and settings_user.project_creation_limit.period_hard_limit)
            or DefaultPeriodHardLimit(actionlimit.PROJECT_CREATION))
        issue_comment_lifetime_limit = (
            (settings_user.issue_comment_limit
             and settings_user.issue_comment_limit.lifetime_limit)
            or DefaultLifetimeLimit(actionlimit.ISSUE_COMMENT))
        issue_comment_soft_limit = (
            (settings_user.issue_comment_limit
             and settings_user.issue_comment_limit.period_soft_limit)
            or DefaultPeriodSoftLimit(actionlimit.ISSUE_COMMENT))
        issue_comment_hard_limit = (
            (settings_user.issue_comment_limit
             and settings_user.issue_comment_limit.period_hard_limit)
            or DefaultPeriodHardLimit(actionlimit.ISSUE_COMMENT))
        issue_attachment_lifetime_limit = (
            (settings_user.issue_attachment_limit
             and settings_user.issue_attachment_limit.lifetime_limit)
            or DefaultLifetimeLimit(actionlimit.ISSUE_ATTACHMENT))
        issue_attachment_soft_limit = (
            (settings_user.issue_attachment_limit
             and settings_user.issue_attachment_limit.period_soft_limit)
            or DefaultPeriodSoftLimit(actionlimit.ISSUE_ATTACHMENT))
        issue_attachment_hard_limit = (
            (settings_user.issue_attachment_limit
             and settings_user.issue_attachment_limit.period_hard_limit)
            or DefaultPeriodHardLimit(actionlimit.ISSUE_ATTACHMENT))
        issue_bulk_edit_lifetime_limit = (
            (settings_user.issue_bulk_edit_limit
             and settings_user.issue_bulk_edit_limit.lifetime_limit)
            or DefaultLifetimeLimit(actionlimit.ISSUE_BULK_EDIT))
        issue_bulk_edit_soft_limit = (
            (settings_user.issue_bulk_edit_limit
             and settings_user.issue_bulk_edit_limit.period_soft_limit)
            or DefaultPeriodSoftLimit(actionlimit.ISSUE_BULK_EDIT))
        issue_bulk_edit_hard_limit = (
            (settings_user.issue_bulk_edit_limit
             and settings_user.issue_bulk_edit_limit.period_hard_limit)
            or DefaultPeriodHardLimit(actionlimit.ISSUE_BULK_EDIT))
        api_request_lifetime_limit = (
            (settings_user.api_request_limit
             and settings_user.api_request_limit.lifetime_limit)
            or DefaultLifetimeLimit(actionlimit.API_REQUEST))
        api_request_soft_limit = (
            (settings_user.api_request_limit
             and settings_user.api_request_limit.period_soft_limit)
            or DefaultPeriodSoftLimit(actionlimit.API_REQUEST))
        api_request_hard_limit = (
            (settings_user.api_request_limit
             and settings_user.api_request_limit.period_hard_limit)
            or DefaultPeriodHardLimit(actionlimit.API_REQUEST))

        return {
            'settings_user':
            settings_user_view,
            'settings_user_pb':
            template_helpers.PBProxy(settings_user),
            'settings_user_is_banned':
            ezt.boolean(settings_user.banned),
            'settings_user_ignore_action_limits':
            (ezt.boolean(settings_user.ignore_action_limits)),
            'self':
            ezt.boolean(logged_in_user_id == settings_user_view.user_id),
            'project_creation_reset':
            (ActionLastReset(settings_user.project_creation_limit)),
            'issue_comment_reset':
            (ActionLastReset(settings_user.issue_comment_limit)),
            'issue_attachment_reset':
            (ActionLastReset(settings_user.issue_attachment_limit)),
            'issue_bulk_edit_reset':
            (ActionLastReset(settings_user.issue_bulk_edit_limit)),
            'api_request_reset':
            (ActionLastReset(settings_user.api_request_limit)),
            'project_creation_lifetime_limit':
            project_creation_lifetime_limit,
            'project_creation_soft_limit':
            project_creation_soft_limit,
            'project_creation_hard_limit':
            project_creation_hard_limit,
            'issue_comment_lifetime_limit':
            issue_comment_lifetime_limit,
            'issue_comment_soft_limit':
            issue_comment_soft_limit,
            'issue_comment_hard_limit':
            issue_comment_hard_limit,
            'issue_attachment_lifetime_limit':
            issue_attachment_lifetime_limit,
            'issue_attachment_soft_limit':
            issue_attachment_soft_limit,
            'issue_attachment_hard_limit':
            issue_attachment_hard_limit,
            'issue_bulk_edit_lifetime_limit':
            issue_bulk_edit_lifetime_limit,
            'issue_bulk_edit_soft_limit':
            issue_bulk_edit_soft_limit,
            'issue_bulk_edit_hard_limit':
            issue_bulk_edit_hard_limit,
            'api_request_lifetime_limit':
            api_request_lifetime_limit,
            'api_request_soft_limit':
            api_request_soft_limit,
            'api_request_hard_limit':
            api_request_hard_limit,
            'profile_url_fragment':
            (settings_user_view.profile_url[len('/u/'):]),
            'preview_on_hover':
            ezt.boolean(settings_user.preview_on_hover),
        }
Exemplo n.º 16
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.
    """
    with self.profiler.Phase('getting config'):
      config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)

    # In addition to checking perms, we adjust some default field values for
    # project members.
    is_member = framework_bizobj.UserIsInProject(
        mr.project, mr.auth.effective_ids)
    page_perms = self.MakePagePerms(
        mr, None,
        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)

    wkp = _SelectTemplate(mr.template_name, config, is_member)

    if wkp.summary:
      initial_summary = wkp.summary
      initial_summary_must_be_edited = wkp.summary_must_be_edited
    else:
      initial_summary = PLACEHOLDER_SUMMARY
      initial_summary_must_be_edited = True

    if wkp.status:
      initial_status = wkp.status
    elif is_member:
      initial_status = 'Accepted'
    else:
      initial_status = 'New'  # not offering meta, only used in hidden field.

    component_paths = []
    for component_id in wkp.component_ids:
      component_paths.append(
          tracker_bizobj.FindComponentDefByID(component_id, config).path)
    initial_components = ', '.join(component_paths)

    if wkp.owner_id:
      initial_owner = framework_views.MakeUserView(
          mr.cnxn, self.services.user, wkp.owner_id)
    elif wkp.owner_defaults_to_member and page_perms.EditIssue:
      initial_owner = mr.auth.user_view
    else:
      initial_owner = None

    if initial_owner:
      initial_owner_name = initial_owner.email
      owner_avail_state = initial_owner.avail_state
      owner_avail_message_short = initial_owner.avail_message_short
    else:
      initial_owner_name = ''
      owner_avail_state = None
      owner_avail_message_short = None

    # Check whether to allow attachments from the entry page
    allow_attachments = tracker_helpers.IsUnderSoftAttachmentQuota(mr.project)

    config_view = tracker_views.ConfigView(mr, self.services, config)
    # If the user followed a link that specified the template name, make sure
    # that it is also in the menu as the current choice.
    for template_view in config_view.templates:
      if template_view.name == mr.template_name:
        template_view.can_view = ezt.boolean(True)

    offer_templates = len(list(
        tmpl for tmpl in config_view.templates if tmpl.can_view)) > 1
    restrict_to_known = config.restrict_to_known
    field_name_set = {fd.field_name.lower() for fd in config.field_defs
                      if not fd.is_deleted}  # TODO(jrobbins): restrictions
    link_or_template_labels = mr.GetListParam('labels', wkp.labels)
    labels = [lab for lab in link_or_template_labels
              if not tracker_bizobj.LabelIsMaskedByField(lab, field_name_set)]

    field_user_views = tracker_views.MakeFieldUserViews(
        mr.cnxn, wkp, self.services.user)
    field_views = [
        tracker_views.MakeFieldValueView(
            fd, config, link_or_template_labels, [], wkp.field_values,
            field_user_views)
        # TODO(jrobbins): field-level view restrictions, display options
        for fd in config.field_defs
        if not fd.is_deleted]

    page_data = {
        'issue_tab_mode': 'issueEntry',
        'initial_summary': initial_summary,
        'template_summary': initial_summary,
        'clear_summary_on_click': ezt.boolean(
            initial_summary_must_be_edited and
            'initial_summary' not in mr.form_overrides),
        'must_edit_summary': ezt.boolean(initial_summary_must_be_edited),

        'initial_description': wkp.content,
        'template_name': wkp.name,
        'component_required': ezt.boolean(wkp.component_required),
        'initial_status': initial_status,
        'initial_owner': initial_owner_name,
        'owner_avail_state': owner_avail_state,
        'owner_avail_message_short': owner_avail_message_short,
        'initial_components': initial_components,
        'initial_cc': '',
        'initial_blocked_on': '',
        'initial_blocking': '',
        'labels': labels,
        'fields': field_views,

        'any_errors': ezt.boolean(mr.errors.AnyErrors()),
        'page_perms': page_perms,
        'allow_attachments': ezt.boolean(allow_attachments),
        'max_attach_size': template_helpers.BytesKbOrMb(
            framework_constants.MAX_POST_BODY_SIZE),

        'offer_templates': ezt.boolean(offer_templates),
        'config': config_view,

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

    return page_data
Exemplo n.º 17
0
    def GatherPageData(self, mr):
        """Build up a dictionary of data values to use when rendering the page."""
        all_members = (mr.project.owner_ids + mr.project.committer_ids +
                       mr.project.contributor_ids)

        with mr.profiler.Phase('gathering members on this page'):
            users_by_id = framework_views.MakeAllUserViews(
                mr.cnxn, self.services.user, all_members)
            framework_views.RevealAllEmailsToMembers(mr.auth, mr.project,
                                                     users_by_id)

        # TODO(jrobbins): re-implement FindUntrustedGroups()
        untrusted_user_group_proxies = []

        with mr.profiler.Phase('gathering commitments (notes)'):
            project_commitments = self.services.project.GetProjectCommitments(
                mr.cnxn, mr.project_id)

        with mr.profiler.Phase('gathering autocomple exclusion ids'):
            group_ids = set(
                self.services.usergroup.DetermineWhichUserIDsAreGroups(
                    mr.cnxn, all_members))
            (ac_exclusion_ids, no_expand_ids
             ) = self.services.project.GetProjectAutocompleteExclusion(
                 mr.cnxn, mr.project_id)

        with mr.profiler.Phase('making member views'):
            owner_views = self._MakeMemberViews(mr.auth.user_id, users_by_id,
                                                mr.project.owner_ids,
                                                mr.project,
                                                project_commitments,
                                                ac_exclusion_ids,
                                                no_expand_ids, group_ids)
            committer_views = self._MakeMemberViews(
                mr.auth.user_id, users_by_id, mr.project.committer_ids,
                mr.project, project_commitments, ac_exclusion_ids,
                no_expand_ids, group_ids)
            contributor_views = self._MakeMemberViews(
                mr.auth.user_id, users_by_id, mr.project.contributor_ids,
                mr.project, project_commitments, ac_exclusion_ids,
                no_expand_ids, group_ids)
            all_member_views = owner_views + committer_views + contributor_views

        url_params = [(name, mr.GetParam(name))
                      for name in framework_helpers.RECOGNIZED_PARAMS]
        pagination = paginate.ArtifactPagination(
            all_member_views,
            mr.GetPositiveIntParam('num', MEMBERS_PER_PAGE),
            mr.GetPositiveIntParam('start'),
            mr.project_name,
            urls.PEOPLE_LIST,
            url_params=url_params)

        offer_membership_editing = mr.perms.HasPerm(permissions.EDIT_PROJECT,
                                                    mr.auth.user_id,
                                                    mr.project)

        check_abandonment = permissions.ShouldCheckForAbandonment(mr)

        newly_added_views = [
            mv for mv in all_member_views
            if str(mv.user.user_id) in mr.GetParam('new', [])
        ]

        return {
            'pagination': pagination,
            'subtab_mode': None,
            'offer_membership_editing': ezt.boolean(offer_membership_editing),
            'initial_add_members': '',
            'initially_expand_form': ezt.boolean(False),
            'untrusted_user_groups': untrusted_user_group_proxies,
            'check_abandonment': ezt.boolean(check_abandonment),
            'total_num_owners': len(mr.project.owner_ids),
            'newly_added_views': newly_added_views,
            'is_hotlist': ezt.boolean(False),
        }
Exemplo n.º 18
0
    def GatherPageData(self, mr):
        """Build up a dictionary of data values to use when rendering the page."""
        if mr.auth.user_id:
            self.services.user.AddVisitedHotlist(mr.cnxn, mr.auth.user_id,
                                                 mr.hotlist_id)

        all_members = (mr.hotlist.owner_ids + mr.hotlist.editor_ids +
                       mr.hotlist.follower_ids)

        hotlist_url = hotlist_helpers.GetURLOfHotlist(mr.cnxn, mr.hotlist,
                                                      self.services.user)

        with mr.profiler.Phase('gathering members on this page'):
            users_by_id = framework_views.MakeAllUserViews(
                mr.cnxn, self.services.user, all_members)
            framework_views.RevealAllEmailsToMembers(mr.auth, mr.project,
                                                     users_by_id)

        untrusted_user_group_proxies = []
        # TODO(jojwang): implement FindUntrustedGroups()

        with mr.profiler.Phase('making member views'):
            owner_views = self._MakeMemberViews(mr, mr.hotlist.owner_ids,
                                                users_by_id)
            editor_views = self._MakeMemberViews(mr, mr.hotlist.editor_ids,
                                                 users_by_id)
            follower_views = self._MakeMemberViews(mr, mr.hotlist.follower_ids,
                                                   users_by_id)
            all_member_views = owner_views + editor_views + follower_views

        url_params = [(name, mr.GetParam(name))
                      for name in framework_helpers.RECOGNIZED_PARAMS]
        # We are passing in None for the project_name because we are not operating
        # under any project.
        pagination = paginate.ArtifactPagination(
            all_member_views,
            mr.GetPositiveIntParam('num', MEMBERS_PER_PAGE),
            mr.GetPositiveIntParam('start'),
            None,
            '%s%s' % (hotlist_url, urls.HOTLIST_PEOPLE),
            url_params=url_params)

        offer_membership_editing = permissions.CanAdministerHotlist(
            mr.auth.effective_ids, mr.perms, mr.hotlist)

        offer_remove_self = (not offer_membership_editing and mr.auth.user_id
                             and mr.auth.user_id in mr.hotlist.editor_ids)

        newly_added_views = [
            mv for mv in all_member_views
            if str(mv.user.user_id) in mr.GetParam('new', [])
        ]

        return {
            'is_hotlist': ezt.boolean(True),
            'untrusted_user_groups': untrusted_user_group_proxies,
            'pagination': pagination,
            'initial_add_members': '',
            'subtab_mode': None,
            'initially_expand_form': ezt.boolean(False),
            'newly_added_views': newly_added_views,
            'offer_membership_editing': ezt.boolean(offer_membership_editing),
            'offer_remove_self': ezt.boolean(offer_remove_self),
            'total_num_owners': len(mr.hotlist.owner_ids),
            'check_abandonment': ezt.boolean(True),
            'initial_new_owner_username': '',
            'placeholder': 'new-owner-username',
            'open_dialog': ezt.boolean(False),
            'viewing_user_page': ezt.boolean(True),
        }
Exemplo n.º 19
0
 def testSpecificLanguage(self):
     prettify_data = prettify.BuildPrettifyData(123, 'trunk/src/hello.java')
     self.assertDictEqual(
         dict(should_prettify=ezt.boolean(True),
              prettify_class='lang-java'), prettify_data)
Exemplo n.º 20
0
    def ProcessFormData(self, mr, post_data):
        """Validate and store the contents of the issues tracker admin page.

    Args:
      mr: commonly used info parsed from the request.
      post_data: HTML form data from the request.

    Returns:
      String URL to redirect the user to, or None if response was already sent.
    """

        config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
        parsed = template_helpers.ParseTemplateRequest(post_data, config)
        field_helpers.ShiftEnumFieldsIntoLabels(parsed.labels, [],
                                                parsed.field_val_strs, [],
                                                config)

        if not parsed.name:
            mr.errors.name = 'Please provide a template name'
        if self.services.template.GetTemplateByName(mr.cnxn, parsed.name,
                                                    mr.project_id):
            mr.errors.name = 'Template with name %s already exists' % parsed.name

        (admin_ids, owner_id, component_ids, field_values, phases,
         approvals) = template_helpers.GetTemplateInfoFromParsed(
             mr, self.services, parsed, config)

        if mr.errors.AnyErrors():
            field_views = tracker_views.MakeAllFieldValueViews(
                config, [], [], field_values, {})
            prechecked_approvals = template_helpers.GetCheckedApprovalsFromParsed(
                parsed.approvals_to_phase_idx)

            self.PleaseCorrect(
                mr,
                initial_members_only=ezt.boolean(parsed.members_only),
                template_name=parsed.name,
                initial_content=parsed.summary,
                initial_must_edit_summary=ezt.boolean(
                    parsed.summary_must_be_edited),
                initial_description=parsed.content,
                initial_status=parsed.status,
                initial_owner=parsed.owner_str,
                initial_owner_defaults_to_member=ezt.boolean(
                    parsed.owner_defaults_to_member),
                initial_components=', '.join(parsed.component_paths),
                initial_component_required=ezt.boolean(
                    parsed.component_required),
                initial_admins=parsed.admin_str,
                labels=parsed.labels,
                fields=[
                    view for view in field_views
                    if view.field_def.type_name is not 'APPROVAL_TYPE'
                ],
                initial_add_approvals=ezt.boolean(parsed.add_approvals),
                initial_phases=[
                    tracker_pb2.Phase(name=name) for name in parsed.phase_names
                ],
                approvals=[
                    view for view in field_views
                    if view.field_def.type_name is 'APPROVAL_TYPE'
                ],
                prechecked_approvals=prechecked_approvals,
                required_approval_ids=parsed.required_approval_ids)
            return

        labels = [label for label in parsed.labels if label]
        self.services.template.CreateIssueTemplateDef(
            mr.cnxn,
            mr.project_id,
            parsed.name,
            parsed.content,
            parsed.summary,
            parsed.summary_must_be_edited,
            parsed.status,
            parsed.members_only,
            parsed.owner_defaults_to_member,
            parsed.component_required,
            owner_id,
            labels,
            component_ids,
            admin_ids,
            field_values,
            phases=phases,
            approval_values=approvals)

        return framework_helpers.FormatAbsoluteURL(mr,
                                                   urls.ADMIN_TEMPLATES,
                                                   saved=1,
                                                   ts=int(time.time()))
Exemplo n.º 21
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)
Exemplo n.º 22
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.
    """

        config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
        field_views = tracker_views.MakeAllFieldValueViews(
            config, [], [], [], {})
        approval_subfields_present = any(fv.field_def.is_approval_subfield
                                         for fv in field_views)

        initial_phases = [tracker_pb2.Phase()
                          ] * template_helpers.MAX_NUM_PHASES
        return {
            'admin_tab_mode':
            self._PROCESS_SUBTAB,
            'allow_edit':
            ezt.boolean(True),
            'new_template_form':
            ezt.boolean(True),
            'initial_members_only':
            ezt.boolean(False),
            'template_name':
            '',
            'initial_content':
            '',
            'initial_must_edit_summary':
            ezt.boolean(False),
            'initial_summary':
            '',
            'initial_status':
            '',
            'initial_owner':
            '',
            'initial_owner_defaults_to_member':
            ezt.boolean(False),
            'initial_components':
            '',
            'initial_component_required':
            ezt.boolean(False),
            'initial_admins':
            '',
            'fields': [
                view for view in field_views
                if view.field_def.type_name is not "APPROVAL_TYPE"
            ],
            'initial_add_approvals':
            ezt.boolean(False),
            'initial_phases':
            initial_phases,
            'approvals': [
                view for view in field_views
                if view.field_def.type_name is "APPROVAL_TYPE"
            ],
            'prechecked_approvals': [],
            'required_approval_ids': [],
            'approval_subfields_present':
            ezt.boolean(approval_subfields_present),
            # We do not support setting phase field values during template creation.
            'phase_fields_present':
            ezt.boolean(False),
        }
Exemplo n.º 23
0
    def GatherPageData(self, mr):
        """Build up a dictionary of data values to use when rendering the page."""

        member_id = self.ValidateMemberID(mr.cnxn, mr.specified_user_id,
                                          mr.project)
        group_ids = self.services.usergroup.DetermineWhichUserIDsAreGroups(
            mr.cnxn, [member_id])
        users_by_id = framework_views.MakeAllUserViews(mr.cnxn,
                                                       self.services.user,
                                                       [member_id])
        framework_views.RevealAllEmailsToMembers(mr.auth, mr.project,
                                                 users_by_id)

        project_commitments = self.services.project.GetProjectCommitments(
            mr.cnxn, mr.project_id)
        (ac_exclusion_ids, no_expand_ids
         ) = self.services.project.GetProjectAutocompleteExclusion(
             mr.cnxn, mr.project_id)
        member_view = project_views.MemberView(
            mr.auth.user_id,
            member_id,
            users_by_id[member_id],
            mr.project,
            project_commitments,
            ac_exclusion=(member_id in ac_exclusion_ids),
            no_expand=(member_id in no_expand_ids),
            is_group=(member_id in group_ids))

        member_user = self.services.user.GetUser(mr.cnxn, member_id)
        # This ignores indirect memberships, which is ok because we are viewing
        # the page for a member directly involved in the project
        role_perms = permissions.GetPermissions(member_user, {member_id},
                                                mr.project)

        # TODO(jrobbins): clarify in the UI which permissions are built-in to
        # the user's direct role, vs. which are granted via a group membership,
        # vs. which ones are extra_perms that have been added specifically for
        # this user.
        member_perms = template_helpers.EZTItem()
        for perm in CHECKBOX_PERMS:
            setattr(
                member_perms, perm,
                ezt.boolean(role_perms.HasPerm(perm, member_id, mr.project)))

        displayed_extra_perms = [
            perm for perm in member_view.extra_perms
            if perm not in CHECKBOX_PERMS
        ]

        viewing_self = mr.auth.user_id == member_id
        warn_abandonment = (viewing_self
                            and permissions.ShouldCheckForAbandonment(mr))

        return {
            'subtab_mode':
            None,
            'member':
            member_view,
            'role_perms':
            role_perms,
            'member_perms':
            member_perms,
            'displayed_extra_perms':
            displayed_extra_perms,
            'offer_edit_perms':
            ezt.boolean(self.CanEditPerms(mr)),
            'offer_edit_member_notes':
            ezt.boolean(self.CanEditMemberNotes(mr, member_id)),
            'offer_remove_role':
            ezt.boolean(self.CanRemoveRole(mr, member_id)),
            'expand_perms':
            ezt.boolean(mr.auth.user_pb.keep_people_perms_open),
            'warn_abandonment':
            ezt.boolean(warn_abandonment),
            'total_num_owners':
            len(mr.project.owner_ids),
        }
Exemplo n.º 24
0
def GetGridViewData(mr,
                    results,
                    config,
                    users_by_id,
                    starred_iid_set,
                    grid_limited,
                    related_issues,
                    hotlist_context_dict=None):
    """EZT template values to render a Grid View of issues.
  Args:
    mr: commonly used info parsed from the request.
    results: The Issue PBs that are the search results to be displayed.
    config: The ProjectConfig PB for the project this view is in.
    users_by_id: A dictionary {user_id: user_view,...} for all the users
        involved in results.
    starred_iid_set: Set of issues that the user has starred.
    grid_limited: True if the results were limited to fit within the grid.
    related_issues: dict {issue_id: issue} of pre-fetched related issues.
    hotlist_context_dict: dict for building a hotlist grid table

  Returns:
    Dictionary for EZT template rendering of the Grid View.
  """
    # We need ordered_columns because EZT loops have no loop-counter available.
    # And, we use column number in the Javascript to hide/show columns.
    columns = mr.col_spec.split()
    ordered_columns = [
        template_helpers.EZTItem(col_index=i, name=col)
        for i, col in enumerate(columns)
    ]
    other_built_in_cols = (features_constants.OTHER_BUILT_IN_COLS
                           if hotlist_context_dict else
                           tracker_constants.OTHER_BUILT_IN_COLS)
    unshown_columns = table_view_helpers.ComputeUnshownColumns(
        results, columns, config, other_built_in_cols)

    grid_x_attr = (mr.x or config.default_x_attr or '--').lower()
    grid_y_attr = (mr.y or config.default_y_attr or '--').lower()

    # Prevent the user from using an axis that we don't support.
    for bad_axis in tracker_constants.NOT_USED_IN_GRID_AXES:
        lower_bad_axis = bad_axis.lower()
        if grid_x_attr == lower_bad_axis:
            grid_x_attr = '--'
        if grid_y_attr == lower_bad_axis:
            grid_y_attr = '--'
    # Using the same attribute on both X and Y is not useful.
    if grid_x_attr == grid_y_attr:
        grid_x_attr = '--'

    all_label_values = {}
    for art in results:
        all_label_values[art.local_id] = (MakeLabelValuesDict(art))

    if grid_x_attr == '--':
        grid_x_headings = ['All']
    else:
        grid_x_items = table_view_helpers.ExtractUniqueValues(
            [grid_x_attr],
            results,
            users_by_id,
            config,
            related_issues,
            hotlist_context_dict=hotlist_context_dict)
        grid_x_headings = grid_x_items[0].filter_values
        if AnyArtifactHasNoAttr(results,
                                grid_x_attr,
                                users_by_id,
                                all_label_values,
                                config,
                                related_issues,
                                hotlist_context_dict=hotlist_context_dict):
            grid_x_headings.append(framework_constants.NO_VALUES)
        grid_x_headings = SortGridHeadings(grid_x_attr, grid_x_headings,
                                           users_by_id, config,
                                           tracker_helpers.SORTABLE_FIELDS)

    if grid_y_attr == '--':
        grid_y_headings = ['All']
    else:
        grid_y_items = table_view_helpers.ExtractUniqueValues(
            [grid_y_attr],
            results,
            users_by_id,
            config,
            related_issues,
            hotlist_context_dict=hotlist_context_dict)
        grid_y_headings = grid_y_items[0].filter_values
        if AnyArtifactHasNoAttr(results,
                                grid_y_attr,
                                users_by_id,
                                all_label_values,
                                config,
                                related_issues,
                                hotlist_context_dict=hotlist_context_dict):
            grid_y_headings.append(framework_constants.NO_VALUES)
        grid_y_headings = SortGridHeadings(grid_y_attr, grid_y_headings,
                                           users_by_id, config,
                                           tracker_helpers.SORTABLE_FIELDS)

    logging.info('grid_x_headings = %s', grid_x_headings)
    logging.info('grid_y_headings = %s', grid_y_headings)
    grid_data = PrepareForMakeGridData(
        results,
        starred_iid_set,
        grid_x_attr,
        grid_x_headings,
        grid_y_attr,
        grid_y_headings,
        users_by_id,
        all_label_values,
        config,
        related_issues,
        hotlist_context_dict=hotlist_context_dict)

    grid_axis_choice_dict = {}
    for oc in ordered_columns:
        grid_axis_choice_dict[oc.name] = True
    for uc in unshown_columns:
        grid_axis_choice_dict[uc] = True
    for bad_axis in tracker_constants.NOT_USED_IN_GRID_AXES:
        if bad_axis in grid_axis_choice_dict:
            del grid_axis_choice_dict[bad_axis]
    grid_axis_choices = list(grid_axis_choice_dict.keys())
    grid_axis_choices.sort()

    grid_cell_mode = mr.cells
    if len(results) > settings.max_tiles_in_grid and mr.cells == 'tiles':
        grid_cell_mode = 'ids'

    grid_view_data = {
        'grid_limited': ezt.boolean(grid_limited),
        'grid_shown': len(results),
        'grid_x_headings': grid_x_headings,
        'grid_y_headings': grid_y_headings,
        'grid_data': grid_data,
        'grid_axis_choices': grid_axis_choices,
        'grid_cell_mode': grid_cell_mode,
        'results': results,  # Really only useful in if-any.
    }
    return grid_view_data
Exemplo n.º 25
0
    def testMissingIssueShouldNotBeVisible(self):
        open_list = {1: self.issue1, 2: self.issue2}
        closed_list = {3: self.issue3}

        irv = tracker_views.IssueRefView('foo', None, open_list, closed_list)
        self.assertEquals(irv.visible, ezt.boolean(False))
Exemplo n.º 26
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.
    """
    search_error_message = ''
    with work_env.WorkEnv(mr, self.services) as we:
      # Check if the user's query is just the ID of an existing issue.
      # TODO(jrobbins): consider implementing this for cross-project search.
      if mr.project and tracker_constants.JUMP_RE.match(mr.query):
        local_id = int(mr.query)
        try:
          we.GetIssueByLocalID(mr.project_id, local_id)  # does it exist?
          url = framework_helpers.FormatAbsoluteURL(
              mr, urls.ISSUE_DETAIL, id=local_id)
          self.redirect(url, abort=True)  # Jump to specified issue.
        except exceptions.NoSuchIssueException:
          pass  # The user is searching for a number that is not an issue ID.
      with mr.profiler.Phase('finishing config work'):
        if mr.project_id:
          config = we.GetProjectConfig(mr.project_id)
        else:
          config = tracker_bizobj.MakeDefaultProjectIssueConfig(None)

      url_params = [(name, mr.GetParam(name)) for name in
                    framework_helpers.RECOGNIZED_PARAMS]
      pipeline = we.ListIssues(
          mr.query, mr.query_project_names, mr.me_user_id, mr.num, mr.start,
          url_params, mr.can, mr.group_by_spec, mr.sort_spec,
          mr.use_cached_searches, display_mode=mr.mode, project=mr.project)
      starred_iid_set = set(we.ListStarredIssueIDs())

    with mr.profiler.Phase('computing col_spec'):
      mr.ComputeColSpec(config)

    with mr.profiler.Phase('publishing emails'):
      framework_views.RevealAllEmailsToMembers(
          mr.auth, mr.project, pipeline.users_by_id)

    with mr.profiler.Phase('getting related issues'):
      related_iids = set()
      if pipeline.grid_mode:
        results_needing_related = pipeline.allowed_results or []
      else:
        results_needing_related = pipeline.visible_results or []
      lower_cols = mr.col_spec.lower().split()
      lower_cols.extend(mr.group_by_spec.lower().split())
      grid_x = (mr.x or config.default_x_attr or '--').lower()
      grid_y = (mr.y or config.default_y_attr or '--').lower()
      lower_cols.append(grid_x)
      lower_cols.append(grid_y)
      for issue in results_needing_related:
        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}

    with mr.profiler.Phase('filtering unviewable issues'):
      viewable_iids_set = {issue.issue_id
                           for issue in tracker_helpers.GetAllowedIssues(
                               mr, [related_issues.values()], self.services)[0]}

    with mr.profiler.Phase('building table/grid'):
      if pipeline.grid_mode:
        # TODO(eyalsoha): Add viewable_iids_set to the grid so that referenced
        # issues can be links.
        page_data = grid_view_helpers.GetGridViewData(
            mr, pipeline.allowed_results or [], pipeline.harmonized_config,
            pipeline.users_by_id, starred_iid_set, pipeline.grid_limited,
            related_issues)
      else:
        page_data = self.GetTableViewData(
            mr, pipeline.visible_results or [], pipeline.harmonized_config,
            pipeline.users_by_id, starred_iid_set, related_issues,
            viewable_iids_set)

    # We show a special message when no query will every produce any results
    # because the project has no issues in it.
    with mr.profiler.Phase('starting stars promise'):
      if mr.project_id:
        project_has_any_issues = (
            pipeline.allowed_results or
            self.services.issue.GetHighestLocalID(mr.cnxn, mr.project_id) != 0)
      else:
        project_has_any_issues = True  # Message only applies in a project.

    with mr.profiler.Phase('making page perms'):
      page_perms = self.MakePagePerms(
          mr, None,
          permissions.SET_STAR,
          permissions.CREATE_ISSUE,
          permissions.EDIT_ISSUE)

    if pipeline.error_responses:
      search_error_message = (
          '%d search backends did not respond or had errors. '
          'These results are probably incomplete.'
          % len(pipeline.error_responses))

    # Update page data with variables that are shared between list and
    # grid view.
    user_hotlists = self.services.features.GetHotlistsByUserID(
        mr.cnxn, mr.auth.user_id)

    new_ui_url = ''
    if hasattr(self.request, 'url'):
      new_ui_url = self.request.url.replace('issues/list', 'issues/list_new')

    # monorail:6336, needed for <ezt-show-columns-connector>
    phase_names = _GetAllPhaseNames(pipeline.visible_results)

    page_data.update({
        'issue_tab_mode': 'issueList',
        'pagination': pipeline.pagination,
        'is_cross_project': ezt.boolean(len(pipeline.query_project_ids) != 1),
        'project_has_any_issues': ezt.boolean(project_has_any_issues),
        'colspec': mr.col_spec,
        # monorail:6336, used in <ezt-show-columns-connector>
        'phasespec': " ".join(phase_names),
        'page_perms': page_perms,
        'grid_mode': ezt.boolean(pipeline.grid_mode),
        'list_mode': ezt.boolean(pipeline.list_mode),
        'chart_mode': ezt.boolean(pipeline.chart_mode),
        'panel_id': mr.panel_id,
        'search_error_message': search_error_message,
        'is_hotlist': ezt.boolean(False),
        # for update-issues-hotlists-dialog, user_remininag_hotlists
        # are displayed with their checkboxes unchecked.
        'user_remaining_hotlists': user_hotlists,
        'user_issue_hotlists': [], # for update-issues-hotlsits-dialog
        # the following are needed by templates for hotlists
        'owner_permissions': ezt.boolean(False),
        'editor_permissions': ezt.boolean(False),
        'edit_hotlist_token': '',
        'add_local_ids': '',
        'placeholder': '',
        'col_spec': '',
        'new_ui_url': new_ui_url,
    })

    return page_data
Exemplo n.º 27
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 = 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, body, upstream_project,
            hostport, commenter_view, detail_url)

        return one_issue_email_tasks
Exemplo n.º 28
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.
    """
        can_create_project = permissions.CanCreateProject(mr.perms)

        # Kick off the search pipeline, it has its own promises for parallelism.
        pipeline = projectsearch.ProjectSearchPipeline(mr, self.services,
                                                       self.profiler)

        # Meanwhile, determine which projects the signed-in user has starred.
        starred_project_ids = set()
        # A dict of project id to the user's membership status.
        project_memberships = {}
        if mr.auth.user_id:
            starred_projects = sitewide_helpers.GetViewableStarredProjects(
                mr.cnxn, self.services, mr.auth.user_id, mr.auth.effective_ids,
                mr.auth.user_pb)
            starred_project_ids = {p.project_id for p in starred_projects}

            owned, _archive_owned, member_of, contrib_of = (
                sitewide_helpers.GetUserProjects(mr.cnxn, self.services,
                                                 mr.auth.user_pb,
                                                 mr.auth.effective_ids,
                                                 mr.auth.effective_ids))
            project_memberships.update(
                {proj.project_id: 'Owner'
                 for proj in owned})
            project_memberships.update(
                {proj.project_id: 'Member'
                 for proj in member_of})
            project_memberships.update(
                {proj.project_id: 'Contributor'
                 for proj in contrib_of})

        # Finish the project search pipeline.
        pipeline.SearchForIDs()
        pipeline.GetProjectsAndPaginate(mr.cnxn, urls.HOSTING_HOME)
        project_ids = [p.project_id for p in pipeline.visible_results]
        star_count_dict = self.services.project_star.CountItemsStars(
            mr.cnxn, project_ids)

        # Make ProjectView objects
        project_view_list = [
            project_views.ProjectView(
                p,
                starred=p.project_id in starred_project_ids,
                num_stars=star_count_dict.get(p.project_id),
                membership_desc=project_memberships.get(p.project_id))
            for p in pipeline.visible_results
        ]
        return {
            'can_create_project': ezt.boolean(can_create_project),
            'learn_more_link': settings.learn_more_link,
            'projects': project_view_list,
            'pagination': pipeline.pagination,
        }
Exemplo n.º 29
0
  def _MakeEmailTasks(
      self, cnxn, project, issue, config, old_owner_id,
      users_by_id, all_comments, comment, starrer_ids,
      contributor_could_view, hostport, omit_ids, perms):
    """Formulate emails to be sent."""
    detail_url = framework_helpers.IssueCommentURL(
        hostport, project, issue.local_id, seq_num=comment.sequence)

    # TODO(jrobbins): avoid the need to make a MonorailRequest object.
    mr = monorailrequest.MonorailRequest(self.services)
    mr.project_name = project.project_name
    mr.project = project
    mr.perms = perms

    # We do not autolink in the emails, so just use an empty
    # registry of autolink rules.
    # TODO(jrobbins): offer users an HTML email option w/ autolinks.
    autolinker = autolink.Autolink()
    was_created = ezt.boolean(comment.sequence == 0)

    email_data = {
        # Pass open_related and closed_related into this method and to
        # the issue view so that we can show it on new issue email.
        'issue': tracker_views.IssueView(issue, users_by_id, config),
        'summary': issue.summary,
        'comment': tracker_views.IssueCommentView(
            project.project_name, comment, users_by_id,
            autolinker, {}, mr, issue),
        'comment_text': comment.content,
        'detail_url': detail_url,
        'was_created': was_created,
        }

    # Generate three versions of email body: link-only is just the link,
    # non-members see some obscured email addresses, and members version has
    # all full email addresses exposed.
    body_link_only = self.link_only_email_template.GetResponse(
      {'detail_url': detail_url, 'was_created': was_created})
    body_for_non_members = self.email_template.GetResponse(email_data)
    framework_views.RevealAllEmails(users_by_id)
    email_data['comment'] = tracker_views.IssueCommentView(
        project.project_name, comment, users_by_id,
        autolinker, {}, mr, issue)
    body_for_members = self.email_template.GetResponse(email_data)

    logging.info('link-only body is:\n%r' % body_link_only)
    logging.info('body for non-members is:\n%r' % body_for_non_members)
    logging.info('body for members is:\n%r' % body_for_members)

    commenter_email = users_by_id[comment.user_id].email
    omit_addrs = set([commenter_email] +
                     [users_by_id[omit_id].email for omit_id in omit_ids])

    auth = authdata.AuthData.FromUserID(
        cnxn, comment.user_id, self.services)
    commenter_in_project = framework_bizobj.UserIsInProject(
        project, auth.effective_ids)
    noisy = tracker_helpers.IsNoisy(len(all_comments) - 1, len(starrer_ids))

    # Give each user a bullet-list of all the reasons that apply for that user.
    group_reason_list = notify_reasons.ComputeGroupReasonList(
        cnxn, self.services, project, issue, config, users_by_id,
        omit_addrs, contributor_could_view, noisy=noisy,
        starrer_ids=starrer_ids, old_owner_id=old_owner_id,
        commenter_in_project=commenter_in_project)

    commenter_view = users_by_id[comment.user_id]
    detail_url = framework_helpers.FormatAbsoluteURLForDomain(
        hostport, issue.project_name, urls.ISSUE_DETAIL,
        id=issue.local_id)
    email_tasks = notify_helpers.MakeBulletedEmailWorkItems(
        group_reason_list, issue, body_link_only, body_for_non_members,
        body_for_members, project, hostport, commenter_view, detail_url,
        seq_num=comment.sequence)

    return email_tasks
Exemplo n.º 30
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.
    """
        config, field_def = self._GetFieldDef(mr)
        approval_def, subfields = None, []
        if field_def.field_type == tracker_pb2.FieldTypes.APPROVAL_TYPE:
            approval_def = tracker_bizobj.FindApprovalDefByID(
                field_def.field_id, config)
            user_views = framework_views.MakeAllUserViews(
                mr.cnxn, self.services.user, field_def.admin_ids,
                approval_def.approver_ids)
            subfields = tracker_bizobj.FindApprovalsSubfields(
                [field_def.field_id], config)[field_def.field_id]
        else:
            user_views = framework_views.MakeAllUserViews(
                mr.cnxn, self.services.user, field_def.admin_ids)
        field_def_view = tracker_views.FieldDefView(field_def,
                                                    config,
                                                    user_views=user_views,
                                                    approval_def=approval_def)

        well_known_issue_types = tracker_helpers.FilterIssueTypes(config)

        allow_edit = permissions.CanEditFieldDef(mr.auth.effective_ids,
                                                 mr.perms, mr.project,
                                                 field_def)

        # Right now we do not allow renaming of enum fields.
        _uneditable_name = field_def.field_type == tracker_pb2.FieldTypes.ENUM_TYPE

        initial_choices = '\n'.join([
            choice.name if not choice.docstring else
            (choice.name + ' = ' + choice.docstring)
            for choice in field_def_view.choices
        ])

        initial_approvers = ', '.join(
            sorted([
                approver_view.email
                for approver_view in field_def_view.approvers
            ]))

        initial_admins = ', '.join(
            sorted([uv.email for uv in field_def_view.admins]))

        return {
            'admin_tab_mode': servlet.Servlet.PROCESS_TAB_LABELS,
            'field_def': field_def_view,
            'allow_edit': ezt.boolean(allow_edit),
            # TODO(jojwang): update when name changes are actually saved
            'uneditable_name': ezt.boolean(True),
            'initial_admins': initial_admins,
            'initial_applicable_type': field_def.applicable_type,
            'initial_applicable_predicate': field_def.applicable_predicate,
            'initial_approvers': initial_approvers,
            'initial_choices': initial_choices,
            'approval_subfields':
            [fd for fd in subfields if not fd.is_deleted],
            'well_known_issue_types': well_known_issue_types,
        }