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