OLD | NEW |
(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 """The Monorail issue tracker uses ProtoRPC for storing business objects.""" |
| 7 |
| 8 from protorpc import messages |
| 9 |
| 10 |
| 11 class FieldValue(messages.Message): |
| 12 """Holds a single custom field value in an issue. |
| 13 |
| 14 Multi-valued custom fields will have multiple such FieldValues on a given |
| 15 issue. Note that enumerated type custom fields are represented as key-value |
| 16 labels. |
| 17 """ |
| 18 field_id = messages.IntegerField(1, required=True) |
| 19 # Only one of the following fields will hve any value. |
| 20 int_value = messages.IntegerField(2) |
| 21 str_value = messages.StringField(3) |
| 22 user_id = messages.IntegerField(4, default=0) |
| 23 |
| 24 derived = messages.BooleanField(5, default=False) |
| 25 |
| 26 |
| 27 class DanglingIssueRef(messages.Message): |
| 28 """Holds a reference to an issue still on Google Codesite.""" |
| 29 project = messages.StringField(1, required=True) |
| 30 issue_id = messages.IntegerField(2, required=True) |
| 31 |
| 32 |
| 33 class Issue(messages.Message): |
| 34 """Holds all the current metadata about an issue. |
| 35 |
| 36 The most frequent searches can work by consulting solely the issue metadata. |
| 37 Display of the issue list is done solely with this issue metadata. |
| 38 Displaying one issue in detail with description and comments requires |
| 39 more info from other objects. |
| 40 |
| 41 The issue_id field is the unique primary key for retrieving issues. Local ID |
| 42 is a small integer that counts up in each project. |
| 43 |
| 44 Summary, Status, Owner, CC, reporter, and opened_timestamp are hard |
| 45 fields that are always there. All other metadata is stored as |
| 46 labels or custom fields. |
| 47 Next available tag: 54. |
| 48 """ |
| 49 # Globally unique issue ID. |
| 50 issue_id = messages.IntegerField(42) |
| 51 # project_name is not stored in the DB, only the project_id is stored. |
| 52 # project_name is used in RAM to simplify formatting logic in lots of places. |
| 53 project_name = messages.StringField(1, required=True) |
| 54 project_id = messages.IntegerField(50) |
| 55 local_id = messages.IntegerField(2, required=True) |
| 56 summary = messages.StringField(3, default='') |
| 57 status = messages.StringField(4, default='') |
| 58 owner_id = messages.IntegerField(5) |
| 59 cc_ids = messages.IntegerField(6, repeated=True) |
| 60 labels = messages.StringField(7, repeated=True) |
| 61 component_ids = messages.IntegerField(39, repeated=True) |
| 62 |
| 63 # Denormalized count of stars on this Issue. |
| 64 star_count = messages.IntegerField(8, required=True, default=0) |
| 65 reporter_id = messages.IntegerField(9, required=True, default=0) |
| 66 # Time that the issue was opened, in seconds since the Epoch. |
| 67 opened_timestamp = messages.IntegerField(10, required=True, default=0) |
| 68 |
| 69 # This should be set when an issue is closed and cleared when a |
| 70 # closed issue is reopened. Measured in seconds since the Epoch. |
| 71 closed_timestamp = messages.IntegerField(12, default=0) |
| 72 |
| 73 # This should be updated every time an issue is modified. Measured |
| 74 # in seconds since the Epoch. |
| 75 modified_timestamp = messages.IntegerField(13, default=0) |
| 76 |
| 77 # Issue IDs of issues that this issue is blocked on. |
| 78 blocked_on_iids = messages.IntegerField(16, repeated=True) |
| 79 |
| 80 # Issue IDs of issues that this issue is blocking. |
| 81 blocking_iids = messages.IntegerField(17, repeated=True) |
| 82 |
| 83 # References to 'dangling' (still in codesite) issue relations. |
| 84 dangling_blocked_on_refs = messages.MessageField( |
| 85 DanglingIssueRef, 52, repeated=True) |
| 86 dangling_blocking_refs = messages.MessageField( |
| 87 DanglingIssueRef, 53, repeated=True) |
| 88 |
| 89 # Issue ID of issue that this issue was merged into most recently. When it |
| 90 # is missing or 0, it is considered to be not merged into any other issue. |
| 91 merged_into = messages.IntegerField(18) |
| 92 |
| 93 # Default derived via rules, used iff status == ''. |
| 94 derived_status = messages.StringField(30, default='') |
| 95 # Default derived via rules, used iff owner_id == 0. |
| 96 derived_owner_id = messages.IntegerField(31, default=0) |
| 97 # Additional CCs derived via rules. |
| 98 derived_cc_ids = messages.IntegerField(32, repeated=True) |
| 99 # Additional labels derived via rules. |
| 100 derived_labels = messages.StringField(33, repeated=True) |
| 101 # Additional notification email addresses derived via rules. |
| 102 derived_notify_addrs = messages.StringField(34, repeated=True) |
| 103 # Additional components derived via rules. |
| 104 derived_component_ids = messages.IntegerField(40, repeated=True) |
| 105 |
| 106 # Soft delete of the entire issue. |
| 107 deleted = messages.BooleanField(35, default=False) |
| 108 |
| 109 # Total number of attachments in the issue |
| 110 attachment_count = messages.IntegerField(36, default=0) |
| 111 |
| 112 # Total number of comments on the issue (not counting the initial comment |
| 113 # created when the issue is created). |
| 114 comment_count = messages.IntegerField(37, default=0) |
| 115 |
| 116 # Custom field values (other than enums) |
| 117 field_values = messages.MessageField(FieldValue, 41, repeated=True) |
| 118 |
| 119 is_spam = messages.BooleanField(51, default=False) |
| 120 |
| 121 |
| 122 class FieldID(messages.Enum): |
| 123 """Possible fields that can be updated in an Amendment.""" |
| 124 # The spelling of these names must match enum values in tracker.sql. |
| 125 SUMMARY = 1 |
| 126 STATUS = 2 |
| 127 OWNER = 3 |
| 128 CC = 4 |
| 129 LABELS = 5 |
| 130 BLOCKEDON = 6 |
| 131 BLOCKING = 7 |
| 132 MERGEDINTO = 8 |
| 133 PROJECT = 9 |
| 134 COMPONENTS = 10 |
| 135 CUSTOM = 11 |
| 136 |
| 137 |
| 138 class Amendment(messages.Message): |
| 139 """Holds info about one issue field change.""" |
| 140 field = messages.EnumField(FieldID, 11, required=True) |
| 141 # User-visible string describing the change |
| 142 newvalue = messages.StringField(12, required=True) |
| 143 # Newvalue could have + or - characters to indicate that labels and CCs |
| 144 # were added or removed |
| 145 # Users added to owner or cc field |
| 146 added_user_ids = messages.IntegerField(29, repeated=True) |
| 147 # Users removed from owner or cc |
| 148 removed_user_ids = messages.IntegerField(30, repeated=True) |
| 149 custom_field_name = messages.StringField(31) |
| 150 # When having newvalue be a +/- string doesn't make sense (e.g. status), |
| 151 # store the old value here so that it can still be displayed. |
| 152 oldvalue = messages.StringField(32) |
| 153 |
| 154 |
| 155 class Attachment(messages.Message): |
| 156 """Holds info about one attachment.""" |
| 157 attachment_id = messages.IntegerField(21, required=True) |
| 158 # Client-side filename |
| 159 filename = messages.StringField(22, required=True) |
| 160 filesize = messages.IntegerField(23, required=True) |
| 161 # File mime-type, or at least our best guess. |
| 162 mimetype = messages.StringField(24, required=True) |
| 163 deleted = messages.BooleanField(27, default=False) |
| 164 gcs_object_id = messages.StringField(29, required=False) |
| 165 |
| 166 |
| 167 class IssueComment(messages.Message): |
| 168 """Holds one issue description or one additional comment on an issue. |
| 169 |
| 170 The IssueComment with the lowest timestamp is the issue description. |
| 171 Next available tag: 52 |
| 172 """ |
| 173 id = messages.IntegerField(32) |
| 174 # Issue ID of the issue that was commented on. |
| 175 issue_id = messages.IntegerField(31, required=True) |
| 176 project_id = messages.IntegerField(50) |
| 177 # User who entered the comment |
| 178 user_id = messages.IntegerField(4, required=True, default=0) |
| 179 # Time when comment was entered (seconds). |
| 180 timestamp = messages.IntegerField(5, required=True) |
| 181 # Text of the comment |
| 182 content = messages.StringField(6, required=True) |
| 183 # Audit trail of changes made w/ this comment |
| 184 amendments = messages.MessageField(Amendment, 10, repeated=True) |
| 185 |
| 186 # Soft delete that can be undeleted. |
| 187 # Deleted comments should not be shown to average users. |
| 188 # If deleted, deleted_by contains the user id of user who deleted. |
| 189 deleted_by = messages.IntegerField(13) |
| 190 |
| 191 attachments = messages.MessageField(Attachment, 20, repeated=True) |
| 192 |
| 193 # TODO(jrobbins): Always store unescaped text and let EZT do the |
| 194 # escaping on output. Then I can eliminate this. |
| 195 was_escaped = messages.BooleanField(25, default=True) |
| 196 |
| 197 # Sequence number of the comment |
| 198 # The field is optional for compatibility with code existing before |
| 199 # this field was added. |
| 200 sequence = messages.IntegerField(26) |
| 201 |
| 202 # The body text of the inbound email that caused this issue comment |
| 203 # to be automatically entered. If this field is non-empty, it means |
| 204 # that the comment was added via an inbound email. Headers and attachments |
| 205 # are not included. |
| 206 inbound_message = messages.StringField(28) |
| 207 |
| 208 is_spam = messages.BooleanField(51, default=False) |
| 209 |
| 210 class SavedQuery(messages.Message): |
| 211 """Store a saved query, for either a project or a user.""" |
| 212 query_id = messages.IntegerField(1) |
| 213 name = messages.StringField(2) |
| 214 base_query_id = messages.IntegerField(3) |
| 215 query = messages.StringField(4, required=True) |
| 216 |
| 217 # For personal cross-project queries. |
| 218 executes_in_project_ids = messages.IntegerField(5, repeated=True) |
| 219 |
| 220 # For user saved queries. |
| 221 subscription_mode = messages.StringField(6) |
| 222 |
| 223 |
| 224 class NotifyTriggers(messages.Enum): |
| 225 """Issue tracker events that can trigger notification emails.""" |
| 226 NEVER = 0 |
| 227 ANY_COMMENT = 1 |
| 228 # TODO(jrobbins): ANY_CHANGE, OPENED_CLOSED, ETC. |
| 229 |
| 230 |
| 231 class FieldTypes(messages.Enum): |
| 232 """Types of custom fields that Monorail supports.""" |
| 233 ENUM_TYPE = 1 |
| 234 INT_TYPE = 2 |
| 235 STR_TYPE = 3 |
| 236 USER_TYPE = 4 |
| 237 DATE_TYPE = 5 |
| 238 BOOL_TYPE = 6 |
| 239 # TODO(jrobbins): more types, see tracker.sql for all TODOs. |
| 240 |
| 241 |
| 242 class FieldDef(messages.Message): |
| 243 """This PB stores info about one custom field definition.""" |
| 244 field_id = messages.IntegerField(1, required=True) |
| 245 project_id = messages.IntegerField(2, required=True) |
| 246 field_name = messages.StringField(3, required=True) |
| 247 field_type = messages.EnumField(FieldTypes, 4, required=True) |
| 248 applicable_type = messages.StringField(11) |
| 249 applicable_predicate = messages.StringField(10) |
| 250 is_required = messages.BooleanField(5, default=False) |
| 251 is_multivalued = messages.BooleanField(6, default=False) |
| 252 docstring = messages.StringField(7) |
| 253 is_deleted = messages.BooleanField(8, default=False) |
| 254 admin_ids = messages.IntegerField(9, repeated=True) |
| 255 |
| 256 # validation details for int_type |
| 257 min_value = messages.IntegerField(12) |
| 258 max_value = messages.IntegerField(13) |
| 259 # validation details for str_type |
| 260 regex = messages.StringField(14) |
| 261 # validation details for user_type |
| 262 needs_member = messages.BooleanField(15, default=False) |
| 263 needs_perm = messages.StringField(16) |
| 264 |
| 265 # semantics for user_type fields |
| 266 grants_perm = messages.StringField(17) |
| 267 notify_on = messages.EnumField(NotifyTriggers, 18) |
| 268 |
| 269 |
| 270 class ComponentDef(messages.Message): |
| 271 """This stores info about a component in a project.""" |
| 272 component_id = messages.IntegerField(1, required=True) |
| 273 project_id = messages.IntegerField(2, required=True) |
| 274 path = messages.StringField(3, required=True) |
| 275 docstring = messages.StringField(4) |
| 276 admin_ids = messages.IntegerField(5, repeated=True) |
| 277 cc_ids = messages.IntegerField(6, repeated=True) |
| 278 deprecated = messages.BooleanField(7, default=False) |
| 279 created = messages.IntegerField(8) |
| 280 creator_id = messages.IntegerField(9) |
| 281 modified = messages.IntegerField(10) |
| 282 modifier_id = messages.IntegerField(11) |
| 283 |
| 284 |
| 285 class FilterRule(messages.Message): |
| 286 """Filter rules implement semantics as project-specific if-then rules.""" |
| 287 predicate = messages.StringField(10, required=True) |
| 288 |
| 289 # If the predicate is satisfied, these actions set some of the derived_* |
| 290 # fields on the issue: labels, status, owner, or CCs. |
| 291 add_labels = messages.StringField(20, repeated=True) |
| 292 default_status = messages.StringField(21) |
| 293 default_owner_id = messages.IntegerField(22) |
| 294 add_cc_ids = messages.IntegerField(23, repeated=True) |
| 295 add_notify_addrs = messages.StringField(24, repeated=True) |
| 296 |
| 297 |
| 298 class StatusDef(messages.Message): |
| 299 """Definition of one well-known issue status.""" |
| 300 status = messages.StringField(11, required=True) |
| 301 means_open = messages.BooleanField(12, default=False) |
| 302 status_docstring = messages.StringField(13) |
| 303 deprecated = messages.BooleanField(14, default=False) |
| 304 |
| 305 |
| 306 class LabelDef(messages.Message): |
| 307 """Definition of one well-known issue label.""" |
| 308 label = messages.StringField(21, required=True) |
| 309 label_docstring = messages.StringField(22) |
| 310 deprecated = messages.BooleanField(23, default=False) |
| 311 |
| 312 |
| 313 class TemplateDef(messages.Message): |
| 314 """Definition of one issue template.""" |
| 315 template_id = messages.IntegerField(57) |
| 316 name = messages.StringField(31, required=True) |
| 317 content = messages.StringField(32, required=True) |
| 318 summary = messages.StringField(33) |
| 319 summary_must_be_edited = messages.BooleanField(34, default=False) |
| 320 owner_id = messages.IntegerField(35) |
| 321 status = messages.StringField(36) |
| 322 # Note: labels field is considered to have been set iff summary was set. |
| 323 labels = messages.StringField(37, repeated=True) |
| 324 # This controls what is listed in the template drop-down menu. Users |
| 325 # could still select any template by editing the URL, and that's OK. |
| 326 members_only = messages.BooleanField(38, default=False) |
| 327 # If no owner_id is specified, and owner_defaults_to_member is |
| 328 # true, then when an issue is entered by a member, fill in the initial |
| 329 # owner field with the signed in user's name. |
| 330 owner_defaults_to_member = messages.BooleanField(39, default=True) |
| 331 admin_ids = messages.IntegerField(41, repeated=True) |
| 332 |
| 333 # Custom field values (other than enums) |
| 334 field_values = messages.MessageField(FieldValue, 42, repeated=True) |
| 335 # Components. |
| 336 component_ids = messages.IntegerField(43, repeated=True) |
| 337 component_required = messages.BooleanField(44, default=False) |
| 338 |
| 339 |
| 340 class ProjectIssueConfig(messages.Message): |
| 341 """This holds all configuration info for one project. |
| 342 |
| 343 That includes canned queries, well-known issue statuses, |
| 344 and well-known issue labels. |
| 345 |
| 346 "Well-known" means that they are always offered to the user in |
| 347 drop-downs, even if there are currently no open issues that have |
| 348 that label or status value. Deleting a well-known value from the |
| 349 configuration does not change any issues that may still reference |
| 350 that old label, and users are still free to use it. |
| 351 |
| 352 Exclusive label prefixes mean that a given issue may only have one |
| 353 label that begins with that prefix. E.g., Priority should be |
| 354 exclusive so that no issue can be labeled with both Priority-High |
| 355 and Priority-Low. |
| 356 """ |
| 357 |
| 358 project_id = messages.IntegerField(60) |
| 359 well_known_statuses = messages.MessageField(StatusDef, 10, repeated=True) |
| 360 # If an issue's status is being set to one of these, show "Merge with:". |
| 361 statuses_offer_merge = messages.StringField(14, repeated=True) |
| 362 |
| 363 well_known_labels = messages.MessageField(LabelDef, 20, repeated=True) |
| 364 exclusive_label_prefixes = messages.StringField(2, repeated=True) |
| 365 |
| 366 field_defs = messages.MessageField(FieldDef, 5, repeated=True) |
| 367 component_defs = messages.MessageField(ComponentDef, 6, repeated=True) |
| 368 |
| 369 templates = messages.MessageField(TemplateDef, 30, repeated=True) |
| 370 |
| 371 default_template_for_developers = messages.IntegerField(3, required=True) |
| 372 default_template_for_users = messages.IntegerField(4, required=True) |
| 373 |
| 374 # These options control the default appearance of the issue list or grid. |
| 375 default_col_spec = messages.StringField(50, default='') |
| 376 default_sort_spec = messages.StringField(51, default='') |
| 377 default_x_attr = messages.StringField(52, default='') |
| 378 default_y_attr = messages.StringField(53, default='') |
| 379 |
| 380 # This bool controls whether users are able to enter odd-ball |
| 381 # labels and status values, or whether they are limited to only the |
| 382 # well-known labels and status values defined on the admin subtab. |
| 383 restrict_to_known = messages.BooleanField(16, default=False) |
| 384 |
| 385 # Allow special projects to have a custom URL for the "New issue" link. |
| 386 custom_issue_entry_url = messages.StringField(56) |
OLD | NEW |