Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(464)

Side by Side Diff: appengine/monorail/framework/grid_view_helpers.py

Issue 1868553004: Open Source Monorail (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: Rebase Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « appengine/monorail/framework/gcs_helpers.py ('k') | appengine/monorail/framework/jsonfeed.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 # Copyright 2016 The Chromium Authors. All rights reserved.
2 # Use of this source code is govered by a BSD-style
3 # license that can be found in the LICENSE file or at
4 # https://developers.google.com/open-source/licenses/bsd
5
6 """Classes and functions for displaying grids of project artifacts.
7
8 A grid is a two-dimensional display of items where the user can choose
9 the X and Y axes.
10 """
11
12 import collections
13 import logging
14
15 from framework import framework_constants
16 from framework import sorting
17 from framework import template_helpers
18 from proto import tracker_pb2
19 from tracker import tracker_bizobj
20
21
22 # We shorten long attribute values to fit into the table cells.
23 _MAX_CELL_DISPLAY_CHARS = 70
24
25
26 def SortGridHeadings(col_name, heading_value_list, users_by_id, config,
27 asc_accessors):
28 """Sort the grid headings according to well-known status and label order.
29
30 Args:
31 col_name: String column name that is used on that grid axis.
32 heading_value_list: List of grid row or column heading values.
33 users_by_id: Dict mapping user_ids to UserViews.
34 config: ProjectIssueConfig PB for the current project.
35 asc_accessors: Dict (col_name -> function()) for special columns.
36
37 Returns:
38 The same heading values, but sorted in a logical order.
39 """
40 decorated_list = []
41 fd = tracker_bizobj.FindFieldDef(col_name, config)
42 if fd: # Handle fields.
43 for value in heading_value_list:
44 field_value = tracker_bizobj.GetFieldValueWithRawValue(
45 fd.field_type, None, users_by_id, value)
46 decorated_list.append([field_value, field_value])
47 elif col_name == 'status':
48 wk_statuses = [wks.status.lower()
49 for wks in config.well_known_statuses]
50 decorated_list = [(_WKSortingValue(value.lower(), wk_statuses), value)
51 for value in heading_value_list]
52
53 elif col_name in asc_accessors: # Special cols still sort alphabetically.
54 decorated_list = [(value, value)
55 for value in heading_value_list]
56
57 else: # Anything else is assumed to be a label prefix
58 wk_labels = [wkl.label.lower().split('-', 1)[-1]
59 for wkl in config.well_known_labels]
60 decorated_list = [(_WKSortingValue(value.lower(), wk_labels), value)
61 for value in heading_value_list]
62
63 decorated_list.sort()
64 result = [decorated_tuple[1] for decorated_tuple in decorated_list]
65 logging.info('Headers for %s are: %r', col_name, result)
66 return result
67
68
69 def _WKSortingValue(value, well_known_list):
70 """Return a value used to sort headings so that well-known ones are first."""
71 if not value:
72 return sorting.MAX_STRING # Undefined values sort last.
73 try:
74 # well-known values sort by index
75 return well_known_list.index(value)
76 except ValueError:
77 return value # odd-ball values lexicographically after all well-known ones
78
79
80 def MakeGridData(
81 artifacts, x_attr, x_headings, y_attr, y_headings, users_by_id,
82 artifact_view_factory, all_label_values, config):
83 """Return a list of grid row items for display by EZT.
84
85 Args:
86 artifacts: a list of issues to consider showing.
87 x_attr: lowercase name of the attribute that defines the x-axis.
88 x_headings: list of values for column headings.
89 y_attr: lowercase name of the attribute that defines the y-axis.
90 y_headings: list of values for row headings.
91 users_by_id: dict {user_id: user_view, ...} for referenced users.
92 artifact_view_factory: constructor for grid tiles.
93 all_label_values: pre-parsed dictionary of values from the key-value
94 labels on each issue: {issue_id: {key: [val,...], ...}, ...}
95 config: ProjectIssueConfig PB for the current project.
96
97 Returns:
98 A list of EZTItems, each representing one grid row, and each having
99 a nested list of grid cells.
100
101 Each grid row has a row name, and a list of cells. Each cell has a
102 list of tiles. Each tile represents one artifact. Artifacts are
103 represented once in each cell that they match, so one artifact that
104 has multiple values for a certain attribute can occur in multiple cells.
105 """
106 x_attr = x_attr.lower()
107 y_attr = y_attr.lower()
108
109 # A flat dictionary {(x, y): [cell, ...], ...] for the whole grid.
110 x_y_data = collections.defaultdict(list)
111
112 # Put each issue into the grid cell(s) where it belongs.
113 for art in artifacts:
114 label_value_dict = all_label_values[art.local_id]
115 x_vals = GetArtifactAttr(
116 art, x_attr, users_by_id, label_value_dict, config)
117 y_vals = GetArtifactAttr(
118 art, y_attr, users_by_id, label_value_dict, config)
119 tile = artifact_view_factory(art)
120
121 # Put the current issue into each cell where it belongs, which will usually
122 # be exactly 1 cell, but it could be a few.
123 if x_attr != '--' and y_attr != '--': # User specified both axes.
124 for x in x_vals:
125 for y in y_vals:
126 x_y_data[x, y].append(tile)
127 elif y_attr != '--': # User only specified Y axis.
128 for y in y_vals:
129 x_y_data['All', y].append(tile)
130 elif x_attr != '--': # User only specified X axis.
131 for x in x_vals:
132 x_y_data[x, 'All'].append(tile)
133 else: # User specified neither axis.
134 x_y_data['All', 'All'].append(tile)
135
136 # Convert the dictionary to a list-of-lists so that EZT can iterate over it.
137 grid_data = []
138 for y in y_headings:
139 cells_in_row = []
140 for x in x_headings:
141 tiles = x_y_data[x, y]
142
143 drill_down = ''
144 if x_attr != '--':
145 drill_down = MakeDrillDownSearch(x_attr, x)
146 if y_attr != '--':
147 drill_down += MakeDrillDownSearch(y_attr, y)
148
149 cells_in_row.append(template_helpers.EZTItem(
150 tiles=tiles, count=len(tiles), drill_down=drill_down))
151 grid_data.append(template_helpers.EZTItem(
152 grid_y_heading=y, cells_in_row=cells_in_row))
153
154 return grid_data
155
156
157 def MakeDrillDownSearch(attr, value):
158 """Constructs search term for drill-down.
159
160 Args:
161 attr: lowercase name of the attribute to narrow the search on.
162 value: value to narrow the search to.
163
164 Returns:
165 String with user-query term to narrow a search to the given attr value.
166 """
167 if value == framework_constants.NO_VALUES:
168 return '-has:%s ' % attr
169 else:
170 return '%s=%s ' % (attr, value)
171
172
173 def MakeLabelValuesDict(art):
174 """Return a dict of label values and a list of one-word labels.
175
176 Args:
177 art: artifact object, e.g., an issue PB.
178
179 Returns:
180 A dict {prefix: [suffix,...], ...} for each key-value label.
181 """
182 label_values = collections.defaultdict(list)
183 for label_name in tracker_bizobj.GetLabels(art):
184 if '-' in label_name:
185 key, value = label_name.split('-', 1)
186 label_values[key.lower()].append(value)
187
188 return label_values
189
190
191 def GetArtifactAttr(
192 art, attribute_name, users_by_id, label_attr_values_dict, config):
193 """Return the requested attribute values of the given artifact.
194
195 Args:
196 art: a tracked artifact with labels, local_id, summary, stars, and owner.
197 attribute_name: lowercase string name of attribute to get.
198 users_by_id: dictionary of UserViews already created.
199 label_attr_values_dict: dictionary {'key': [value, ...], }.
200 config: ProjectIssueConfig PB for the current project.
201
202 Returns:
203 A list of string attribute values, or [framework_constants.NO_VALUES]
204 if the artifact has no value for that attribute.
205 """
206 if attribute_name == '--':
207 return []
208 if attribute_name == 'id':
209 return [art.local_id]
210 if attribute_name == 'summary':
211 return [art.summary]
212 if attribute_name == 'status':
213 return [tracker_bizobj.GetStatus(art)]
214 if attribute_name == 'stars':
215 return [art.star_count]
216 if attribute_name == 'attachments':
217 return [art.attachment_count]
218 # TODO(jrobbins): support blocked on, blocking, and mergedinto.
219 if attribute_name == 'reporter':
220 return [users_by_id[art.reporter_id].display_name]
221 if attribute_name == 'owner':
222 owner_id = tracker_bizobj.GetOwnerId(art)
223 if not owner_id:
224 return [framework_constants.NO_VALUES]
225 else:
226 return [users_by_id[owner_id].display_name]
227 if attribute_name == 'cc':
228 cc_ids = tracker_bizobj.GetCcIds(art)
229 if not cc_ids:
230 return [framework_constants.NO_VALUES]
231 else:
232 return [users_by_id[cc_id].display_name for cc_id in cc_ids]
233 if attribute_name == 'component':
234 comp_ids = list(art.component_ids) + list(art.derived_component_ids)
235 if not comp_ids:
236 return [framework_constants.NO_VALUES]
237 else:
238 paths = []
239 for comp_id in comp_ids:
240 cd = tracker_bizobj.FindComponentDefByID(comp_id, config)
241 if cd:
242 paths.append(cd.path)
243 return paths
244
245 # Check to see if it is a field. Process as field only if it is not an enum
246 # type because enum types are stored as key-value labels.
247 fd = tracker_bizobj.FindFieldDef(attribute_name, config)
248 if fd and fd.field_type != tracker_pb2.FieldTypes.ENUM_TYPE:
249 values = []
250 for fv in art.field_values:
251 if fv.field_id == fd.field_id:
252 value = tracker_bizobj.GetFieldValueWithRawValue(
253 fd.field_type, fv, users_by_id, None)
254 values.append(value)
255 return values
256
257 # Since it is not a built-in attribute or a field, it must be a key-value
258 # label.
259 return label_attr_values_dict.get(
260 attribute_name, [framework_constants.NO_VALUES])
261
262
263 def AnyArtifactHasNoAttr(
264 artifacts, attr_name, users_by_id, all_label_values, config):
265 """Return true if any artifact does not have a value for attr_name."""
266 # TODO(jrobbins): all_label_values needs to be keyed by issue_id to allow
267 # cross-project grid views.
268 for art in artifacts:
269 vals = GetArtifactAttr(
270 art, attr_name.lower(), users_by_id, all_label_values[art.local_id],
271 config)
272 if framework_constants.NO_VALUES in vals:
273 return True
274
275 return False
OLDNEW
« no previous file with comments | « appengine/monorail/framework/gcs_helpers.py ('k') | appengine/monorail/framework/jsonfeed.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698