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

Side by Side Diff: appengine/monorail/tracker/field_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
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 """Helper functions for custom field sevlets."""
7
8 import collections
9 import logging
10 import re
11
12 from framework import framework_bizobj
13 from framework import framework_constants
14 from framework import monorailrequest
15 from framework import permissions
16 from proto import tracker_pb2
17 from services import config_svc
18 from services import user_svc
19 from tracker import tracker_bizobj
20
21
22 INVALID_USER_ID = -1
23
24 ParsedFieldDef = collections.namedtuple(
25 'ParsedFieldDef',
26 'field_name, field_type_str, min_value, max_value, regex, '
27 'needs_member, needs_perm, grants_perm, notify_on, is_required, '
28 'is_multivalued, field_docstring, choices_text, applicable_type, '
29 'applicable_predicate, revised_labels')
30
31
32 def ParseFieldDefRequest(post_data, config):
33 """Parse the user's HTML form data to update a field definition."""
34 field_name = post_data.get('name', '')
35 field_type_str = post_data.get('field_type')
36 # TODO(jrobbins): once a min or max is set, it cannot be completely removed.
37 min_value_str = post_data.get('min_value')
38 try:
39 min_value = int(min_value_str)
40 except (ValueError, TypeError):
41 min_value = None
42 max_value_str = post_data.get('max_value')
43 try:
44 max_value = int(max_value_str)
45 except (ValueError, TypeError):
46 max_value = None
47 regex = post_data.get('regex')
48 needs_member = 'needs_member' in post_data
49 needs_perm = post_data.get('needs_perm', '').strip()
50 grants_perm = post_data.get('grants_perm', '').strip()
51 notify_on_str = post_data.get('notify_on')
52 if notify_on_str in config_svc.NOTIFY_ON_ENUM:
53 notify_on = config_svc.NOTIFY_ON_ENUM.index(notify_on_str)
54 else:
55 notify_on = 0
56 is_required = 'is_required' in post_data
57 is_multivalued = 'is_multivalued' in post_data
58 field_docstring = post_data.get('docstring', '')
59 choices_text = post_data.get('choices', '')
60 applicable_type = post_data.get('applicable_type', '')
61 applicable_predicate = '' # TODO(jrobbins): placeholder for future feature
62 revised_labels = _ParseChoicesIntoWellKnownLabels(
63 choices_text, field_name, config)
64
65 return ParsedFieldDef(
66 field_name, field_type_str, min_value, max_value, regex,
67 needs_member, needs_perm, grants_perm, notify_on, is_required,
68 is_multivalued, field_docstring, choices_text, applicable_type,
69 applicable_predicate, revised_labels)
70
71
72 def _ParseChoicesIntoWellKnownLabels(choices_text, field_name, config):
73 """Parse a field's possible choices and integrate them into the config.
74
75 Args:
76 choices_text: string with one label and optional docstring per line.
77 field_name: string name of the field definition being edited.
78 config: ProjectIssueConfig PB of the current project.
79
80 Returns:
81 A revised list of labels that can be used to update the config.
82 """
83 matches = framework_constants.IDENTIFIER_DOCSTRING_RE.findall(choices_text)
84 new_labels = [
85 ('%s-%s' % (field_name, label), choice_docstring.strip(), False)
86 for label, choice_docstring in matches]
87 kept_labels = [
88 (wkl.label, wkl.label_docstring, False)
89 for wkl in config.well_known_labels
90 if not tracker_bizobj.LabelIsMaskedByField(
91 wkl.label, [field_name.lower()])]
92 revised_labels = kept_labels + new_labels
93 return revised_labels
94
95
96 def ShiftEnumFieldsIntoLabels(
97 labels, labels_remove, field_val_strs, field_val_strs_remove, config):
98 """Look at the custom field values and treat enum fields as labels.
99
100 Args:
101 labels: list of labels to add/set on the issue.
102 labels_remove: list of labels to remove from the issue.
103 field_val_strs: {field_id: [val_str, ...]} of custom fields to add/set.
104 field_val_strs_remove: {field_id: [val_str, ...]} of custom fields to
105 remove.
106 config: ProjectIssueConfig PB including custom field definitions.
107
108 SIDE-EFFECT: the labels and labels_remove lists will be extended with
109 key-value labels corresponding to the enum field values. Those field
110 entries will be removed from field_vals and field_vals_remove.
111 """
112 for fd in config.field_defs:
113 if fd.field_type != tracker_pb2.FieldTypes.ENUM_TYPE:
114 continue
115
116 if fd.field_id in field_val_strs:
117 labels.extend(
118 '%s-%s' % (fd.field_name, val)
119 for val in field_val_strs[fd.field_id]
120 if val and val != '--')
121 del field_val_strs[fd.field_id]
122
123 if fd.field_id in field_val_strs_remove:
124 labels_remove.extend(
125 '%s-%s' % (fd.field_name, val)
126 for val in field_val_strs_remove[fd.field_id]
127 if val and val != '--')
128 del field_val_strs_remove[fd.field_id]
129
130
131 def _ParseOneFieldValue(cnxn, user_service, fd, val_str):
132 """Make one FieldValue PB from the given user-supplied string."""
133 if fd.field_type == tracker_pb2.FieldTypes.INT_TYPE:
134 try:
135 return tracker_bizobj.MakeFieldValue(
136 fd.field_id, int(val_str), None, None, False)
137 except ValueError:
138 return None # TODO(jrobbins): should bounce
139
140 elif fd.field_type == tracker_pb2.FieldTypes.STR_TYPE:
141 return tracker_bizobj.MakeFieldValue(
142 fd.field_id, None, val_str, None, False)
143
144 elif fd.field_type == tracker_pb2.FieldTypes.USER_TYPE:
145 if val_str:
146 try:
147 user_id = user_service.LookupUserID(cnxn, val_str, autocreate=False)
148 except user_svc.NoSuchUserException:
149 # Set to invalid user ID to display error during the validation step.
150 user_id = INVALID_USER_ID
151 return tracker_bizobj.MakeFieldValue(
152 fd.field_id, None, None, user_id, False)
153 else:
154 return None
155
156 else:
157 logging.error('Cant parse field with unexpected type %r', fd.field_type)
158 return None
159
160
161 def ParseFieldValues(cnxn, user_service, field_val_strs, config):
162 """Return a list of FieldValue PBs based on the the given dict of strings."""
163 field_values = []
164 for fd in config.field_defs:
165 if fd.field_id not in field_val_strs:
166 continue
167 for val_str in field_val_strs[fd.field_id]:
168 fv = _ParseOneFieldValue(cnxn, user_service, fd, val_str)
169 if fv:
170 field_values.append(fv)
171
172 return field_values
173
174
175 def _ValidateOneCustomField(mr, services, field_def, field_val):
176 """Validate one custom field value and return an error string or None."""
177 if field_def.field_type == tracker_pb2.FieldTypes.INT_TYPE:
178 if (field_def.min_value is not None and
179 field_val.int_value < field_def.min_value):
180 return 'Value must be >= %d' % field_def.min_value
181 if (field_def.max_value is not None and
182 field_val.int_value > field_def.max_value):
183 return 'Value must be <= %d' % field_def.max_value
184
185 elif field_def.field_type == tracker_pb2.FieldTypes.STR_TYPE:
186 if field_def.regex and field_val.str_value:
187 try:
188 regex = re.compile(field_def.regex)
189 if not regex.match(field_val.str_value):
190 return 'Value must match regular expression: %s' % field_def.regex
191 except re.error:
192 logging.info('Failed to process regex %r with value %r. Allowing.',
193 field_def.regex, field_val.str_value)
194 return None
195
196 elif field_def.field_type == tracker_pb2.FieldTypes.USER_TYPE:
197 if field_val.user_id == INVALID_USER_ID:
198 return 'User not found'
199 if field_def.needs_member:
200 auth = monorailrequest.AuthData.FromUserID(
201 mr.cnxn, field_val.user_id, services)
202 user_value_in_project = framework_bizobj.UserIsInProject(
203 mr.project, auth.effective_ids)
204 if not user_value_in_project:
205 return 'User must be a member of the project'
206 if field_def.needs_perm:
207 field_val_user = services.user.GetUser(mr.cnxn, field_val.user_id)
208 user_perms = permissions.GetPermissions(
209 field_val_user, auth.effective_ids, mr.project)
210 has_perm = user_perms.CanUsePerm(
211 field_def.needs_perm, auth.effective_ids, mr.project, [])
212 if not has_perm:
213 return 'User must have permission "%s"' % field_def.needs_perm
214
215 return None
216
217
218 def ValidateCustomFields(mr, services, field_values, config, errors):
219 """Validate each of the given fields and report problems in errors object."""
220 for fv in field_values:
221 fd = tracker_bizobj.FindFieldDefByID(fv.field_id, config)
222 if fd:
223 err_msg = _ValidateOneCustomField(mr, services, fd, fv)
224 if err_msg:
225 errors.SetCustomFieldError(fv.field_id, err_msg)
OLDNEW
« no previous file with comments | « appengine/monorail/tracker/componentdetail.py ('k') | appengine/monorail/tracker/fieldcreate.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698