blob: 00e546bedf0db4a37ba4488a02a4ab96f8435cf4 [file] [log] [blame]
# 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
"""The Monorail issue tracker uses ProtoRPC for storing business objects."""
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, default=0)
derived = messages.BooleanField(5, default=False)
class DanglingIssueRef(messages.Message):
"""Holds a reference to an issue still on Google Codesite."""
project = messages.StringField(1, required=True)
issue_id = messages.IntegerField(2, required=True)
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: 54.
"""
# 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)
# Issue IDs of issues that this issue is blocked on.
blocked_on_iids = messages.IntegerField(16, 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)
# 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)
# 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)
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
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, required=True)
# 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)
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):
"""Holds one issue description or one additional comment on an issue.
The IssueComment with the lowest timestamp is the issue description.
Next available tag: 52
"""
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)
# 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)
# TODO(jrobbins): Always store unescaped text and let EZT do the
# escaping on output. Then I can eliminate this.
was_escaped = messages.BooleanField(25, default=True)
# Sequence number of the comment
# The field is optional for compatibility with code existing before
# this field was added.
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)
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
# TODO(jrobbins): more types, see tracker.sql for all TODOs.
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_multivalued = messages.BooleanField(6, default=False)
docstring = messages.StringField(7)
is_deleted = messages.BooleanField(8, default=False)
admin_ids = messages.IntegerField(9, 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)
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)
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)
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 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)
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.
"""
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)
field_defs = messages.MessageField(FieldDef, 5, repeated=True)
component_defs = messages.MessageField(ComponentDef, 6, repeated=True)
templates = messages.MessageField(TemplateDef, 30, 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.
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='')
# 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)