blob: f5820e69168043d03d4920cede254f304d6aac10 [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"""The Monorail issue tracker uses ProtoRPC for storing business objects."""
6
7from __future__ import print_function
8from __future__ import division
9from __future__ import absolute_import
10
11from protorpc import messages
12
13
14class FieldValue(messages.Message):
15 """Holds a single custom field value in an issue.
16
17 Multi-valued custom fields will have multiple such FieldValues on a given
18 issue. Note that enumerated type custom fields are represented as key-value
19 labels.
20 """
21 field_id = messages.IntegerField(1, required=True)
22 # Only one of the following fields will hve any value.
23 int_value = messages.IntegerField(2)
24 str_value = messages.StringField(3)
25 user_id = messages.IntegerField(4)
26 date_value = messages.IntegerField(6)
27 url_value = messages.StringField(7)
28
29 derived = messages.BooleanField(5, default=False)
30
31 # None if field is not a phse field.
32 phase_id = messages.IntegerField(8)
33
34
35class ApprovalStatus(messages.Enum):
36 """Statuses that an approval field could be set to."""
37 NEEDS_REVIEW = 1
38 NA = 2
39 REVIEW_REQUESTED = 3
40 REVIEW_STARTED = 4
41 NEED_INFO = 5
42 APPROVED = 6
43 NOT_APPROVED = 7
44 NOT_SET = 8
45
46
47class ApprovalValue(messages.Message):
48 """Holds a single approval field value in an issue."""
49 approval_id = messages.IntegerField(1)
50 status = messages.EnumField(ApprovalStatus, 2, default='NOT_SET')
51 setter_id = messages.IntegerField(3)
52 set_on = messages.IntegerField(4)
53 approver_ids = messages.IntegerField(5, repeated=True)
54 phase_id = messages.IntegerField(7)
55
56
57class ApprovalDelta(messages.Message):
58 """In-memory representation of requested changes to an issue's approval."""
59 status = messages.EnumField(ApprovalStatus, 1)
60 set_on = messages.IntegerField(2)
61 setter_id = messages.IntegerField(3)
62 approver_ids_add = messages.IntegerField(4, repeated=True)
63 approver_ids_remove = messages.IntegerField(5, repeated=True)
64 subfield_vals_add = messages.MessageField(FieldValue, 6, repeated=True)
65 subfield_vals_remove = messages.MessageField(FieldValue, 7, repeated=True)
66 subfields_clear = messages.IntegerField(8, repeated=True)
67 # Stores Approval's Enum subfield changes.
68 labels_add = messages.StringField(9, repeated=True)
69 labels_remove = messages.StringField(10, repeated=True)
70
71
72class Phase(messages.Message):
73 """Holds a single launch review phase."""
74 phase_id = messages.IntegerField(1)
75 name = messages.StringField(2)
76 rank = messages.IntegerField(4)
77
78
79class DanglingIssueRef(messages.Message):
80 """Holds a reference to an issue on Codesite or an external tracker."""
81 project = messages.StringField(1, required=True)
82 issue_id = messages.IntegerField(2, required=True)
83 ext_issue_identifier = messages.StringField(3, required=False)
84
85
86class Issue(messages.Message):
87 """Holds all the current metadata about an issue.
88
89 The most frequent searches can work by consulting solely the issue metadata.
90 Display of the issue list is done solely with this issue metadata.
91 Displaying one issue in detail with description and comments requires
92 more info from other objects.
93
94 The issue_id field is the unique primary key for retrieving issues. Local ID
95 is a small integer that counts up in each project.
96
97 Summary, Status, Owner, CC, reporter, and opened_timestamp are hard
98 fields that are always there. All other metadata is stored as
99 labels or custom fields.
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100100 Next available tag: 63.
Copybara854996b2021-09-07 19:36:02 +0000101 """
102 # Globally unique issue ID.
103 issue_id = messages.IntegerField(42)
104 # project_name is not stored in the DB, only the project_id is stored.
105 # project_name is used in RAM to simplify formatting logic in lots of places.
106 project_name = messages.StringField(1, required=True)
107 project_id = messages.IntegerField(50)
108 local_id = messages.IntegerField(2, required=True)
109 summary = messages.StringField(3, default='')
110 status = messages.StringField(4, default='')
111 owner_id = messages.IntegerField(5)
112 cc_ids = messages.IntegerField(6, repeated=True)
113 labels = messages.StringField(7, repeated=True)
114 component_ids = messages.IntegerField(39, repeated=True)
115
116 # Denormalized count of stars on this Issue.
117 star_count = messages.IntegerField(8, required=True, default=0)
118 reporter_id = messages.IntegerField(9, required=True, default=0)
119 # Time that the issue was opened, in seconds since the Epoch.
120 opened_timestamp = messages.IntegerField(10, required=True, default=0)
121
122 # This should be set when an issue is closed and cleared when a
123 # closed issue is reopened. Measured in seconds since the Epoch.
124 closed_timestamp = messages.IntegerField(12, default=0)
125
126 # This should be updated every time an issue is modified. Measured
127 # in seconds since the Epoch.
128 modified_timestamp = messages.IntegerField(13, default=0)
129
130 # These timestamps are updated whenever owner, status, or components
131 # change, including when altered by a filter rule.
132 owner_modified_timestamp = messages.IntegerField(19, default=0)
133 status_modified_timestamp = messages.IntegerField(20, default=0)
134 component_modified_timestamp = messages.IntegerField(21, default=0)
135
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100136 # Enhanced version of modified_timestamp that also captures changes to
137 # subresources of issues like stars, comments, and attachments.
138 # See: go/monorail-enhanced-modified-time
139 migration_modified_timestamp = messages.IntegerField(62, default=0)
140
Copybara854996b2021-09-07 19:36:02 +0000141 # Issue IDs of issues that this issue is blocked on.
142 blocked_on_iids = messages.IntegerField(16, repeated=True)
143
144 # Rank values of issue relations that are blocking this issue. The issue
145 # with id blocked_on_iids[i] has rank value blocked_on_ranks[i]
146 blocked_on_ranks = messages.IntegerField(54, repeated=True)
147
148 # Issue IDs of issues that this issue is blocking.
149 blocking_iids = messages.IntegerField(17, repeated=True)
150
151 # References to 'dangling' (still in codesite) issue relations.
152 dangling_blocked_on_refs = messages.MessageField(
153 DanglingIssueRef, 52, repeated=True)
154 dangling_blocking_refs = messages.MessageField(
155 DanglingIssueRef, 53, repeated=True)
156
157 # Issue ID of issue that this issue was merged into most recently. When it
158 # is missing or 0, it is considered to be not merged into any other issue.
159 merged_into = messages.IntegerField(18)
160 # Use this when an issue is a duplicate of an issue in an external tracker.
161 merged_into_external = messages.StringField(61)
162
163 # Default derived via rules, used iff status == ''.
164 derived_status = messages.StringField(30, default='')
165 # Default derived via rules, used iff owner_id == 0.
166 derived_owner_id = messages.IntegerField(31, default=0)
167 # Additional CCs derived via rules.
168 derived_cc_ids = messages.IntegerField(32, repeated=True)
169 # Additional labels derived via rules.
170 derived_labels = messages.StringField(33, repeated=True)
171 # Additional notification email addresses derived via rules.
172 derived_notify_addrs = messages.StringField(34, repeated=True)
173 # Additional components derived via rules.
174 derived_component_ids = messages.IntegerField(40, repeated=True)
175 # Software development process warnings and errors generated by filter rules.
176 # TODO(jrobbins): these are not yet stored in the DB, they are only in RAM.
177 derived_warnings = messages.StringField(55, repeated=True)
178 derived_errors = messages.StringField(56, repeated=True)
179
180 # Soft delete of the entire issue.
181 deleted = messages.BooleanField(35, default=False)
182
183 # Total number of attachments in the issue
184 attachment_count = messages.IntegerField(36, default=0)
185
186 # Total number of comments on the issue (not counting the initial comment
187 # created when the issue is created).
188 comment_count = messages.IntegerField(37, default=0)
189
190 # Custom field values (other than enums)
191 field_values = messages.MessageField(FieldValue, 41, repeated=True)
192
193 is_spam = messages.BooleanField(51, default=False)
194 # assume_stale is used in RAM to ensure that a value saved to the DB was
195 # loaded from the DB in the same request handler (not via the cache).
196 assume_stale = messages.BooleanField(57, default=True)
197
198 phases = messages.MessageField(Phase, 59, repeated=True)
199 approval_values = messages.MessageField(ApprovalValue, 60, repeated=True)
200
201
202class FieldID(messages.Enum):
203 """Possible fields that can be updated in an Amendment."""
204 # The spelling of these names must match enum values in tracker.sql.
205 SUMMARY = 1
206 STATUS = 2
207 OWNER = 3
208 CC = 4
209 LABELS = 5
210 BLOCKEDON = 6
211 BLOCKING = 7
212 MERGEDINTO = 8
213 PROJECT = 9
214 COMPONENTS = 10
215 CUSTOM = 11
216 WARNING = 12
217 ERROR = 13
218
219
220class IssueDelta(messages.Message):
221 """In-memory representation of requested changes to an issue.
222
223 Next available tag: 23
224 """
225 status = messages.StringField(1)
226 owner_id = messages.IntegerField(2)
227 cc_ids_add = messages.IntegerField(3, repeated=True)
228 cc_ids_remove = messages.IntegerField(4, repeated=True)
229 comp_ids_add = messages.IntegerField(5, repeated=True)
230 comp_ids_remove = messages.IntegerField(6, repeated=True)
231 labels_add = messages.StringField(7, repeated=True)
232 labels_remove = messages.StringField(8, repeated=True)
233 field_vals_add = messages.MessageField(FieldValue, 9, repeated=True)
234 field_vals_remove = messages.MessageField(FieldValue, 10, repeated=True)
235 fields_clear = messages.IntegerField(11, repeated=True)
236 blocked_on_add = messages.IntegerField(12, repeated=True)
237 blocked_on_remove = messages.IntegerField(13, repeated=True)
238 blocking_add = messages.IntegerField(14, repeated=True)
239 blocking_remove = messages.IntegerField(15, repeated=True)
240 merged_into = messages.IntegerField(16)
241 merged_into_external = messages.StringField(22)
242 summary = messages.StringField(17)
243 ext_blocked_on_add = messages.StringField(18, repeated=True)
244 ext_blocked_on_remove = messages.StringField(19, repeated=True)
245 ext_blocking_add = messages.StringField(20, repeated=True)
246 ext_blocking_remove = messages.StringField(21, repeated=True)
247
248
249class Amendment(messages.Message):
250 """Holds info about one issue field change."""
251 field = messages.EnumField(FieldID, 11, required=True)
252 # User-visible string describing the change
253 newvalue = messages.StringField(12)
254 # Newvalue could have + or - characters to indicate that labels and CCs
255 # were added or removed
256 # Users added to owner or cc field
257 added_user_ids = messages.IntegerField(29, repeated=True)
258 # Users removed from owner or cc
259 removed_user_ids = messages.IntegerField(30, repeated=True)
260 custom_field_name = messages.StringField(31)
261 # When having newvalue be a +/- string doesn't make sense (e.g. status),
262 # store the old value here so that it can still be displayed.
263 oldvalue = messages.StringField(32)
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100264 # New Components value add to the issue
265 added_component_ids = messages.IntegerField(33, repeated=True)
266 # Old Components value removed from the issue
267 removed_component_ids = messages.IntegerField(34, repeated=True)
Copybara854996b2021-09-07 19:36:02 +0000268
269
270class Attachment(messages.Message):
271 """Holds info about one attachment."""
272 attachment_id = messages.IntegerField(21, required=True)
273 # Client-side filename
274 filename = messages.StringField(22, required=True)
275 filesize = messages.IntegerField(23, required=True)
276 # File mime-type, or at least our best guess.
277 mimetype = messages.StringField(24, required=True)
278 deleted = messages.BooleanField(27, default=False)
279 gcs_object_id = messages.StringField(29, required=False)
280
281
282class IssueComment(messages.Message):
283 # TODO(lukasperaza): update first comment to is_description=True
284 """Holds one issue description or one additional comment on an issue.
285
286 The IssueComment with the lowest timestamp is the issue description,
287 if there is no IssueComment with is_description=True; otherwise, the
288 IssueComment with is_description=True and the highest timestamp is
289 the issue description.
290 Next available tag: 56
291 """
292 id = messages.IntegerField(32)
293 # Issue ID of the issue that was commented on.
294 issue_id = messages.IntegerField(31, required=True)
295 project_id = messages.IntegerField(50)
296 # User who entered the comment
297 user_id = messages.IntegerField(4, required=True, default=0)
298 # id of the APPROVAL_TYPE fielddef, if this is an approval comment.
299 approval_id = messages.IntegerField(54)
300 # Time when comment was entered (seconds).
301 timestamp = messages.IntegerField(5, required=True)
302 # Text of the comment
303 content = messages.StringField(6, required=True)
304 # Audit trail of changes made w/ this comment
305 amendments = messages.MessageField(Amendment, 10, repeated=True)
306
307 # Soft delete that can be undeleted.
308 # Deleted comments should not be shown to average users.
309 # If deleted, deleted_by contains the user id of user who deleted.
310 deleted_by = messages.IntegerField(13)
311
312 attachments = messages.MessageField(Attachment, 20, repeated=True)
313
314 # Sequence number of the comment
315 # The field is optional for compatibility with code existing before
316 # this field was added.
317 # In practice, issue_svc sets this for all comments in GetCommentsForIssue.
318 sequence = messages.IntegerField(26)
319
320 # The body text of the inbound email that caused this issue comment
321 # to be automatically entered. If this field is non-empty, it means
322 # that the comment was added via an inbound email. Headers and attachments
323 # are not included.
324 inbound_message = messages.StringField(28)
325
326 is_spam = messages.BooleanField(51, default=False)
327
328 is_description = messages.BooleanField(52, default=False)
329 description_num = messages.StringField(53)
330
331 # User ID of script that imported the comment on behalf of a user.
332 importer_id = messages.IntegerField(55, default=0)
333
334
335class SavedQuery(messages.Message):
336 """Store a saved query, for either a project or a user."""
337 query_id = messages.IntegerField(1)
338 name = messages.StringField(2)
339 base_query_id = messages.IntegerField(3)
340 query = messages.StringField(4, required=True)
341
342 # For personal cross-project queries.
343 executes_in_project_ids = messages.IntegerField(5, repeated=True)
344
345 # For user saved queries.
346 subscription_mode = messages.StringField(6)
347
348
349class NotifyTriggers(messages.Enum):
350 """Issue tracker events that can trigger notification emails."""
351 NEVER = 0
352 ANY_COMMENT = 1
353 # TODO(jrobbins): ANY_CHANGE, OPENED_CLOSED, ETC.
354
355
356class FieldTypes(messages.Enum):
357 """Types of custom fields that Monorail supports."""
358 ENUM_TYPE = 1
359 INT_TYPE = 2
360 STR_TYPE = 3
361 USER_TYPE = 4
362 DATE_TYPE = 5
363 BOOL_TYPE = 6
364 URL_TYPE = 7
365 APPROVAL_TYPE = 8
366 # TODO(jrobbins): more types, see tracker.sql for all TODOs.
367
368
369class DateAction(messages.Enum):
370 """What to do when a date field value arrives."""
371 NO_ACTION = 0
372 PING_OWNER_ONLY = 1
373 PING_PARTICIPANTS = 2
374
375
376class FieldDef(messages.Message):
377 """This PB stores info about one custom field definition."""
378 field_id = messages.IntegerField(1, required=True)
379 project_id = messages.IntegerField(2, required=True)
380 field_name = messages.StringField(3, required=True)
381 field_type = messages.EnumField(FieldTypes, 4, required=True)
382 applicable_type = messages.StringField(11)
383 applicable_predicate = messages.StringField(10)
384 is_required = messages.BooleanField(5, default=False)
385 is_niche = messages.BooleanField(19, default=False)
386 is_multivalued = messages.BooleanField(6, default=False)
387 docstring = messages.StringField(7)
388 is_deleted = messages.BooleanField(8, default=False)
389 admin_ids = messages.IntegerField(9, repeated=True)
390 editor_ids = messages.IntegerField(24, repeated=True)
391
392 # validation details for int_type
393 min_value = messages.IntegerField(12)
394 max_value = messages.IntegerField(13)
395 # validation details for str_type
396 regex = messages.StringField(14)
397 # validation details for user_type
398 needs_member = messages.BooleanField(15, default=False)
399 needs_perm = messages.StringField(16)
400
401 # semantics for user_type fields
402 grants_perm = messages.StringField(17)
403 notify_on = messages.EnumField(NotifyTriggers, 18)
404
405 # semantics for date_type fields
406 date_action = messages.EnumField(DateAction, 20)
407
408 # field_id of the approval this FieldDef belongs to
409 approval_id = messages.IntegerField(21)
410
411 # These fields should only be associated with issue phases
412 is_phase_field = messages.BooleanField(22, default=False)
413
414 # boolean that indicates if this field is restricted
415 is_restricted_field = messages.BooleanField(23, default=False)
416
417
418class ComponentDef(messages.Message):
419 """This stores info about a component in a project."""
420 component_id = messages.IntegerField(1, required=True)
421 project_id = messages.IntegerField(2, required=True)
422 path = messages.StringField(3, required=True)
423 docstring = messages.StringField(4)
424 admin_ids = messages.IntegerField(5, repeated=True)
425 cc_ids = messages.IntegerField(6, repeated=True)
426 deprecated = messages.BooleanField(7, default=False)
427 created = messages.IntegerField(8)
428 creator_id = messages.IntegerField(9)
429 modified = messages.IntegerField(10)
430 modifier_id = messages.IntegerField(11)
431 label_ids = messages.IntegerField(12, repeated=True)
432
433
434class FilterRule(messages.Message):
435 """Filter rules implement semantics as project-specific if-then rules."""
436 predicate = messages.StringField(10, required=True)
437
438 # If the predicate is satisfied, these actions set some of the derived_*
439 # fields on the issue: labels, status, owner, or CCs.
440 add_labels = messages.StringField(20, repeated=True)
441 default_status = messages.StringField(21)
442 default_owner_id = messages.IntegerField(22)
443 add_cc_ids = messages.IntegerField(23, repeated=True)
444 add_notify_addrs = messages.StringField(24, repeated=True)
445 warning = messages.StringField(25)
446 error = messages.StringField(26)
447
448
449class StatusDef(messages.Message):
450 """Definition of one well-known issue status."""
451 status = messages.StringField(11, required=True)
452 means_open = messages.BooleanField(12, default=False)
453 status_docstring = messages.StringField(13)
454 deprecated = messages.BooleanField(14, default=False)
455
456
457class LabelDef(messages.Message):
458 """Definition of one well-known issue label."""
459 label = messages.StringField(21, required=True)
460 label_docstring = messages.StringField(22)
461 deprecated = messages.BooleanField(23, default=False)
462
463
464class ApprovalDef(messages.Message):
465 """Definition of an approval type field def."""
466 # Note: approval_id is semantically required
467 approval_id = messages.IntegerField(1)
468 approver_ids = messages.IntegerField(4, repeated=True)
469 survey = messages.StringField(5)
470
471# Next available tag: 48
472class TemplateDef(messages.Message):
473 """Definition of one issue template."""
474 template_id = messages.IntegerField(57)
475 name = messages.StringField(31, required=True)
476 content = messages.StringField(32, required=True)
477 summary = messages.StringField(33)
478 summary_must_be_edited = messages.BooleanField(34, default=False)
479 owner_id = messages.IntegerField(35)
480 status = messages.StringField(36)
481 # Note: labels field is considered to have been set iff summary was set.
482 labels = messages.StringField(37, repeated=True)
483 # This controls what is listed in the template drop-down menu. Users
484 # could still select any template by editing the URL, and that's OK.
485 members_only = messages.BooleanField(38, default=False)
486 # If no owner_id is specified, and owner_defaults_to_member is
487 # true, then when an issue is entered by a member, fill in the initial
488 # owner field with the signed in user's name.
489 owner_defaults_to_member = messages.BooleanField(39, default=True)
490 admin_ids = messages.IntegerField(41, repeated=True)
491
492 # Custom field values (other than enums)
493 field_values = messages.MessageField(FieldValue, 42, repeated=True)
494 # Components.
495 component_ids = messages.IntegerField(43, repeated=True)
496 component_required = messages.BooleanField(44, default=False)
497 phases = messages.MessageField(Phase, 46, repeated=True)
498 approval_values = messages.MessageField(ApprovalValue, 47, repeated=True)
499
500
501class ProjectIssueConfig(messages.Message):
502 """This holds all configuration info for one project.
503
504 That includes canned queries, well-known issue statuses,
505 and well-known issue labels.
506
507 "Well-known" means that they are always offered to the user in
508 drop-downs, even if there are currently no open issues that have
509 that label or status value. Deleting a well-known value from the
510 configuration does not change any issues that may still reference
511 that old label, and users are still free to use it.
512
513 Exclusive label prefixes mean that a given issue may only have one
514 label that begins with that prefix. E.g., Priority should be
515 exclusive so that no issue can be labeled with both Priority-High
516 and Priority-Low.
517 Next available tag: 62
518 """
519
520 project_id = messages.IntegerField(60)
521 well_known_statuses = messages.MessageField(StatusDef, 10, repeated=True)
522 # If an issue's status is being set to one of these, show "Merge with:".
523 statuses_offer_merge = messages.StringField(14, repeated=True)
524
525 well_known_labels = messages.MessageField(LabelDef, 20, repeated=True)
526 exclusive_label_prefixes = messages.StringField(2, repeated=True)
527
528 approval_defs = messages.MessageField(ApprovalDef, 61, repeated=True)
529
530 field_defs = messages.MessageField(FieldDef, 5, repeated=True)
531 component_defs = messages.MessageField(ComponentDef, 6, repeated=True)
532
533 default_template_for_developers = messages.IntegerField(3, required=True)
534 default_template_for_users = messages.IntegerField(4, required=True)
535
536 # These options control the default appearance of the issue list or grid
537 # for non-members.
538 default_col_spec = messages.StringField(50, default='')
539 default_sort_spec = messages.StringField(51, default='')
540 default_x_attr = messages.StringField(52, default='')
541 default_y_attr = messages.StringField(53, default='')
542
543 # These options control the default appearance of the issue list or grid
544 # for project members.
545 member_default_query = messages.StringField(57, default='')
546
547 # This bool controls whether users are able to enter odd-ball
548 # labels and status values, or whether they are limited to only the
549 # well-known labels and status values defined on the admin subtab.
550 restrict_to_known = messages.BooleanField(16, default=False)
551
552 # Allow special projects to have a custom URL for the "New issue" link.
553 custom_issue_entry_url = messages.StringField(56)