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
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, }
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
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
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))
def testGenericLanguage(self): prettify_data = prettify.BuildPrettifyData(123, 'trunk/src/hello.php') self.assertDictEqual( dict(should_prettify=ezt.boolean(True), prettify_class=''), prettify_data)
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)
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
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)
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)
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
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)
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
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
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), }
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
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), }
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), }
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)
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()))
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)
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), }
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), }
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
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))
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
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
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, }
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
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, }