def _MakeTableData(issues, starred_iid_set, lower_columns, lower_group_by, users_by_id, cell_factories, related_issues, config, context_for_all_issues, hotlist_id, sort_spec): """Returns data from MakeTableData after adding additional information.""" table_data = table_view_helpers.MakeTableData( issues, starred_iid_set, lower_columns, lower_group_by, users_by_id, cell_factories, lambda issue: issue.issue_id, related_issues, config, context_for_all_issues) for row, art in zip(table_data, issues): row.issue_id = art.issue_id row.local_id = art.local_id row.project_name = art.project_name row.project_url = framework_helpers.FormatURL( None, '/p/%s' % row.project_name) row.issue_ref = '%s:%d' % (art.project_name, art.local_id) row.issue_clean_url = tracker_helpers.FormatRelativeIssueURL( art.project_name, urls.ISSUE_DETAIL, id=art.local_id) row.issue_ctx_url = tracker_helpers.FormatRelativeIssueURL( art.project_name, urls.ISSUE_DETAIL, id=art.local_id, sort=sort_spec, hotlist_id=hotlist_id) return table_data
def testFormatURLWithStrangeParams(self): mr = testing_helpers.MakeMonorailRequest(path='/foo?start=0') url = framework_helpers.FormatURL( mr, '/foo', r=0, path='/foo/bar', sketchy='/foo/ bar baz ') self.assertEqual( '/foo?start=0&path=/foo/bar&r=0&sketchy=/foo/%20bar%20baz%20', url)
def get(self, project_name=None, viewed_username=None, hotlist_id=None): with work_env.WorkEnv(self.mr, self.services) as we: hotlist_id = self.mr.GetIntParam('hotlist_id') current_issue = we.GetIssueByLocalID(self.mr.project_id, self.mr.local_id, use_cache=False) hotlist = None if hotlist_id: try: hotlist = self.services.features.GetHotlist( self.mr.cnxn, hotlist_id) except features_svc.NoSuchHotlistException: pass try: adj_issue = GetAdjacentIssue(we, current_issue, hotlist=hotlist, next_issue=self.next_handler) path = '/p/%s%s' % (adj_issue.project_name, urls.ISSUE_DETAIL) url = framework_helpers.FormatURL( [(name, self.mr.GetParam(name)) for name in framework_helpers.RECOGNIZED_PARAMS], path, id=adj_issue.local_id) except exceptions.NoSuchIssueException: config = we.GetProjectConfig(self.mr.project_id) url = _ComputeBackToListURL(self.mr, current_issue, config, hotlist, self.services) self.redirect(url)
def _StartBackendSearchCall(mr, query_project_names, shard_key, invalidation_timestep, deadline=None, failfast=True): """Ask a backend to query one shard of the database.""" shard_id, subquery = shard_key backend_host = modules.get_hostname(module='besearch') url = 'http://%s%s' % (backend_host, framework_helpers.FormatURL( mr, urls.BACKEND_SEARCH, projects=','.join(query_project_names), q=subquery, start=0, num=mr.start + mr.num, logged_in_user_id=mr.auth.user_id or 0, me_user_id=mr.me_user_id, shard_id=shard_id, invalidation_timestep=invalidation_timestep)) logging.info('\n\nCalling backend: %s', url) rpc = urlfetch.create_rpc(deadline=deadline or settings.backend_deadline) headers = _MakeBackendRequestHeaders(failfast) # follow_redirects=False is needed to avoid a login screen on googleplex. urlfetch.make_fetch_call(rpc, url, follow_redirects=False, headers=headers) return rpc
def testFormatURL(self): mr = testing_helpers.MakeMonorailRequest() path = '/dude/wheres/my/car' recognized_params = [(name, mr.GetParam(name)) for name in framework_helpers.RECOGNIZED_PARAMS] url = framework_helpers.FormatURL(recognized_params, path) self.assertEqual(path, url)
def GatherPageData(self, mr): """Build up a dictionary of data values to use when rendering the page. Args: mr: commonly usef info parsed from the request. Returns: Dict of values used by EZT for rendering the page. """ with self.profiler.Phase('getting hotlist'): if mr.hotlist_id is None: self.abort(404, 'no hotlist specified') if mr.auth.user_id: self.services.user.AddVisitedHotlist( mr.cnxn, mr.auth.user_id, mr.hotlist_id) if mr.mode == 'grid': page_data = self.GetGridViewData(mr) else: page_data = self.GetTableViewData(mr) with self.profiler.Phase('making page perms'): owner_permissions = permissions.CanAdministerHotlist( mr.auth.effective_ids, mr.hotlist) editor_permissions = permissions.CanEditHotlist( mr.auth.effective_ids, mr.hotlist) # TODO(jojwang): each issue should have an individual # SetStar status based on its project to indicate whether or not # the star icon should be shown to the user. page_perms = template_helpers.EZTItem( EditIssue=None, SetStar=mr.auth.user_id) allow_rerank = (not mr.group_by_spec and mr.sort_spec.startswith( 'rank') and (owner_permissions or editor_permissions)) user_hotlists = self.services.features.GetHotlistsByUserID( mr.cnxn, mr.auth.user_id) try: user_hotlists.remove(self.services.features.GetHotlist( mr.cnxn, mr.hotlist_id)) except ValueError: pass # Note: The HotlistView is created and returned in servlet.py page_data.update({'owner_permissions': ezt.boolean(owner_permissions), 'editor_permissions': ezt.boolean(editor_permissions), 'issue_tab_mode': 'issueList', 'grid_mode': ezt.boolean(mr.mode == 'grid'), 'set_star_token': '', # needed for shared ezt templates. 'page_perms': page_perms, 'colspec': mr.col_spec, 'allow_rerank': ezt.boolean(allow_rerank), 'csv_link': framework_helpers.FormatURL( mr, '%d/csv' % mr.hotlist_id, num=100), 'is_hotlist': ezt.boolean(True), 'col_spec': mr.col_spec.lower(), 'user_hotlists': user_hotlists, 'viewing_user_page': ezt.boolean(True), }) return page_data
def GetTableViewData( self, mr, results, config, users_by_id, starred_iid_set, related_issues, viewable_iids_set): """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. viewable_iids_set: set of issue ids that are viewable by the user. 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, viewable_iids_set, 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 testFormatURLWithStrangeParams(self): mr = testing_helpers.MakeMonorailRequest(path='/foo?start=0') recognized_params = [(name, mr.GetParam(name)) for name in framework_helpers.RECOGNIZED_PARAMS] url = framework_helpers.FormatURL(recognized_params, '/foo', r=0, path='/foo/bar', sketchy='/foo/ bar baz ') self.assertEqual( '/foo?start=0&path=/foo/bar&r=0&sketchy=/foo/%20bar%20baz%20', url)
def testFormatURLWithRecognizedParams(self): params = {} query = [] for name in framework_helpers.RECOGNIZED_PARAMS: params[name] = name query.append('%s=%s' % (name, 123)) path = '/dude/wheres/my/car' expected = '%s?%s' % (path, '&'.join(query)) mr = testing_helpers.MakeMonorailRequest(path=expected) url = framework_helpers.FormatURL(mr, path) # No added params. self.assertEqual(expected, url)
def FormatIssueURL(issue_ref_tuple, default_project_name=None): """Format an issue url from an issue ref.""" if issue_ref_tuple is None: return '' project_name, local_id = issue_ref_tuple project_name = project_name or default_project_name url = framework_helpers.FormatURL(None, '/p/%s%s' % (project_name, urls.ISSUE_DETAIL), id=local_id) return url
def FormatRelativeIssueURL(project_name, path, **kwargs): """Format a URL to get to an issue in the named project. Args: project_name: string name of the project containing the issue. path: string servlet path, e.g., from framework/urls.py. **kwargs: additional query-string parameters to include in the URL. Returns: A URL string. """ return framework_helpers.FormatURL(None, '/p/%s%s' % (project_name, path), **kwargs)
def testFormatURLWithKeywordArgs(self): params = {} query_pairs = [] for name in framework_helpers.RECOGNIZED_PARAMS: params[name] = name if name is not 'can' and name is not 'start': query_pairs.append('%s=%s' % (name, 123)) path = '/dude/wheres/my/car' mr = testing_helpers.MakeMonorailRequest( path='%s?%s' % (path, '&'.join(query_pairs))) query_pairs.append('can=yep') query_pairs.append('start=486') query_string = '&'.join(query_pairs) expected = '%s?%s' % (path, query_string) url = framework_helpers.FormatURL(mr, path, can='yep', start=486) self.assertEqual(expected, url)
def _StartBackendNonviewableCall(project_id, logged_in_user_id, shard_id, invalidation_timestep, deadline=None, failfast=True): """Ask a backend to query one shard of the database.""" backend_host = modules.get_hostname(module='besearch') url = 'http://%s%s' % (backend_host, framework_helpers.FormatURL( None, urls.BACKEND_NONVIEWABLE, project_id=project_id or '', logged_in_user_id=logged_in_user_id or '', shard_id=shard_id, invalidation_timestep=invalidation_timestep)) logging.info('Calling backend nonviewable: %s', url) rpc = urlfetch.create_rpc(deadline=deadline or settings.backend_deadline) headers = _MakeBackendRequestHeaders(failfast) # follow_redirects=False is needed to avoid a login screen on googleplex. urlfetch.make_fetch_call(rpc, url, follow_redirects=False, headers=headers) return rpc
def testFormatURLWithKeywordArgsAndID(self): params = {} query_pairs = [] query_pairs.append('id=200') # id should be the first parameter. for name in framework_helpers.RECOGNIZED_PARAMS: params[name] = name if name != 'can' and name != 'start': query_pairs.append('%s=%s' % (name, 123)) path = '/dude/wheres/my/car' mr = testing_helpers.MakeMonorailRequest(path='%s?%s' % (path, '&'.join(query_pairs))) query_pairs.append('can=yep') query_pairs.append('start=486') query_string = '&'.join(query_pairs) expected = '%s?%s' % (path, query_string) recognized_params = [(name, mr.GetParam(name)) for name in framework_helpers.RECOGNIZED_PARAMS] url = framework_helpers.FormatURL(recognized_params, path, can='yep', start=486, id=200) self.assertEqual(expected, url)
def __init__(self, total_count, items_per_page, start, list_page_url=None, count_up=True, start_param_name='start', num_param_name='num', max_num=None, url_params=None, project_name=None): """Given 'num' and 'start' params, determine Prev and Next links. Args: total_count: total number of artifacts that satisfy the query. items_per_page: number of items to display on each page, e.g., 25. start: the start index of the pagination page. list_page_url: URL of the web application page that is displaying the list of artifacts. Used to build the Prev and Next URLs. If None, no URLs will be built. count_up: if False, count down from total_count. start_param_name: query string parameter name for the start value of the pagination page. num_param: query string parameter name for the number of items to show on a pagination page. max_num: optional limit on the value of the num param. If not given, settings.max_artifact_search_results_per_page is used. url_params: list of (param_name, param_value) we want to keep in any new urls. project_name: the name of the project we are operating in. """ self.total_count = total_count self.prev_url = '' self.reload_url = '' self.next_url = '' if max_num is None: max_num = settings.max_artifact_search_results_per_page self.num = items_per_page self.num = min(self.num, max_num) if count_up: self.start = start or 0 self.last = min(self.total_count, self.start + self.num) prev_start = max(0, self.start - self.num) next_start = self.start + self.num else: self.start = start or self.total_count self.last = max(0, self.start - self.num) prev_start = min(self.total_count, self.start + self.num) next_start = self.start - self.num if list_page_url: if project_name: list_servlet_rel_url = '/p/%s%s' % (project_name, list_page_url) else: list_servlet_rel_url = list_page_url self.reload_url = framework_helpers.FormatURL( url_params, list_servlet_rel_url, **{ start_param_name: self.start, num_param_name: self.num }) if prev_start != self.start: self.prev_url = framework_helpers.FormatURL( url_params, list_servlet_rel_url, **{ start_param_name: prev_start, num_param_name: self.num }) if ((count_up and next_start < self.total_count) or (not count_up and next_start >= 1)): self.next_url = framework_helpers.FormatURL( url_params, list_servlet_rel_url, **{ start_param_name: next_start, num_param_name: self.num }) self.visible = ezt.boolean(self.last != self.start) # Adjust indices to one-based values for display to users. if count_up: self.start += 1 else: self.last += 1
def GatherUpdatesData(services, mr, 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. 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. """ # 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 ascending = bool(mr.after) with mr.profiler.Phase('get activities'): comments = services.issue.GetIssueActivity(mr.cnxn, num=num, before=mr.before, after=mr.after, project_ids=project_ids, user_ids=user_ids, ascending=ascending) # 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. # 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} 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.auth, mr.project, 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 mr.profiler.Phase('rendering activities'): for activity in displayed_activities: entry = ActivityView(activity, mr, prefetched_issues, users_by_id, prefetched_projects, prefetched_configs, 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] recognized_params = [(name, mr.GetParam(name)) for name in framework_helpers.RECOGNIZED_PARAMS] if displayed_activities and (mr.before or mr.after): prev_url = framework_helpers.FormatURL(recognized_params, list_servlet_rel_url, after=new_after) if mr.after or len(comments) > UPDATES_PER_PAGE: next_url = framework_helpers.FormatURL(recognized_params, 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 HandleRequest(self, mr): hotlist_id = mr.GetIntParam('hotlist_id') list_url = None with work_env.WorkEnv(mr, self.services) as we: if not _ShouldShowFlipper(mr, self.services): return {} issue = we.GetIssueByLocalID(mr.project_id, mr.local_id, use_cache=False) hotlist = None if hotlist_id: hotlist = self.services.features.GetHotlist( mr.cnxn, hotlist_id) if not features_bizobj.IssueIsInHotlist( hotlist, issue.issue_id): raise exceptions.InvalidHotlistException() if not permissions.CanViewHotlist(mr.auth.effective_ids, mr.perms, hotlist): raise permissions.PermissionException() (prev_iid, cur_index, next_iid, total_count) = we.GetIssuePositionInHotlist(issue, hotlist) else: (prev_iid, cur_index, next_iid, total_count) = we.FindIssuePositionInSearch(issue) config = we.GetProjectConfig(self.mr.project_id) if hotlist: mr.ComputeColSpec(hotlist) else: mr.ComputeColSpec(config) list_url = _ComputeBackToListURL(mr, issue, config, hotlist, self.services) prev_url = None next_url = None recognized_params = [(name, mr.GetParam(name)) for name in framework_helpers.RECOGNIZED_PARAMS] if prev_iid: prev_issue = we.services.issue.GetIssue(mr.cnxn, prev_iid) path = '/p/%s%s' % (prev_issue.project_name, urls.ISSUE_DETAIL) prev_url = framework_helpers.FormatURL(recognized_params, path, id=prev_issue.local_id) if next_iid: next_issue = we.services.issue.GetIssue(mr.cnxn, next_iid) path = '/p/%s%s' % (next_issue.project_name, urls.ISSUE_DETAIL) next_url = framework_helpers.FormatURL(recognized_params, path, id=next_issue.local_id) return { 'prev_iid': prev_iid, 'prev_url': prev_url, 'cur_index': cur_index, 'next_iid': next_iid, 'next_url': next_url, 'list_url': list_url, 'total_count': total_count, }
def testFormatURL(self): mr = testing_helpers.MakeMonorailRequest() path = '/dude/wheres/my/car' url = framework_helpers.FormatURL(mr, path) self.assertEqual(path, url)