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

Unified 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « appengine/monorail/tracker/componentdetail.py ('k') | appengine/monorail/tracker/fieldcreate.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: appengine/monorail/tracker/field_helpers.py
diff --git a/appengine/monorail/tracker/field_helpers.py b/appengine/monorail/tracker/field_helpers.py
new file mode 100644
index 0000000000000000000000000000000000000000..452f329df6a6fec48af933aa6ca9fae113ffef0e
--- /dev/null
+++ b/appengine/monorail/tracker/field_helpers.py
@@ -0,0 +1,225 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is govered by a BSD-style
+# license that can be found in the LICENSE file or at
+# https://developers.google.com/open-source/licenses/bsd
+
+"""Helper functions for custom field sevlets."""
+
+import collections
+import logging
+import re
+
+from framework import framework_bizobj
+from framework import framework_constants
+from framework import monorailrequest
+from framework import permissions
+from proto import tracker_pb2
+from services import config_svc
+from services import user_svc
+from tracker import tracker_bizobj
+
+
+INVALID_USER_ID = -1
+
+ParsedFieldDef = collections.namedtuple(
+ 'ParsedFieldDef',
+ 'field_name, field_type_str, min_value, max_value, regex, '
+ 'needs_member, needs_perm, grants_perm, notify_on, is_required, '
+ 'is_multivalued, field_docstring, choices_text, applicable_type, '
+ 'applicable_predicate, revised_labels')
+
+
+def ParseFieldDefRequest(post_data, config):
+ """Parse the user's HTML form data to update a field definition."""
+ field_name = post_data.get('name', '')
+ field_type_str = post_data.get('field_type')
+ # TODO(jrobbins): once a min or max is set, it cannot be completely removed.
+ min_value_str = post_data.get('min_value')
+ try:
+ min_value = int(min_value_str)
+ except (ValueError, TypeError):
+ min_value = None
+ max_value_str = post_data.get('max_value')
+ try:
+ max_value = int(max_value_str)
+ except (ValueError, TypeError):
+ max_value = None
+ regex = post_data.get('regex')
+ needs_member = 'needs_member' in post_data
+ needs_perm = post_data.get('needs_perm', '').strip()
+ grants_perm = post_data.get('grants_perm', '').strip()
+ notify_on_str = post_data.get('notify_on')
+ if notify_on_str in config_svc.NOTIFY_ON_ENUM:
+ notify_on = config_svc.NOTIFY_ON_ENUM.index(notify_on_str)
+ else:
+ notify_on = 0
+ is_required = 'is_required' in post_data
+ is_multivalued = 'is_multivalued' in post_data
+ field_docstring = post_data.get('docstring', '')
+ choices_text = post_data.get('choices', '')
+ applicable_type = post_data.get('applicable_type', '')
+ applicable_predicate = '' # TODO(jrobbins): placeholder for future feature
+ revised_labels = _ParseChoicesIntoWellKnownLabels(
+ choices_text, field_name, config)
+
+ return ParsedFieldDef(
+ field_name, field_type_str, min_value, max_value, regex,
+ needs_member, needs_perm, grants_perm, notify_on, is_required,
+ is_multivalued, field_docstring, choices_text, applicable_type,
+ applicable_predicate, revised_labels)
+
+
+def _ParseChoicesIntoWellKnownLabels(choices_text, field_name, config):
+ """Parse a field's possible choices and integrate them into the config.
+
+ Args:
+ choices_text: string with one label and optional docstring per line.
+ field_name: string name of the field definition being edited.
+ config: ProjectIssueConfig PB of the current project.
+
+ Returns:
+ A revised list of labels that can be used to update the config.
+ """
+ matches = framework_constants.IDENTIFIER_DOCSTRING_RE.findall(choices_text)
+ new_labels = [
+ ('%s-%s' % (field_name, label), choice_docstring.strip(), False)
+ for label, choice_docstring in matches]
+ kept_labels = [
+ (wkl.label, wkl.label_docstring, False)
+ for wkl in config.well_known_labels
+ if not tracker_bizobj.LabelIsMaskedByField(
+ wkl.label, [field_name.lower()])]
+ revised_labels = kept_labels + new_labels
+ return revised_labels
+
+
+def ShiftEnumFieldsIntoLabels(
+ labels, labels_remove, field_val_strs, field_val_strs_remove, config):
+ """Look at the custom field values and treat enum fields as labels.
+
+ Args:
+ labels: list of labels to add/set on the issue.
+ labels_remove: list of labels to remove from the issue.
+ field_val_strs: {field_id: [val_str, ...]} of custom fields to add/set.
+ field_val_strs_remove: {field_id: [val_str, ...]} of custom fields to
+ remove.
+ config: ProjectIssueConfig PB including custom field definitions.
+
+ SIDE-EFFECT: the labels and labels_remove lists will be extended with
+ key-value labels corresponding to the enum field values. Those field
+ entries will be removed from field_vals and field_vals_remove.
+ """
+ for fd in config.field_defs:
+ if fd.field_type != tracker_pb2.FieldTypes.ENUM_TYPE:
+ continue
+
+ if fd.field_id in field_val_strs:
+ labels.extend(
+ '%s-%s' % (fd.field_name, val)
+ for val in field_val_strs[fd.field_id]
+ if val and val != '--')
+ del field_val_strs[fd.field_id]
+
+ if fd.field_id in field_val_strs_remove:
+ labels_remove.extend(
+ '%s-%s' % (fd.field_name, val)
+ for val in field_val_strs_remove[fd.field_id]
+ if val and val != '--')
+ del field_val_strs_remove[fd.field_id]
+
+
+def _ParseOneFieldValue(cnxn, user_service, fd, val_str):
+ """Make one FieldValue PB from the given user-supplied string."""
+ if fd.field_type == tracker_pb2.FieldTypes.INT_TYPE:
+ try:
+ return tracker_bizobj.MakeFieldValue(
+ fd.field_id, int(val_str), None, None, False)
+ except ValueError:
+ return None # TODO(jrobbins): should bounce
+
+ elif fd.field_type == tracker_pb2.FieldTypes.STR_TYPE:
+ return tracker_bizobj.MakeFieldValue(
+ fd.field_id, None, val_str, None, False)
+
+ elif fd.field_type == tracker_pb2.FieldTypes.USER_TYPE:
+ if val_str:
+ try:
+ user_id = user_service.LookupUserID(cnxn, val_str, autocreate=False)
+ except user_svc.NoSuchUserException:
+ # Set to invalid user ID to display error during the validation step.
+ user_id = INVALID_USER_ID
+ return tracker_bizobj.MakeFieldValue(
+ fd.field_id, None, None, user_id, False)
+ else:
+ return None
+
+ else:
+ logging.error('Cant parse field with unexpected type %r', fd.field_type)
+ return None
+
+
+def ParseFieldValues(cnxn, user_service, field_val_strs, config):
+ """Return a list of FieldValue PBs based on the the given dict of strings."""
+ field_values = []
+ for fd in config.field_defs:
+ if fd.field_id not in field_val_strs:
+ continue
+ for val_str in field_val_strs[fd.field_id]:
+ fv = _ParseOneFieldValue(cnxn, user_service, fd, val_str)
+ if fv:
+ field_values.append(fv)
+
+ return field_values
+
+
+def _ValidateOneCustomField(mr, services, field_def, field_val):
+ """Validate one custom field value and return an error string or None."""
+ if field_def.field_type == tracker_pb2.FieldTypes.INT_TYPE:
+ if (field_def.min_value is not None and
+ field_val.int_value < field_def.min_value):
+ return 'Value must be >= %d' % field_def.min_value
+ if (field_def.max_value is not None and
+ field_val.int_value > field_def.max_value):
+ return 'Value must be <= %d' % field_def.max_value
+
+ elif field_def.field_type == tracker_pb2.FieldTypes.STR_TYPE:
+ if field_def.regex and field_val.str_value:
+ try:
+ regex = re.compile(field_def.regex)
+ if not regex.match(field_val.str_value):
+ return 'Value must match regular expression: %s' % field_def.regex
+ except re.error:
+ logging.info('Failed to process regex %r with value %r. Allowing.',
+ field_def.regex, field_val.str_value)
+ return None
+
+ elif field_def.field_type == tracker_pb2.FieldTypes.USER_TYPE:
+ if field_val.user_id == INVALID_USER_ID:
+ return 'User not found'
+ if field_def.needs_member:
+ auth = monorailrequest.AuthData.FromUserID(
+ mr.cnxn, field_val.user_id, services)
+ user_value_in_project = framework_bizobj.UserIsInProject(
+ mr.project, auth.effective_ids)
+ if not user_value_in_project:
+ return 'User must be a member of the project'
+ if field_def.needs_perm:
+ field_val_user = services.user.GetUser(mr.cnxn, field_val.user_id)
+ user_perms = permissions.GetPermissions(
+ field_val_user, auth.effective_ids, mr.project)
+ has_perm = user_perms.CanUsePerm(
+ field_def.needs_perm, auth.effective_ids, mr.project, [])
+ if not has_perm:
+ return 'User must have permission "%s"' % field_def.needs_perm
+
+ return None
+
+
+def ValidateCustomFields(mr, services, field_values, config, errors):
+ """Validate each of the given fields and report problems in errors object."""
+ for fv in field_values:
+ fd = tracker_bizobj.FindFieldDefByID(fv.field_id, config)
+ if fd:
+ err_msg = _ValidateOneCustomField(mr, services, fd, fv)
+ if err_msg:
+ errors.SetCustomFieldError(fv.field_id, err_msg)
« 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