Merge branch 'main' into avm99963-monorail

Merged commit 34d8229ae2b51fb1a15bd208e6fe6185c94f6266

GitOrigin-RevId: 7ee0917f93a577e475f8e09526dd144d245593f4
diff --git a/mrproto/tracker_pb2.py b/mrproto/tracker_pb2.py
new file mode 100644
index 0000000..f5820e6
--- /dev/null
+++ b/mrproto/tracker_pb2.py
@@ -0,0 +1,553 @@
+# Copyright 2016 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""The Monorail issue tracker uses ProtoRPC for storing business objects."""
+
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+from protorpc import messages
+
+
+class FieldValue(messages.Message):
+  """Holds a single custom field value in an issue.
+
+  Multi-valued custom fields will have multiple such FieldValues on a given
+  issue. Note that enumerated type custom fields are represented as key-value
+  labels.
+  """
+  field_id = messages.IntegerField(1, required=True)
+  # Only one of the following fields will hve any value.
+  int_value = messages.IntegerField(2)
+  str_value = messages.StringField(3)
+  user_id = messages.IntegerField(4)
+  date_value = messages.IntegerField(6)
+  url_value = messages.StringField(7)
+
+  derived = messages.BooleanField(5, default=False)
+
+  # None if field is not a phse field.
+  phase_id = messages.IntegerField(8)
+
+
+class ApprovalStatus(messages.Enum):
+  """Statuses that an approval field could be set to."""
+  NEEDS_REVIEW = 1
+  NA = 2
+  REVIEW_REQUESTED = 3
+  REVIEW_STARTED = 4
+  NEED_INFO = 5
+  APPROVED = 6
+  NOT_APPROVED = 7
+  NOT_SET = 8
+
+
+class ApprovalValue(messages.Message):
+  """Holds a single approval field value in an issue."""
+  approval_id = messages.IntegerField(1)
+  status = messages.EnumField(ApprovalStatus, 2, default='NOT_SET')
+  setter_id = messages.IntegerField(3)
+  set_on = messages.IntegerField(4)
+  approver_ids = messages.IntegerField(5, repeated=True)
+  phase_id = messages.IntegerField(7)
+
+
+class ApprovalDelta(messages.Message):
+  """In-memory representation of requested changes to an issue's approval."""
+  status = messages.EnumField(ApprovalStatus, 1)
+  set_on = messages.IntegerField(2)
+  setter_id = messages.IntegerField(3)
+  approver_ids_add = messages.IntegerField(4, repeated=True)
+  approver_ids_remove = messages.IntegerField(5, repeated=True)
+  subfield_vals_add = messages.MessageField(FieldValue, 6, repeated=True)
+  subfield_vals_remove = messages.MessageField(FieldValue, 7, repeated=True)
+  subfields_clear = messages.IntegerField(8, repeated=True)
+  # Stores Approval's Enum subfield changes.
+  labels_add = messages.StringField(9, repeated=True)
+  labels_remove = messages.StringField(10, repeated=True)
+
+
+class Phase(messages.Message):
+  """Holds a single launch review phase."""
+  phase_id = messages.IntegerField(1)
+  name = messages.StringField(2)
+  rank = messages.IntegerField(4)
+
+
+class DanglingIssueRef(messages.Message):
+  """Holds a reference to an issue on Codesite or an external tracker."""
+  project = messages.StringField(1, required=True)
+  issue_id = messages.IntegerField(2, required=True)
+  ext_issue_identifier = messages.StringField(3, required=False)
+
+
+class Issue(messages.Message):
+  """Holds all the current metadata about an issue.
+
+  The most frequent searches can work by consulting solely the issue metadata.
+  Display of the issue list is done solely with this issue metadata.
+  Displaying one issue in detail with description and comments requires
+  more info from other objects.
+
+  The issue_id field is the unique primary key for retrieving issues.  Local ID
+  is a small integer that counts up in each project.
+
+  Summary, Status, Owner, CC, reporter, and opened_timestamp are hard
+  fields that are always there.  All other metadata is stored as
+  labels or custom fields.
+  Next available tag: 63.
+  """
+  # Globally unique issue ID.
+  issue_id = messages.IntegerField(42)
+  # project_name is not stored in the DB, only the project_id is stored.
+  # project_name is used in RAM to simplify formatting logic in lots of places.
+  project_name = messages.StringField(1, required=True)
+  project_id = messages.IntegerField(50)
+  local_id = messages.IntegerField(2, required=True)
+  summary = messages.StringField(3, default='')
+  status = messages.StringField(4, default='')
+  owner_id = messages.IntegerField(5)
+  cc_ids = messages.IntegerField(6, repeated=True)
+  labels = messages.StringField(7, repeated=True)
+  component_ids = messages.IntegerField(39, repeated=True)
+
+  # Denormalized count of stars on this Issue.
+  star_count = messages.IntegerField(8, required=True, default=0)
+  reporter_id = messages.IntegerField(9, required=True, default=0)
+  # Time that the issue was opened, in seconds since the Epoch.
+  opened_timestamp = messages.IntegerField(10, required=True, default=0)
+
+  # This should be set when an issue is closed and cleared when a
+  # closed issue is reopened.  Measured in seconds since the Epoch.
+  closed_timestamp = messages.IntegerField(12, default=0)
+
+  # This should be updated every time an issue is modified.  Measured
+  # in seconds since the Epoch.
+  modified_timestamp = messages.IntegerField(13, default=0)
+
+  # These timestamps are updated whenever owner, status, or components
+  # change, including when altered by a filter rule.
+  owner_modified_timestamp = messages.IntegerField(19, default=0)
+  status_modified_timestamp = messages.IntegerField(20, default=0)
+  component_modified_timestamp = messages.IntegerField(21, default=0)
+
+  # Enhanced version of modified_timestamp that also captures changes to
+  # subresources of issues like stars, comments, and attachments.
+  # See: go/monorail-enhanced-modified-time
+  migration_modified_timestamp = messages.IntegerField(62, default=0)
+
+  # Issue IDs of issues that this issue is blocked on.
+  blocked_on_iids = messages.IntegerField(16, repeated=True)
+
+  # Rank values of issue relations that are blocking this issue. The issue
+  # with id blocked_on_iids[i] has rank value blocked_on_ranks[i]
+  blocked_on_ranks = messages.IntegerField(54, repeated=True)
+
+  # Issue IDs of issues that this issue is blocking.
+  blocking_iids = messages.IntegerField(17, repeated=True)
+
+  # References to 'dangling' (still in codesite) issue relations.
+  dangling_blocked_on_refs = messages.MessageField(
+      DanglingIssueRef, 52, repeated=True)
+  dangling_blocking_refs = messages.MessageField(
+      DanglingIssueRef, 53, repeated=True)
+
+  # Issue ID of issue that this issue was merged into most recently.  When it
+  # is missing or 0, it is considered to be not merged into any other issue.
+  merged_into = messages.IntegerField(18)
+  # Use this when an issue is a duplicate of an issue in an external tracker.
+  merged_into_external = messages.StringField(61)
+
+  # Default derived via rules, used iff status == ''.
+  derived_status = messages.StringField(30, default='')
+  # Default derived via rules, used iff owner_id == 0.
+  derived_owner_id = messages.IntegerField(31, default=0)
+  # Additional CCs derived via rules.
+  derived_cc_ids = messages.IntegerField(32, repeated=True)
+  # Additional labels derived via rules.
+  derived_labels = messages.StringField(33, repeated=True)
+  # Additional notification email addresses derived via rules.
+  derived_notify_addrs = messages.StringField(34, repeated=True)
+  # Additional components derived via rules.
+  derived_component_ids = messages.IntegerField(40, repeated=True)
+  # Software development process warnings and errors generated by filter rules.
+  # TODO(jrobbins): these are not yet stored in the DB, they are only in RAM.
+  derived_warnings = messages.StringField(55, repeated=True)
+  derived_errors = messages.StringField(56, repeated=True)
+
+  # Soft delete of the entire issue.
+  deleted = messages.BooleanField(35, default=False)
+
+  # Total number of attachments in the issue
+  attachment_count = messages.IntegerField(36, default=0)
+
+  # Total number of comments on the issue (not counting the initial comment
+  # created when the issue is created).
+  comment_count = messages.IntegerField(37, default=0)
+
+  # Custom field values (other than enums)
+  field_values = messages.MessageField(FieldValue, 41, repeated=True)
+
+  is_spam = messages.BooleanField(51, default=False)
+  # assume_stale is used in RAM to ensure that a value saved to the DB was
+  # loaded from the DB in the same request handler (not via the cache).
+  assume_stale = messages.BooleanField(57, default=True)
+
+  phases = messages.MessageField(Phase, 59, repeated=True)
+  approval_values = messages.MessageField(ApprovalValue, 60, repeated=True)
+
+
+class FieldID(messages.Enum):
+  """Possible fields that can be updated in an Amendment."""
+  # The spelling of these names must match enum values in tracker.sql.
+  SUMMARY = 1
+  STATUS = 2
+  OWNER = 3
+  CC = 4
+  LABELS = 5
+  BLOCKEDON = 6
+  BLOCKING = 7
+  MERGEDINTO = 8
+  PROJECT = 9
+  COMPONENTS = 10
+  CUSTOM = 11
+  WARNING = 12
+  ERROR = 13
+
+
+class IssueDelta(messages.Message):
+  """In-memory representation of requested changes to an issue.
+
+  Next available tag: 23
+  """
+  status = messages.StringField(1)
+  owner_id = messages.IntegerField(2)
+  cc_ids_add = messages.IntegerField(3, repeated=True)
+  cc_ids_remove = messages.IntegerField(4, repeated=True)
+  comp_ids_add = messages.IntegerField(5, repeated=True)
+  comp_ids_remove = messages.IntegerField(6, repeated=True)
+  labels_add = messages.StringField(7, repeated=True)
+  labels_remove = messages.StringField(8, repeated=True)
+  field_vals_add = messages.MessageField(FieldValue, 9, repeated=True)
+  field_vals_remove = messages.MessageField(FieldValue, 10, repeated=True)
+  fields_clear = messages.IntegerField(11, repeated=True)
+  blocked_on_add = messages.IntegerField(12, repeated=True)
+  blocked_on_remove = messages.IntegerField(13, repeated=True)
+  blocking_add = messages.IntegerField(14, repeated=True)
+  blocking_remove = messages.IntegerField(15, repeated=True)
+  merged_into = messages.IntegerField(16)
+  merged_into_external = messages.StringField(22)
+  summary = messages.StringField(17)
+  ext_blocked_on_add = messages.StringField(18, repeated=True)
+  ext_blocked_on_remove = messages.StringField(19, repeated=True)
+  ext_blocking_add = messages.StringField(20, repeated=True)
+  ext_blocking_remove = messages.StringField(21, repeated=True)
+
+
+class Amendment(messages.Message):
+  """Holds info about one issue field change."""
+  field = messages.EnumField(FieldID, 11, required=True)
+  # User-visible string describing the change
+  newvalue = messages.StringField(12)
+  # Newvalue could have + or - characters to indicate that labels and CCs
+  # were added or removed
+  # Users added to owner or cc field
+  added_user_ids = messages.IntegerField(29, repeated=True)
+  # Users removed from owner or cc
+  removed_user_ids = messages.IntegerField(30, repeated=True)
+  custom_field_name = messages.StringField(31)
+  # When having newvalue be a +/- string doesn't make sense (e.g. status),
+  # store the old value here so that it can still be displayed.
+  oldvalue = messages.StringField(32)
+  # New Components value add to the issue
+  added_component_ids = messages.IntegerField(33, repeated=True)
+  # Old Components value removed from the issue
+  removed_component_ids = messages.IntegerField(34, repeated=True)
+
+
+class Attachment(messages.Message):
+  """Holds info about one attachment."""
+  attachment_id = messages.IntegerField(21, required=True)
+  # Client-side filename
+  filename = messages.StringField(22, required=True)
+  filesize = messages.IntegerField(23, required=True)
+  # File mime-type, or at least our best guess.
+  mimetype = messages.StringField(24, required=True)
+  deleted = messages.BooleanField(27, default=False)
+  gcs_object_id = messages.StringField(29, required=False)
+
+
+class IssueComment(messages.Message):
+  # TODO(lukasperaza): update first comment to is_description=True
+  """Holds one issue description or one additional comment on an issue.
+
+  The IssueComment with the lowest timestamp is the issue description,
+  if there is no IssueComment with is_description=True; otherwise, the
+  IssueComment with is_description=True and the highest timestamp is
+  the issue description.
+  Next available tag: 56
+  """
+  id = messages.IntegerField(32)
+  # Issue ID of the issue that was commented on.
+  issue_id = messages.IntegerField(31, required=True)
+  project_id = messages.IntegerField(50)
+  # User who entered the comment
+  user_id = messages.IntegerField(4, required=True, default=0)
+  # id of the APPROVAL_TYPE fielddef, if this is an approval comment.
+  approval_id = messages.IntegerField(54)
+  # Time when comment was entered (seconds).
+  timestamp = messages.IntegerField(5, required=True)
+  # Text of the comment
+  content = messages.StringField(6, required=True)
+  # Audit trail of changes made w/ this comment
+  amendments = messages.MessageField(Amendment, 10, repeated=True)
+
+  # Soft delete that can be undeleted.
+  # Deleted comments should not be shown to average users.
+  # If deleted, deleted_by contains the user id of user who deleted.
+  deleted_by = messages.IntegerField(13)
+
+  attachments = messages.MessageField(Attachment, 20, repeated=True)
+
+  # Sequence number of the comment
+  # The field is optional for compatibility with code existing before
+  # this field was added.
+  # In practice, issue_svc sets this for all comments in GetCommentsForIssue.
+  sequence = messages.IntegerField(26)
+
+  # The body text of the inbound email that caused this issue comment
+  # to be automatically entered.  If this field is non-empty, it means
+  # that the comment was added via an inbound email.  Headers and attachments
+  # are not included.
+  inbound_message = messages.StringField(28)
+
+  is_spam = messages.BooleanField(51, default=False)
+
+  is_description = messages.BooleanField(52, default=False)
+  description_num = messages.StringField(53)
+
+  # User ID of script that imported the comment on behalf of a user.
+  importer_id = messages.IntegerField(55, default=0)
+
+
+class SavedQuery(messages.Message):
+  """Store a saved query, for either a project or a user."""
+  query_id = messages.IntegerField(1)
+  name = messages.StringField(2)
+  base_query_id = messages.IntegerField(3)
+  query = messages.StringField(4, required=True)
+
+  # For personal cross-project queries.
+  executes_in_project_ids = messages.IntegerField(5, repeated=True)
+
+  # For user saved queries.
+  subscription_mode = messages.StringField(6)
+
+
+class NotifyTriggers(messages.Enum):
+  """Issue tracker events that can trigger notification emails."""
+  NEVER = 0
+  ANY_COMMENT = 1
+  # TODO(jrobbins): ANY_CHANGE, OPENED_CLOSED, ETC.
+
+
+class FieldTypes(messages.Enum):
+  """Types of custom fields that Monorail supports."""
+  ENUM_TYPE = 1
+  INT_TYPE = 2
+  STR_TYPE = 3
+  USER_TYPE = 4
+  DATE_TYPE = 5
+  BOOL_TYPE = 6
+  URL_TYPE = 7
+  APPROVAL_TYPE = 8
+  # TODO(jrobbins): more types, see tracker.sql for all TODOs.
+
+
+class DateAction(messages.Enum):
+  """What to do when a date field value arrives."""
+  NO_ACTION = 0
+  PING_OWNER_ONLY = 1
+  PING_PARTICIPANTS = 2
+
+
+class FieldDef(messages.Message):
+  """This PB stores info about one custom field definition."""
+  field_id = messages.IntegerField(1, required=True)
+  project_id = messages.IntegerField(2, required=True)
+  field_name = messages.StringField(3, required=True)
+  field_type = messages.EnumField(FieldTypes, 4, required=True)
+  applicable_type = messages.StringField(11)
+  applicable_predicate = messages.StringField(10)
+  is_required = messages.BooleanField(5, default=False)
+  is_niche = messages.BooleanField(19, default=False)
+  is_multivalued = messages.BooleanField(6, default=False)
+  docstring = messages.StringField(7)
+  is_deleted = messages.BooleanField(8, default=False)
+  admin_ids = messages.IntegerField(9, repeated=True)
+  editor_ids = messages.IntegerField(24, repeated=True)
+
+  # validation details for int_type
+  min_value = messages.IntegerField(12)
+  max_value = messages.IntegerField(13)
+  # validation details for str_type
+  regex = messages.StringField(14)
+  # validation details for user_type
+  needs_member = messages.BooleanField(15, default=False)
+  needs_perm = messages.StringField(16)
+
+  # semantics for user_type fields
+  grants_perm = messages.StringField(17)
+  notify_on = messages.EnumField(NotifyTriggers, 18)
+
+  # semantics for date_type fields
+  date_action = messages.EnumField(DateAction, 20)
+
+  # field_id of the approval this FieldDef belongs to
+  approval_id = messages.IntegerField(21)
+
+  # These fields should only be associated with issue phases
+  is_phase_field = messages.BooleanField(22, default=False)
+
+  # boolean that indicates if this field is restricted
+  is_restricted_field = messages.BooleanField(23, default=False)
+
+
+class ComponentDef(messages.Message):
+  """This stores info about a component in a project."""
+  component_id = messages.IntegerField(1, required=True)
+  project_id = messages.IntegerField(2, required=True)
+  path = messages.StringField(3, required=True)
+  docstring = messages.StringField(4)
+  admin_ids = messages.IntegerField(5, repeated=True)
+  cc_ids = messages.IntegerField(6, repeated=True)
+  deprecated = messages.BooleanField(7, default=False)
+  created = messages.IntegerField(8)
+  creator_id = messages.IntegerField(9)
+  modified = messages.IntegerField(10)
+  modifier_id = messages.IntegerField(11)
+  label_ids = messages.IntegerField(12, repeated=True)
+
+
+class FilterRule(messages.Message):
+  """Filter rules implement semantics as project-specific if-then rules."""
+  predicate = messages.StringField(10, required=True)
+
+  # If the predicate is satisfied, these actions set some of the derived_*
+  # fields on the issue: labels, status, owner, or CCs.
+  add_labels = messages.StringField(20, repeated=True)
+  default_status = messages.StringField(21)
+  default_owner_id = messages.IntegerField(22)
+  add_cc_ids = messages.IntegerField(23, repeated=True)
+  add_notify_addrs = messages.StringField(24, repeated=True)
+  warning = messages.StringField(25)
+  error = messages.StringField(26)
+
+
+class StatusDef(messages.Message):
+  """Definition of one well-known issue status."""
+  status = messages.StringField(11, required=True)
+  means_open = messages.BooleanField(12, default=False)
+  status_docstring = messages.StringField(13)
+  deprecated = messages.BooleanField(14, default=False)
+
+
+class LabelDef(messages.Message):
+  """Definition of one well-known issue label."""
+  label = messages.StringField(21, required=True)
+  label_docstring = messages.StringField(22)
+  deprecated = messages.BooleanField(23, default=False)
+
+
+class ApprovalDef(messages.Message):
+  """Definition of an approval type field def."""
+  # Note: approval_id is semantically required
+  approval_id = messages.IntegerField(1)
+  approver_ids = messages.IntegerField(4, repeated=True)
+  survey = messages.StringField(5)
+
+# Next available tag: 48
+class TemplateDef(messages.Message):
+  """Definition of one issue template."""
+  template_id = messages.IntegerField(57)
+  name = messages.StringField(31, required=True)
+  content = messages.StringField(32, required=True)
+  summary = messages.StringField(33)
+  summary_must_be_edited = messages.BooleanField(34, default=False)
+  owner_id = messages.IntegerField(35)
+  status = messages.StringField(36)
+  # Note: labels field is considered to have been set iff summary was set.
+  labels = messages.StringField(37, repeated=True)
+  # This controls what is listed in the template drop-down menu. Users
+  # could still select any template by editing the URL, and that's OK.
+  members_only = messages.BooleanField(38, default=False)
+  # If no owner_id is specified, and owner_defaults_to_member is
+  # true, then when an issue is entered by a member, fill in the initial
+  # owner field with the signed in user's name.
+  owner_defaults_to_member = messages.BooleanField(39, default=True)
+  admin_ids = messages.IntegerField(41, repeated=True)
+
+  # Custom field values (other than enums)
+  field_values = messages.MessageField(FieldValue, 42, repeated=True)
+  # Components.
+  component_ids = messages.IntegerField(43, repeated=True)
+  component_required = messages.BooleanField(44, default=False)
+  phases = messages.MessageField(Phase, 46, repeated=True)
+  approval_values = messages.MessageField(ApprovalValue, 47, repeated=True)
+
+
+class ProjectIssueConfig(messages.Message):
+  """This holds all configuration info for one project.
+
+  That includes canned queries, well-known issue statuses,
+  and well-known issue labels.
+
+  "Well-known" means that they are always offered to the user in
+  drop-downs, even if there are currently no open issues that have
+  that label or status value.  Deleting a well-known value from the
+  configuration does not change any issues that may still reference
+  that old label, and users are still free to use it.
+
+  Exclusive label prefixes mean that a given issue may only have one
+  label that begins with that prefix.  E.g., Priority should be
+  exclusive so that no issue can be labeled with both Priority-High
+  and Priority-Low.
+  Next available tag: 62
+  """
+
+  project_id = messages.IntegerField(60)
+  well_known_statuses = messages.MessageField(StatusDef, 10, repeated=True)
+  # If an issue's status is being set to one of these, show "Merge with:".
+  statuses_offer_merge = messages.StringField(14, repeated=True)
+
+  well_known_labels = messages.MessageField(LabelDef, 20, repeated=True)
+  exclusive_label_prefixes = messages.StringField(2, repeated=True)
+
+  approval_defs = messages.MessageField(ApprovalDef, 61, repeated=True)
+
+  field_defs = messages.MessageField(FieldDef, 5, repeated=True)
+  component_defs = messages.MessageField(ComponentDef, 6, repeated=True)
+
+  default_template_for_developers = messages.IntegerField(3, required=True)
+  default_template_for_users = messages.IntegerField(4, required=True)
+
+  # These options control the default appearance of the issue list or grid
+  # for non-members.
+  default_col_spec = messages.StringField(50, default='')
+  default_sort_spec = messages.StringField(51, default='')
+  default_x_attr = messages.StringField(52, default='')
+  default_y_attr = messages.StringField(53, default='')
+
+  # These options control the default appearance of the issue list or grid
+  # for project members.
+  member_default_query = messages.StringField(57, default='')
+
+  # This bool controls whether users are able to enter odd-ball
+  # labels and status values, or whether they are limited to only the
+  # well-known labels and status values defined on the admin subtab.
+  restrict_to_known = messages.BooleanField(16, default=False)
+
+  # Allow special projects to have a custom URL for the "New issue" link.
+  custom_issue_entry_url = messages.StringField(56)