blob: 849e59f04e6ccd9bfe0bfd126e7cd59c25cda856 [file] [log] [blame]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001# Copyright 2016 The Chromium Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
Copybara854996b2021-09-07 19:36:02 +00004
5"""Servlets for issue tracker configuration.
6
7These classes implement the Statuses, Labels and fields, Components, Rules, and
8Views subtabs under the Process tab. Unlike most servlet modules, this single
9file holds a base class and several related servlet classes.
10"""
11from __future__ import print_function
12from __future__ import division
13from __future__ import absolute_import
14
15import collections
Copybara854996b2021-09-07 19:36:02 +000016import logging
17import time
18
Copybara854996b2021-09-07 19:36:02 +000019from features import filterrules_helpers
20from features import filterrules_views
21from features import savedqueries_helpers
Copybara854996b2021-09-07 19:36:02 +000022from framework import framework_constants
23from framework import framework_helpers
24from framework import framework_views
25from framework import monorailrequest
26from framework import permissions
27from framework import servlet
28from framework import urls
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010029from mrproto import tracker_pb2
Copybara854996b2021-09-07 19:36:02 +000030from tracker import tracker_bizobj
31from tracker import tracker_constants
32from tracker import tracker_helpers
33from tracker import tracker_views
34
35
36class IssueAdminBase(servlet.Servlet):
37 """Base class for servlets allowing project owners to configure tracker."""
38
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010039 _MAIN_TAB_MODE = servlet.Servlet.MAIN_TAB_PROCESS
Copybara854996b2021-09-07 19:36:02 +000040 _PROCESS_SUBTAB = None # specified in subclasses
41
42 def GatherPageData(self, mr):
43 """Build up a dictionary of data values to use when rendering the page.
44
45 Args:
46 mr: commonly used info parsed from the request.
47
48 Returns:
49 Dict of values used by EZT for rendering the page.
50 """
51 config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
52 config_view = tracker_views.ConfigView(mr, self.services, config,
53 template=None, load_all_templates=True)
54 open_text, closed_text = tracker_views.StatusDefsAsText(config)
55 labels_text = tracker_views.LabelDefsAsText(config)
56
57 return {
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010058 'admin_tab_mode':
59 self._PROCESS_SUBTAB,
60 'config':
61 config_view,
62 'open_text':
63 open_text,
64 'closed_text':
65 closed_text,
66 'labels_text':
67 labels_text,
68 'can_edit_project':
69 permissions.CanEditProjectConfig(mr, self.services) or None,
70 }
Copybara854996b2021-09-07 19:36:02 +000071
72 def ProcessFormData(self, mr, post_data):
73 """Validate and store the contents of the issues tracker admin page.
74
75 Args:
76 mr: commonly used info parsed from the request.
77 post_data: HTML form data from the request.
78
79 Returns:
80 String URL to redirect the user to, or None if response was already sent.
81 """
82 page_url = self.ProcessSubtabForm(post_data, mr)
83
84 if page_url:
85 return framework_helpers.FormatAbsoluteURL(
86 mr, page_url, saved=1, ts=int(time.time()))
87
88
89class AdminStatuses(IssueAdminBase):
90 """Servlet allowing project owners to configure well-known statuses."""
91
92 _PAGE_TEMPLATE = 'tracker/admin-statuses-page.ezt'
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010093 _PROCESS_SUBTAB = servlet.Servlet.PROCESS_TAB_STATUSES
Copybara854996b2021-09-07 19:36:02 +000094
95 def ProcessSubtabForm(self, post_data, mr):
96 """Process the status definition section of the admin page.
97
98 Args:
99 post_data: HTML form data for the HTTP request being processed.
100 mr: commonly used info parsed from the request.
101
102 Returns:
103 The URL of the page to show after processing.
104 """
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100105 if not permissions.CanEditProjectConfig(mr, self.services):
Copybara854996b2021-09-07 19:36:02 +0000106 raise permissions.PermissionException(
107 'Only project owners may edit the status definitions')
108
109 wks_open_text = post_data.get('predefinedopen', '')
110 wks_open_matches = framework_constants.IDENTIFIER_DOCSTRING_RE.findall(
111 wks_open_text)
112 wks_open_tuples = [
113 (status.lstrip('#'), docstring.strip(), True, status.startswith('#'))
114 for status, docstring in wks_open_matches]
115 if not wks_open_tuples:
116 mr.errors.open_statuses = 'A project cannot have zero open statuses'
117
118 wks_closed_text = post_data.get('predefinedclosed', '')
119 wks_closed_matches = framework_constants.IDENTIFIER_DOCSTRING_RE.findall(
120 wks_closed_text)
121 wks_closed_tuples = [
122 (status.lstrip('#'), docstring.strip(), False, status.startswith('#'))
123 for status, docstring in wks_closed_matches]
124 if not wks_closed_tuples:
125 mr.errors.closed_statuses = 'A project cannot have zero closed statuses'
126
127 statuses_offer_merge_text = post_data.get('statuses_offer_merge', '')
128 statuses_offer_merge = framework_constants.IDENTIFIER_RE.findall(
129 statuses_offer_merge_text)
130
131 if mr.errors.AnyErrors():
132 self.PleaseCorrect(
133 mr, open_text=wks_open_text, closed_text=wks_closed_text)
134 return
135
136 self.services.config.UpdateConfig(
137 mr.cnxn, mr.project, statuses_offer_merge=statuses_offer_merge,
138 well_known_statuses=wks_open_tuples + wks_closed_tuples)
139
140 # TODO(jrobbins): define a "strict" mode that affects only statuses.
141
142 return urls.ADMIN_STATUSES
143
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100144 def GetAdminStatusesPage(self, **kwargs):
145 return self.handler(**kwargs)
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200146
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100147 def PostAdminStatusesPage(self, **kwargs):
148 return self.handler(**kwargs)
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200149
Copybara854996b2021-09-07 19:36:02 +0000150
151class AdminLabels(IssueAdminBase):
152 """Servlet allowing project owners to labels and fields."""
153
154 _PAGE_TEMPLATE = 'tracker/admin-labels-page.ezt'
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100155 _PROCESS_SUBTAB = servlet.Servlet.PROCESS_TAB_LABELS
Copybara854996b2021-09-07 19:36:02 +0000156
157 def GatherPageData(self, mr):
158 """Build up a dictionary of data values to use when rendering the page.
159
160 Args:
161 mr: commonly used info parsed from the request.
162
163 Returns:
164 Dict of values used by EZT for rendering the page.
165 """
166 page_data = super(AdminLabels, self).GatherPageData(mr)
167 config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
168 field_def_views = [
169 tracker_views.FieldDefView(fd, config)
170 # TODO(jrobbins): future field-level view restrictions.
171 for fd in config.field_defs
172 if not fd.is_deleted]
173 page_data.update({
174 'field_defs': field_def_views,
175 })
176 return page_data
177
178 def ProcessSubtabForm(self, post_data, mr):
179 """Process changes to labels and custom field definitions.
180
181 Args:
182 post_data: HTML form data for the HTTP request being processed.
183 mr: commonly used info parsed from the request.
184
185 Returns:
186 The URL of the page to show after processing.
187 """
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100188 if not permissions.CanEditProjectConfig(mr, self.services):
Copybara854996b2021-09-07 19:36:02 +0000189 raise permissions.PermissionException(
190 'Only project owners may edit the label definitions')
191
192 wkl_text = post_data.get('predefinedlabels', '')
193 wkl_matches = framework_constants.IDENTIFIER_DOCSTRING_RE.findall(wkl_text)
194 wkl_tuples = [
195 (label.lstrip('#'), docstring.strip(), label.startswith('#'))
196 for label, docstring in wkl_matches]
197 if not wkl_tuples:
198 mr.errors.label_defs = 'A project cannot have zero labels'
199 label_counter = collections.Counter(wkl[0].lower() for wkl in wkl_tuples)
200 for lab, count in label_counter.items():
201 if count > 1:
202 mr.errors.label_defs = 'Duplicate label: %s' % lab
203
204 config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
205 field_names = [fd.field_name for fd in config.field_defs
206 if fd.field_type is tracker_pb2.FieldTypes.ENUM_TYPE
207 and not fd.is_deleted]
208 masked_labels = tracker_helpers.LabelsMaskedByFields(config, field_names)
209 field_names_lower = [field_name.lower() for field_name in field_names]
210 for wkl in wkl_tuples:
211 conflict = tracker_bizobj.LabelIsMaskedByField(wkl[0], field_names_lower)
212 if conflict:
213 mr.errors.label_defs = (
214 'Label "%s" should be defined in enum "%s"' % (wkl[0], conflict))
215 wkl_tuples.extend([
216 (masked.name, masked.docstring, False) for masked in masked_labels])
217
218 excl_prefix_text = post_data.get('excl_prefixes', '')
219 excl_prefixes = framework_constants.IDENTIFIER_RE.findall(excl_prefix_text)
220
221 if mr.errors.AnyErrors():
222 self.PleaseCorrect(mr, labels_text=wkl_text)
223 return
224
225 self.services.config.UpdateConfig(
226 mr.cnxn, mr.project,
227 well_known_labels=wkl_tuples, excl_label_prefixes=excl_prefixes)
228
229 # TODO(jrobbins): define a "strict" mode that affects only labels.
230
231 return urls.ADMIN_LABELS
232
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100233 def GetAdminLabelsPage(self, **kwargs):
234 return self.handler(**kwargs)
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200235
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100236 def PostAdminLabelsPage(self, **kwargs):
237 return self.handler(**kwargs)
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200238
Copybara854996b2021-09-07 19:36:02 +0000239
240class AdminTemplates(IssueAdminBase):
241 """Servlet allowing project owners to configure templates."""
242
243 _PAGE_TEMPLATE = 'tracker/admin-templates-page.ezt'
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100244 _PROCESS_SUBTAB = servlet.Servlet.PROCESS_TAB_TEMPLATES
Copybara854996b2021-09-07 19:36:02 +0000245
246 def GatherPageData(self, mr):
247 """Build up a dictionary of data values to use when rendering the page.
248
249 Args:
250 mr: commonly used info parsed from the request.
251
252 Returns:
253 Dict of values used by EZT for rendering the page.
254 """
255 return super(AdminTemplates, self).GatherPageData(mr)
256
257 def ProcessSubtabForm(self, post_data, mr):
258 """Process changes to new issue templates.
259
260 Args:
261 post_data: HTML form data for the HTTP request being processed.
262 mr: commonly used info parsed from the request.
263
264 Returns:
265 The URL of the page to show after processing.
266 """
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100267 if not permissions.CanEditProjectConfig(mr, self.services):
Copybara854996b2021-09-07 19:36:02 +0000268 raise permissions.PermissionException(
269 'Only project owners may edit the default templates')
270
271 config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
272
273 templates = self.services.template.GetProjectTemplates(mr.cnxn,
274 config.project_id)
275 default_template_id_for_developers, default_template_id_for_users = (
276 self._ParseDefaultTemplateSelections(post_data, templates))
277 if default_template_id_for_developers or default_template_id_for_users:
278 self.services.config.UpdateConfig(
279 mr.cnxn, mr.project,
280 default_template_for_developers=default_template_id_for_developers,
281 default_template_for_users=default_template_id_for_users)
282
283 return urls.ADMIN_TEMPLATES
284
285 def _ParseDefaultTemplateSelections(self, post_data, templates):
286 """Parse the input for the default templates to offer users."""
287 def GetSelectedTemplateID(name):
288 """Find the ID of the template specified in post_data[name]."""
289 if name not in post_data:
290 return None
291 selected_template_name = post_data[name]
292 for template in templates:
293 if selected_template_name == template.name:
294 return template.template_id
295
296 logging.error('User somehow selected an invalid template: %r',
297 selected_template_name)
298 return None
299
300 return (GetSelectedTemplateID('default_template_for_developers'),
301 GetSelectedTemplateID('default_template_for_users'))
302
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100303 def GetAdminTemplatesPage(self, **kwargs):
304 return self.handler(**kwargs)
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200305
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100306 def PostAdminTemplatesPage(self, **kwargs):
307 return self.handler(**kwargs)
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200308
Copybara854996b2021-09-07 19:36:02 +0000309
310class AdminComponents(IssueAdminBase):
311 """Servlet allowing project owners to view the list of components."""
312
313 _PAGE_TEMPLATE = 'tracker/admin-components-page.ezt'
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100314 _PROCESS_SUBTAB = servlet.Servlet.PROCESS_TAB_COMPONENTS
Copybara854996b2021-09-07 19:36:02 +0000315
316 def GatherPageData(self, mr):
317 """Build up a dictionary of data values to use when rendering the page.
318
319 Args:
320 mr: commonly used info parsed from the request.
321
322 Returns:
323 Dict of values used by EZT for rendering the page.
324 """
325 page_data = super(AdminComponents, self).GatherPageData(mr)
326 config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
327 users_by_id = framework_views.MakeAllUserViews(
328 mr.cnxn, self.services.user,
329 *[list(cd.admin_ids) + list(cd.cc_ids)
330 for cd in config.component_defs])
331 framework_views.RevealAllEmailsToMembers(
332 mr.cnxn, self.services, mr.auth, users_by_id, mr.project)
333 component_def_views = [
334 tracker_views.ComponentDefView(mr.cnxn, self.services, cd, users_by_id)
335 # TODO(jrobbins): future component-level view restrictions.
336 for cd in config.component_defs]
337 for cd in component_def_views:
338 if mr.auth.email in [user.email for user in cd.admins]:
339 cd.classes += 'myadmin '
340 if mr.auth.email in [user.email for user in cd.cc]:
341 cd.classes += 'mycc '
342
343 page_data.update({
344 'component_defs': component_def_views,
345 'failed_perm': mr.GetParam('failed_perm'),
346 'failed_subcomp': mr.GetParam('failed_subcomp'),
347 'failed_templ': mr.GetParam('failed_templ'),
348 })
349 return page_data
350
351 def _GetComponentDefs(self, _mr, post_data, config):
352 """Get the config and component definitions from the request."""
353 component_defs = []
354 component_paths = post_data.get('delete_components').split(',')
355 for component_path in component_paths:
356 component_def = tracker_bizobj.FindComponentDef(component_path, config)
357 component_defs.append(component_def)
358 return component_defs
359
360 def _ProcessDeleteComponent(self, mr, component_def):
361 """Delete the specified component and its references."""
362 self.services.issue.DeleteComponentReferences(
363 mr.cnxn, component_def.component_id)
364 self.services.config.DeleteComponentDef(
365 mr.cnxn, mr.project_id, component_def.component_id)
366
367 def ProcessFormData(self, mr, post_data):
368 """Processes a POST command to delete components.
369
370 Args:
371 mr: commonly used info parsed from the request.
372 post_data: HTML form data from the request.
373
374 Returns:
375 String URL to redirect the user to, or None if response was already sent.
376 """
377 config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
378 component_defs = self._GetComponentDefs(mr, post_data, config)
379 # Reverse the component_defs so that we start deleting from subcomponents.
380 component_defs.reverse()
381
382 # Collect errors.
383 perm_errors = []
384 subcomponents_errors = []
385 templates_errors = []
386 # Collect successes.
387 deleted_components = []
388
389 for component_def in component_defs:
390 allow_edit = permissions.CanEditComponentDef(
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100391 mr, self.services, component_def, config)
Copybara854996b2021-09-07 19:36:02 +0000392 if not allow_edit:
393 perm_errors.append(component_def.path)
394
395 subcomponents = tracker_bizobj.FindDescendantComponents(
396 config, component_def)
397 if subcomponents:
398 subcomponents_errors.append(component_def.path)
399
400 templates = self.services.template.TemplatesWithComponent(
401 mr.cnxn, component_def.component_id)
402 if templates:
403 templates_errors.append(component_def.path)
404
405 allow_delete = allow_edit and not subcomponents and not templates
406 if allow_delete:
407 self._ProcessDeleteComponent(mr, component_def)
408 deleted_components.append(component_def.path)
409 # Refresh project config after the component deletion.
410 config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
411
412 return framework_helpers.FormatAbsoluteURL(
413 mr, urls.ADMIN_COMPONENTS, ts=int(time.time()),
414 failed_perm=','.join(perm_errors),
415 failed_subcomp=','.join(subcomponents_errors),
416 failed_templ=','.join(templates_errors),
417 deleted=','.join(deleted_components))
418
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100419 def GetAdminComponentsPage(self, **kwargs):
420 return self.handler(**kwargs)
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200421
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100422 def PostAdminComponentsPage(self, **kwargs):
423 return self.handler(**kwargs)
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200424
Copybara854996b2021-09-07 19:36:02 +0000425
426class AdminViews(IssueAdminBase):
427 """Servlet for project owners to set default columns, axes, and sorting."""
428
429 _PAGE_TEMPLATE = 'tracker/admin-views-page.ezt'
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100430 _PROCESS_SUBTAB = servlet.Servlet.PROCESS_TAB_VIEWS
Copybara854996b2021-09-07 19:36:02 +0000431
432 def GatherPageData(self, mr):
433 """Build up a dictionary of data values to use when rendering the page.
434
435 Args:
436 mr: commonly used info parsed from the request.
437
438 Returns:
439 Dict of values used by EZT for rendering the page.
440 """
441 page_data = super(AdminViews, self).GatherPageData(mr)
442 with mr.profiler.Phase('getting canned queries'):
443 canned_queries = self.services.features.GetCannedQueriesByProjectID(
444 mr.cnxn, mr.project_id)
445 canned_query_views = [
446 savedqueries_helpers.SavedQueryView(sq, idx + 1, None, None)
447 for idx, sq in enumerate(canned_queries)]
448
449 page_data.update({
450 'canned_queries': canned_query_views,
451 'new_query_indexes': list(range(
452 len(canned_queries) + 1, savedqueries_helpers.MAX_QUERIES + 1)),
453 'issue_notify': mr.project.issue_notify_address,
454 'max_queries': savedqueries_helpers.MAX_QUERIES,
455 })
456 return page_data
457
458 def ProcessSubtabForm(self, post_data, mr):
459 """Process the Views subtab.
460
461 Args:
462 post_data: HTML form data for the HTTP request being processed.
463 mr: commonly used info parsed from the request.
464
465 Returns:
466 The URL of the page to show after processing.
467 """
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100468 if not permissions.CanEditProjectConfig(mr, self.services):
Copybara854996b2021-09-07 19:36:02 +0000469 raise permissions.PermissionException(
470 'Only project owners may edit the default views')
471 existing_queries = savedqueries_helpers.ParseSavedQueries(
472 mr.cnxn, post_data, self.services.project)
473 added_queries = savedqueries_helpers.ParseSavedQueries(
474 mr.cnxn, post_data, self.services.project, prefix='new_')
475 canned_queries = existing_queries + added_queries
476
477 list_prefs = _ParseListPreferences(post_data)
478
479 if mr.errors.AnyErrors():
480 self.PleaseCorrect(mr)
481 return
482
483 self.services.config.UpdateConfig(
484 mr.cnxn, mr.project, list_prefs=list_prefs)
485 self.services.features.UpdateCannedQueries(
486 mr.cnxn, mr.project_id, canned_queries)
487
488 return urls.ADMIN_VIEWS
489
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100490 def GetAdminViewsPage(self, **kwargs):
491 return self.handler(**kwargs)
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200492
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100493 def PostAdminViewsPage(self, **kwargs):
494 return self.handler(**kwargs)
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200495
Copybara854996b2021-09-07 19:36:02 +0000496
497def _ParseListPreferences(post_data):
498 """Parse the part of a project admin form about artifact list preferences."""
499 default_col_spec = ''
500 if 'default_col_spec' in post_data:
501 default_col_spec = post_data['default_col_spec']
502 # Don't allow empty colum spec
503 if not default_col_spec:
504 default_col_spec = tracker_constants.DEFAULT_COL_SPEC
505 col_spec_words = monorailrequest.ParseColSpec(
506 default_col_spec, max_parts=framework_constants.MAX_COL_PARTS)
507 col_spec = ' '.join(word for word in col_spec_words)
508
509 default_sort_spec = ''
510 if 'default_sort_spec' in post_data:
511 default_sort_spec = post_data['default_sort_spec']
512 sort_spec_words = monorailrequest.ParseColSpec(default_sort_spec)
513 sort_spec = ' '.join(sort_spec_words)
514
515 x_attr_str = ''
516 if 'default_x_attr' in post_data:
517 x_attr_str = post_data['default_x_attr']
518 x_attr_words = monorailrequest.ParseColSpec(x_attr_str)
519 x_attr = ''
520 if x_attr_words:
521 x_attr = x_attr_words[0]
522
523 y_attr_str = ''
524 if 'default_y_attr' in post_data:
525 y_attr_str = post_data['default_y_attr']
526 y_attr_words = monorailrequest.ParseColSpec(y_attr_str)
527 y_attr = ''
528 if y_attr_words:
529 y_attr = y_attr_words[0]
530
531 member_default_query = ''
532 if 'member_default_query' in post_data:
533 member_default_query = post_data['member_default_query']
534
535 return col_spec, sort_spec, x_attr, y_attr, member_default_query
536
537
538class AdminRules(IssueAdminBase):
539 """Servlet allowing project owners to configure filter rules."""
540
541 _PAGE_TEMPLATE = 'tracker/admin-rules-page.ezt'
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100542 _PROCESS_SUBTAB = servlet.Servlet.PROCESS_TAB_RULES
Copybara854996b2021-09-07 19:36:02 +0000543
544 def AssertBasePermission(self, mr):
545 """Check whether the user has any permission to visit this page.
546
547 Args:
548 mr: commonly used info parsed from the request.
549 """
550 super(AdminRules, self).AssertBasePermission(mr)
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100551 if not permissions.CanEditProjectConfig(mr, self.services):
Copybara854996b2021-09-07 19:36:02 +0000552 raise permissions.PermissionException(
553 'User is not allowed to administer this project')
554
555 def GatherPageData(self, mr):
556 """Build up a dictionary of data values to use when rendering the page.
557
558 Args:
559 mr: commonly used info parsed from the request.
560
561 Returns:
562 Dict of values used by EZT for rendering the page.
563 """
564 page_data = super(AdminRules, self).GatherPageData(mr)
565 rules = self.services.features.GetFilterRules(
566 mr.cnxn, mr.project_id)
567 users_by_id = framework_views.MakeAllUserViews(
568 mr.cnxn, self.services.user,
569 [rule.default_owner_id for rule in rules],
570 *[rule.add_cc_ids for rule in rules])
571 framework_views.RevealAllEmailsToMembers(
572 mr.cnxn, self.services, mr.auth, users_by_id, mr.project)
573 rule_views = [filterrules_views.RuleView(rule, users_by_id)
574 for rule in rules]
575
576 for idx, rule_view in enumerate(rule_views):
577 rule_view.idx = idx + 1 # EZT has no loop index, so we set idx.
578
579 page_data.update({
580 'rules': rule_views,
581 'new_rule_indexes': (
582 list(range(len(rules) + 1, filterrules_helpers.MAX_RULES + 1))),
583 'max_rules': filterrules_helpers.MAX_RULES,
584 })
585 return page_data
586
587 def ProcessSubtabForm(self, post_data, mr):
588 """Process the Rules subtab.
589
590 Args:
591 post_data: HTML form data for the HTTP request being processed.
592 mr: commonly used info parsed from the request.
593
594 Returns:
595 The URL of the page to show after processing.
596 """
597 old_rules = self.services.features.GetFilterRules(mr.cnxn, mr.project_id)
598 rules = filterrules_helpers.ParseRules(
599 mr.cnxn, post_data, self.services.user, mr.errors)
600 new_rules = filterrules_helpers.ParseRules(
601 mr.cnxn, post_data, self.services.user, mr.errors, prefix='new_')
602 rules.extend(new_rules)
603
604 if mr.errors.AnyErrors():
605 self.PleaseCorrect(mr)
606 return
607
608 config = self.services.features.UpdateFilterRules(
609 mr.cnxn, mr.project_id, rules)
610
611 if old_rules != rules:
612 logging.info('recomputing derived fields')
613 config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
614 filterrules_helpers.RecomputeAllDerivedFields(
615 mr.cnxn, self.services, mr.project, config)
616
617 return urls.ADMIN_RULES
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200618
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100619 def GetAdminRulesPage(self, **kwargs):
620 return self.handler(**kwargs)
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200621
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100622 def PostAdminRulesPage(self, **kwargs):
623 return self.handler(**kwargs)