def ConvertApproval(approval_value, users_by_id, config, phase=None): """Use the given ApprovalValue to create a protoc Approval.""" approval_name = '' fd = tracker_bizobj.FindFieldDefByID(approval_value.approval_id, config) if fd: approval_name = fd.field_name else: logging.info( 'Ignoring approval value referencing a non-existing field: %r', approval_value) return None field_ref = ConvertFieldRef(approval_value.approval_id, approval_name, tracker_pb2.FieldTypes.APPROVAL_TYPE, None) approver_refs = ConvertUserRefs(approval_value.approver_ids, [], users_by_id, False) setter_ref = ConvertUserRef(approval_value.setter_id, None, users_by_id) status = ConvertApprovalStatus(approval_value.status) set_on = approval_value.set_on phase_ref = issue_objects_pb2.PhaseRef() if phase: phase_ref.phase_name = phase.name result = issue_objects_pb2.Approval(field_ref=field_ref, approver_refs=approver_refs, status=status, set_on=set_on, setter_ref=setter_ref, phase_ref=phase_ref) return result
def ValidateCustomFields(mr, services, field_values, config, errors): """Validate each of the given fields and report problems in errors object.""" for fv in field_values: fd = tracker_bizobj.FindFieldDefByID(fv.field_id, config) if fd: err_msg = ValidateCustomField(mr, mr.project, services, fd, fv) if err_msg: errors.SetCustomFieldError(fv.field_id, err_msg)
def ConvertApprovalDef(approval_def, users_by_id, config, include_admin_info): """Convert a protorpc ApprovalDef into a protoc ApprovalDef.""" field_def = tracker_bizobj.FindFieldDefByID(approval_def.approval_id, config) field_ref = ConvertFieldRef(field_def.field_id, field_def.field_name, field_def.field_type, None) if not include_admin_info: return project_objects_pb2.ApprovalDef(field_ref=field_ref) approver_refs = ConvertUserRefs(approval_def.approver_ids, [], users_by_id, False) return project_objects_pb2.ApprovalDef(field_ref=field_ref, approver_refs=approver_refs, survey=approval_def.survey)
def ConvertComment(issue, comment, config, users_by_id, comment_reporters, description_nums, user_id, perms): """Convert a protorpc IssueComment to a protoc Comment.""" commenter = users_by_id[comment.user_id] can_delete = permissions.CanDeleteComment(comment, commenter, user_id, perms) can_flag, is_flagged = permissions.CanFlagComment(comment, commenter, comment_reporters, user_id, perms) can_view = permissions.CanViewComment(comment, commenter, user_id, perms) can_view_inbound_message = permissions.CanViewInboundMessage( comment, user_id, perms) is_deleted = bool(comment.deleted_by or is_flagged or commenter.banned) result = issue_objects_pb2.Comment(project_name=issue.project_name, local_id=issue.local_id, sequence_num=comment.sequence, is_deleted=is_deleted, can_delete=can_delete, is_spam=is_flagged, can_flag=can_flag, timestamp=comment.timestamp) if can_view: result.commenter.CopyFrom( ConvertUserRef(comment.user_id, None, users_by_id)) result.content = comment.content if comment.inbound_message and can_view_inbound_message: result.inbound_message = comment.inbound_message result.amendments.extend([ ConvertAmendment(amend, users_by_id) for amend in comment.amendments ]) result.attachments.extend([ ConvertAttachment(attach, issue.project_name) for attach in comment.attachments ]) if comment.id in description_nums: result.description_num = description_nums[comment.id] fd = tracker_bizobj.FindFieldDefByID(comment.approval_id, config) if fd: result.approval_ref.field_name = fd.field_name return result
def ConvertFieldDef(field_def, user_choices, users_by_id, config, include_admin_info): """Convert a protorpc FieldDef into a protoc FieldDef.""" parent_approval_name = None if field_def.approval_id: parent_fd = tracker_bizobj.FindFieldDefByID(field_def.approval_id, config) if parent_fd: parent_approval_name = parent_fd.field_name field_ref = ConvertFieldRef(field_def.field_id, field_def.field_name, field_def.field_type, parent_approval_name) enum_choices = [] if field_def.field_type == tracker_pb2.FieldTypes.ENUM_TYPE: masked_labels = tracker_helpers.LabelsMaskedByFields( config, [field_def.field_name], True) enum_choices = [ project_objects_pb2.LabelDef(label=label.name, docstring=label.docstring, deprecated=(label.commented == '#')) for label in masked_labels ] if not include_admin_info: return project_objects_pb2.FieldDef( field_ref=field_ref, docstring=field_def.docstring, # Display full email address for user choices. user_choices=ConvertUserRefs(user_choices, [], users_by_id, True), enum_choices=enum_choices) admin_refs = ConvertUserRefs(field_def.admin_ids, [], users_by_id, False) # TODO(jrobbins): validation, permission granting, and notification options. return project_objects_pb2.FieldDef( field_ref=field_ref, applicable_type=field_def.applicable_type, is_required=field_def.is_required, is_niche=field_def.is_niche, is_multivalued=field_def.is_multivalued, docstring=field_def.docstring, admin_refs=admin_refs, is_phase_field=field_def.is_phase_field, enum_choices=enum_choices)
def __init__(self, art, col=None, users_by_id=None, config=None, **_kw): explicit_values = [] derived_values = [] for fv in art.field_values: # TODO(jrobbins): for cross-project search this could be a list. fd = tracker_bizobj.FindFieldDefByID(fv.field_id, config) if not fd: # TODO(jrobbins): This can happen if an issue with a custom # field value is moved to a different project. logging.warn('Issue ID %r has undefined field value %r', art.issue_id, fv) elif fd.field_name.lower() == col: val = tracker_bizobj.GetFieldValue(fv, users_by_id) if fv.derived: derived_values.append(val) else: explicit_values.append(val) TableCell.__init__(self, CELL_TYPE_ATTR, explicit_values, derived_values=derived_values)
def __init__(self, art, col=None, users_by_id=None, config=None, **_kw): explicit_values = [] derived_values = [] cell_type = CELL_TYPE_ATTR phase_names_by_id = { phase.phase_id: phase.name.lower() for phase in art.phases } phase_name = None # Check if col represents a phase field value in the form <phase>.<field> if '.' in col: phase_name, col = col.split('.', 1) for fv in art.field_values: # TODO(jrobbins): for cross-project search this could be a list. fd = tracker_bizobj.FindFieldDefByID(fv.field_id, config) if not fd: # TODO(jrobbins): This can happen if an issue with a custom # field value is moved to a different project. logging.warn('Issue ID %r has undefined field value %r', art.issue_id, fv) elif fd.field_name.lower() == col and (phase_names_by_id.get( fv.phase_id) == phase_name): if fd.field_type == tracker_pb2.FieldTypes.URL_TYPE: cell_type = CELL_TYPE_URL if fd.field_type == tracker_pb2.FieldTypes.STR_TYPE: self.NOWRAP = ezt.boolean(False) val = tracker_bizobj.GetFieldValue(fv, users_by_id) if fv.derived: derived_values.append(val) else: explicit_values.append(val) TableCell.__init__(self, cell_type, explicit_values, derived_values=derived_values)
def ComputeUnshownColumns(results, shown_columns, config, built_in_cols): """Return a list of unshown columns that the user could add. Args: results: list of search result PBs. Each must have labels. shown_columns: list of column names to be used in results table. config: harmonized config for the issue search, including all well known labels and custom fields. built_in_cols: list of other column names that are built into the tool. E.g., star count, or creation date. Returns: List of column names to append to the "..." menu. """ unshown_set = set() # lowercases column names unshown_list = [] # original-case column names shown_set = {col.lower() for col in shown_columns} labels_already_seen = set() # whole labels, original case def _MaybeAddLabel(label_name): """Add the key part of the given label if needed.""" if label_name.lower() in labels_already_seen: return labels_already_seen.add(label_name.lower()) if '-' in label_name: col, _value = label_name.split('-', 1) _MaybeAddCol(col) def _MaybeAddCol(col): if col.lower() not in shown_set and col.lower() not in unshown_set: unshown_list.append(col) unshown_set.add(col.lower()) # The user can always add any of the default columns. for col in config.default_col_spec.split(): _MaybeAddCol(col) # The user can always add any of the built-in columns. for col in built_in_cols: _MaybeAddCol(col) # The user can add a column for any well-known labels for wkl in config.well_known_labels: _MaybeAddLabel(wkl.label) phase_names = set( itertools.chain.from_iterable((phase.name.lower() for phase in result.phases) for result in results)) # The user can add a column for any custom field field_ids_alread_seen = set() for fd in config.field_defs: field_lower = fd.field_name.lower() field_ids_alread_seen.add(fd.field_id) if fd.is_phase_field: for name in phase_names: phase_field_col = name + '.' + field_lower if (phase_field_col not in shown_set and phase_field_col not in unshown_set): unshown_list.append(phase_field_col) unshown_set.add(phase_field_col) elif field_lower not in shown_set and field_lower not in unshown_set: unshown_list.append(fd.field_name) unshown_set.add(field_lower) if fd.field_type == tracker_pb2.FieldTypes.APPROVAL_TYPE: approval_lower_approver = (field_lower + tracker_constants.APPROVER_COL_SUFFIX) if (approval_lower_approver not in shown_set and approval_lower_approver not in unshown_set): unshown_list.append(fd.field_name + tracker_constants.APPROVER_COL_SUFFIX) unshown_set.add(approval_lower_approver) # The user can add a column for any key-value label or field in the results. for r in results: for label_name in tracker_bizobj.GetLabels(r): _MaybeAddLabel(label_name) for field_value in r.field_values: if field_value.field_id not in field_ids_alread_seen: field_ids_alread_seen.add(field_value.field_id) fd = tracker_bizobj.FindFieldDefByID(field_value.field_id, config) if fd: # could be None for a foreign field, which we don't display. field_lower = fd.field_name.lower() if field_lower not in shown_set and field_lower not in unshown_set: unshown_list.append(fd.field_name) unshown_set.add(field_lower) return sorted(unshown_list)
def ExtractUniqueValues(columns, artifact_list, users_by_id, config, related_issues, hotlist_context_dict=None): """Build a nested list of unique values so the user can auto-filter. Args: columns: a list of lowercase column name strings, which may contain combined columns like "priority/pri". artifact_list: a list of artifacts in the complete set of search results. users_by_id: dict mapping user_ids to UserViews. config: ProjectIssueConfig PB for the current project. related_issues: dict {issue_id: issue} of pre-fetched related issues. hotlist_context_dict: dict for building a hotlist grid table Returns: [EZTItem(col1, colname1, [val11, val12,...]), ...] A list of EZTItems, each of which has a col_index, column_name, and a list of unique values that appear in that column. """ column_values = {col_name: {} for col_name in columns} # For each combined column "a/b/c", add entries that point from "a" back # to "a/b/c", from "b" back to "a/b/c", and from "c" back to "a/b/c". combined_column_parts = collections.defaultdict(list) for col in columns: if '/' in col: for col_part in col.split('/'): combined_column_parts[col_part].append(col) unique_labels = set() for art in artifact_list: unique_labels.update(tracker_bizobj.GetLabels(art)) for label in unique_labels: if '-' in label: col, val = label.split('-', 1) col = col.lower() if col in column_values: column_values[col][val.lower()] = val if col in combined_column_parts: for combined_column in combined_column_parts[col]: column_values[combined_column][val.lower()] = val else: if 'summary' in column_values: column_values['summary'][label.lower()] = label # TODO(jrobbins): Consider refacting some of this to tracker_bizobj # or a new builtins.py to reduce duplication. if 'reporter' in column_values: for art in artifact_list: reporter_id = art.reporter_id if reporter_id and reporter_id in users_by_id: reporter_username = users_by_id[reporter_id].display_name column_values['reporter'][ reporter_username] = reporter_username if 'owner' in column_values: for art in artifact_list: owner_id = tracker_bizobj.GetOwnerId(art) if owner_id and owner_id in users_by_id: owner_username = users_by_id[owner_id].display_name column_values['owner'][owner_username] = owner_username if 'cc' in column_values: for art in artifact_list: cc_ids = tracker_bizobj.GetCcIds(art) for cc_id in cc_ids: if cc_id and cc_id in users_by_id: cc_username = users_by_id[cc_id].display_name column_values['cc'][cc_username] = cc_username if 'component' in column_values: for art in artifact_list: all_comp_ids = list(art.component_ids) + list( art.derived_component_ids) for component_id in all_comp_ids: cd = tracker_bizobj.FindComponentDefByID(component_id, config) if cd: column_values['component'][cd.path] = cd.path if 'stars' in column_values: for art in artifact_list: star_count = art.star_count column_values['stars'][star_count] = star_count if 'status' in column_values: for art in artifact_list: status = tracker_bizobj.GetStatus(art) if status: column_values['status'][status.lower()] = status if 'project' in column_values: for art in artifact_list: project_name = art.project_name column_values['project'][project_name] = project_name if 'mergedinto' in column_values: for art in artifact_list: if art.merged_into and art.merged_into != 0: merged_issue = related_issues[art.merged_into] merged_issue_ref = tracker_bizobj.FormatIssueRef( (merged_issue.project_name, merged_issue.local_id)) column_values['mergedinto'][ merged_issue_ref] = merged_issue_ref if 'blocked' in column_values: for art in artifact_list: if art.blocked_on_iids: column_values['blocked']['is_blocked'] = 'Yes' else: column_values['blocked']['is_not_blocked'] = 'No' if 'blockedon' in column_values: for art in artifact_list: if art.blocked_on_iids: for blocked_on_iid in art.blocked_on_iids: blocked_on_issue = related_issues[blocked_on_iid] blocked_on_ref = tracker_bizobj.FormatIssueRef( (blocked_on_issue.project_name, blocked_on_issue.local_id)) column_values['blockedon'][blocked_on_ref] = blocked_on_ref if 'blocking' in column_values: for art in artifact_list: if art.blocking_iids: for blocking_iid in art.blocking_iids: blocking_issue = related_issues[blocking_iid] blocking_ref = tracker_bizobj.FormatIssueRef( (blocking_issue.project_name, blocking_issue.local_id)) column_values['blocking'][blocking_ref] = blocking_ref if 'added' in column_values: for art in artifact_list: if hotlist_context_dict and hotlist_context_dict[art.issue_id]: issue_dict = hotlist_context_dict[art.issue_id] date_added = issue_dict['date_added'] column_values['added'][date_added] = date_added if 'adder' in column_values: for art in artifact_list: if hotlist_context_dict and hotlist_context_dict[art.issue_id]: issue_dict = hotlist_context_dict[art.issue_id] adder_id = issue_dict['adder_id'] adder = users_by_id[adder_id].display_name column_values['adder'][adder] = adder if 'note' in column_values: for art in artifact_list: if hotlist_context_dict and hotlist_context_dict[art.issue_id]: issue_dict = hotlist_context_dict[art.issue_id] note = issue_dict['note'] if issue_dict['note']: column_values['note'][note] = note if 'attachments' in column_values: for art in artifact_list: attachment_count = art.attachment_count column_values['attachments'][attachment_count] = attachment_count # Add all custom field values if the custom field name is a shown column. field_id_to_col = {} for art in artifact_list: for fv in art.field_values: field_col, field_type = field_id_to_col.get( fv.field_id, (None, None)) if field_col == 'NOT_SHOWN': continue if field_col is None: fd = tracker_bizobj.FindFieldDefByID(fv.field_id, config) if not fd: field_id_to_col[fv.field_id] = 'NOT_SHOWN', None continue field_col = fd.field_name.lower() field_type = fd.field_type if field_col not in column_values: field_id_to_col[fv.field_id] = 'NOT_SHOWN', None continue field_id_to_col[fv.field_id] = field_col, field_type if field_type == tracker_pb2.FieldTypes.ENUM_TYPE: continue # Already handled by label parsing elif field_type == tracker_pb2.FieldTypes.INT_TYPE: val = fv.int_value elif field_type == tracker_pb2.FieldTypes.STR_TYPE: val = fv.str_value elif field_type == tracker_pb2.FieldTypes.USER_TYPE: user = users_by_id.get(fv.user_id) val = user.email if user else framework_constants.NO_USER_NAME elif field_type == tracker_pb2.FieldTypes.DATE_TYPE: val = fv.int_value # TODO(jrobbins): convert to date elif field_type == tracker_pb2.FieldTypes.BOOL_TYPE: val = 'Yes' if fv.int_value else 'No' column_values[field_col][val] = val # TODO(jrobbins): make the capitalization of well-known unique label and # status values match the way it is written in the issue config. # Return EZTItems for each column in left-to-right display order. result = [] for i, col_name in enumerate(columns): # TODO(jrobbins): sort each set of column values top-to-bottom, by the # order specified in the project artifact config. For now, just sort # lexicographically to make expected output defined. sorted_col_values = sorted(column_values[col_name].values()) result.append( template_helpers.EZTItem(col_index=i, column_name=col_name, filter_values=sorted_col_values)) return result
def testFindFieldDefByID_Empty(self): config = tracker_pb2.ProjectIssueConfig() self.assertIsNone(tracker_bizobj.FindFieldDefByID(1, config))
def testFindFieldDefByID_Normal(self): config = tracker_bizobj.MakeDefaultProjectIssueConfig(789) fd = tracker_pb2.FieldDef(field_id=1) config.field_defs = [fd] self.assertEqual(fd, tracker_bizobj.FindFieldDefByID(1, config)) self.assertIsNone(tracker_bizobj.FindFieldDefByID(99, config))
def testFindFieldDefByID_Default(self): config = tracker_bizobj.MakeDefaultProjectIssueConfig(789) self.assertIsNone(tracker_bizobj.FindFieldDefByID(1, config))
def __init__(self, field_def, config, user_views=None, approval_def=None): super(FieldDefView, self).__init__(field_def) self.type_name = str(field_def.field_type) self.choices = [] if field_def.field_type == tracker_pb2.FieldTypes.ENUM_TYPE: self.choices = tracker_helpers.LabelsMaskedByFields( config, [field_def.field_name], trim_prefix=True) self.approvers = [] self.survey = '' self.survey_questions = [] if (approval_def and field_def.field_type == tracker_pb2.FieldTypes.APPROVAL_TYPE): self.approvers = [ user_views.get(approver_id) for approver_id in approval_def.approver_ids ] if approval_def.survey: self.survey = approval_def.survey self.survey_questions = self.survey.split('\n') self.docstring_short = template_helpers.FitUnsafeText( field_def.docstring, 200) self.validate_help = None if field_def.is_required: self.importance = 'required' elif field_def.is_niche: self.importance = 'niche' else: self.importance = 'normal' if field_def.min_value is not None: self.min_value = field_def.min_value self.validate_help = 'Value must be >= %d' % field_def.min_value else: self.min_value = None # Otherwise it would default to 0 if field_def.max_value is not None: self.max_value = field_def.max_value self.validate_help = 'Value must be <= %d' % field_def.max_value else: self.max_value = None # Otherwise it would default to 0 if field_def.min_value is not None and field_def.max_value is not None: self.validate_help = 'Value must be between %d and %d' % ( field_def.min_value, field_def.max_value) if field_def.regex: self.validate_help = 'Value must match regex: %s' % field_def.regex if field_def.needs_member: self.validate_help = 'Value must be a project member' if field_def.needs_perm: self.validate_help = ( 'Value must be a project member with permission %s' % field_def.needs_perm) self.date_action_str = str(field_def.date_action or 'no_action').lower() self.admins = [] if user_views: self.admins = [ user_views.get(admin_id) for admin_id in field_def.admin_ids ] if field_def.approval_id: self.is_approval_subfield = ezt.boolean(True) self.parent_approval_name = tracker_bizobj.FindFieldDefByID( field_def.approval_id, config).field_name else: self.is_approval_subfield = ezt.boolean(False) self.is_phase_field = ezt.boolean(field_def.is_phase_field)
def HandleRequest(self, mr): """Process the task to notify users after an approval change. Args: mr: common information parsed from the HTTP request. Returns: Results dictionary in JSON format which is useful just for debugging. The main goal is the side-effect of sending emails. """ send_email = bool(mr.GetIntParam('send_email')) issue_id = mr.GetPositiveIntParam('issue_id') approval_id = mr.GetPositiveIntParam('approval_id') comment_id = mr.GetPositiveIntParam('comment_id') hostport = mr.GetParam('hostport') params = dict( temporary='', hostport=hostport, issue_id=issue_id ) logging.info('approval change params are %r', params) issue, approval_value = self.services.issue.GetIssueApproval( mr.cnxn, issue_id, approval_id, use_cache=False) project = self.services.project.GetProject(mr.cnxn, issue.project_id) config = self.services.config.GetProjectConfig(mr.cnxn, issue.project_id) approval_fd = tracker_bizobj.FindFieldDefByID(approval_id, config) if approval_fd is None: raise exceptions.NoSuchFieldDefException() # GetCommentsForIssue will fill the sequence for all comments, while # other method for getting a single comment will not. # The comment sequence is especially useful for Approval issues with # many comment sections. comment = None all_comments = self.services.issue.GetCommentsForIssue(mr.cnxn, issue_id) for c in all_comments: if c.id == comment_id: comment = c break if not comment: raise exceptions.NoSuchCommentException() field_user_ids = set() relevant_fds = [fd for fd in config.field_defs if not fd.approval_id or fd.approval_id is approval_value.approval_id] for fd in relevant_fds: field_user_ids.update( notify_reasons.ComputeNamedUserIDsToNotify(issue.field_values, fd)) users_by_id = framework_views.MakeAllUserViews( mr.cnxn, self.services.user, [issue.owner_id], approval_value.approver_ids, tracker_bizobj.UsersInvolvedInComment(comment), list(field_user_ids)) tasks = [] if send_email: tasks = self._MakeApprovalEmailTasks( hostport, issue, project, approval_value, approval_fd.field_name, comment, users_by_id, list(field_user_ids), mr.perms) notified = notify_helpers.AddAllEmailTasks(tasks) return { 'params': params, 'notified': notified, 'tasks': tasks, }