def _CalculateIssuePings(self, issue, config): """Return a list of (field, timestamp) pairs for dates that should ping.""" timestamp_min, timestamp_max = _GetTimestampRange(int(time.time())) arrived_dates_by_field_id = { fv.field_id: fv.date_value for fv in issue.field_values if timestamp_min <= fv.date_value < timestamp_max } logging.info('arrived_dates_by_field_id = %r', arrived_dates_by_field_id) # TODO(jrobbins): Lookup field defs regardless of project_id to better # handle foreign fields in issues that have been moved between projects. pings = [(field, arrived_dates_by_field_id[field.field_id]) for field in config.field_defs if (field.field_id in arrived_dates_by_field_id and field. date_action in (tracker_pb2.DateAction.PING_OWNER_ONLY, tracker_pb2.DateAction.PING_PARTICIPANTS)) ] # TODO(jrobbins): For now, assume all pings apply only to open issues. # Later, allow each date action to specify whether it applies to open # issues or all issues. means_open = tracker_helpers.MeansOpenInProject( tracker_bizobj.GetStatus(issue), config) pings = [ping for ping in pings if means_open] pings = sorted(pings, key=lambda ping: ping[0].field_name) return pings
def ConvertStatusRef(explicit_status, derived_status, config): """Use the given status strings to create a StatusRef.""" status = explicit_status or derived_status is_derived = not explicit_status if not status: return common_pb2.StatusRef(status=framework_constants.NO_VALUES, is_derived=False, means_open=True) return common_pb2.StatusRef(status=status, is_derived=is_derived, means_open=tracker_helpers.MeansOpenInProject( status, config))
def testGetStatusWithoutOwner(self, status, means_open, expected_status): """Tests GetStatus without an owner.""" self.test_msg.replace_header(AlertEmailHeader.STATUS, status) self.mox.StubOutWithMock(tracker_helpers, 'MeansOpenInProject') tracker_helpers.MeansOpenInProject( status, mox.IgnoreArg()).AndReturn(means_open) self.mox.ReplayAll() props = alert2issue.GetAlertProperties(self.services, self.cnxn, self.project_id, self.incident_id, self.trooper_queue, self.test_msg) self.assertCaseInsensitiveEqual(props['status'], expected_status) self.mox.VerifyAll()
def StatusDefsAsText(config): """Return two strings for editing open and closed status definitions.""" open_lines = [] closed_lines = [] for wks in config.well_known_statuses: line = '%s%s%s%s' % ('#' if wks.deprecated else '', wks.status.ljust( 20), '\t= ' if wks.status_docstring else '', wks.status_docstring) if tracker_helpers.MeansOpenInProject(wks.status, config): open_lines.append(line) else: closed_lines.append(line) open_text = '\n'.join(open_lines) closed_text = '\n'.join(closed_lines) logging.info('open_text is \n%s', open_text) logging.info('closed_text is \n%s', closed_text) return open_text, closed_text
def _make_issue_view(default_pn, config, viewable_iids_set, ref_issue): viewable = ref_issue.issue_id in viewable_iids_set return { "id": tracker_bizobj.FormatIssueRef( (ref_issue.project_name, ref_issue.local_id), default_project_name=default_pn), "href": tracker_helpers.FormatRelativeIssueURL(ref_issue.project_name, urls.ISSUE_DETAIL, id=ref_issue.local_id), "closed": ezt.boolean( viewable and not tracker_helpers.MeansOpenInProject(ref_issue.status, config)), "title": ref_issue.summary if viewable else "" }
def _GetStatus(proj_config, owner_id, status): # XXX: what if assigned and available are not in known_statuses? if status: status = status.strip().lower() if owner_id: # If there is an owner, the status must be 'Assigned'. if status and status != 'assigned': logging.info( 'invalid status %s for an alert with an owner; default to assigned', status) return 'assigned' if status: if tracker_helpers.MeansOpenInProject(status, proj_config): return status logging.info('invalid status %s for an alert; default to available', status) return 'available'
def __init__(self, mr, services, config): """Gather data for the issue section of a project admin page. Args: mr: MonorailRequest, including a database connection, the current project, and authenticated user IDs. services: Persist services with ProjectService, ConfigService, and UserService included. config: ProjectIssueConfig for the current project.. Returns: Project info in a dict suitable for EZT. """ super(ConfigView, self).__init__(config) self.open_statuses = [] self.closed_statuses = [] for wks in config.well_known_statuses: item = template_helpers.EZTItem( name=wks.status, name_padded=wks.status.ljust(20), commented='#' if wks.deprecated else '', docstring=wks.status_docstring) if tracker_helpers.MeansOpenInProject(wks.status, config): self.open_statuses.append(item) else: self.closed_statuses.append(item) self.templates = [ IssueTemplateView(mr, tmpl, services.user, config) for tmpl in config.templates] for index, template in enumerate(self.templates): template.index = index self.field_names = [ # TODO(jrobbins): field-level controls fd.field_name for fd in config.field_defs if not fd.is_deleted] self.issue_labels = tracker_helpers.LabelsNotMaskedByFields( config, self.field_names) self.excl_prefixes = [ prefix.lower() for prefix in config.exclusive_label_prefixes] self.restrict_to_known = ezt.boolean(config.restrict_to_known) self.default_col_spec = ( config.default_col_spec or tracker_constants.DEFAULT_COL_SPEC)
def __init__(self, mr, services, config, template=None, load_all_templates=False): """Gather data for the issue section of a project admin page. Args: mr: MonorailRequest, including a database connection, the current project, and authenticated user IDs. services: Persist services with ProjectService, ConfigService, TemplateService and UserService included. config: ProjectIssueConfig for the current project.. template (TemplateDef, optional): the current template. load_all_templates (boolean): default False. If true loads self.templates. Returns: Project info in a dict suitable for EZT. """ super(ConfigView, self).__init__(config) self.open_statuses = [] self.closed_statuses = [] for wks in config.well_known_statuses: item = template_helpers.EZTItem( name=wks.status, name_padded=wks.status.ljust(20), commented='#' if wks.deprecated else '', docstring=wks.status_docstring) if tracker_helpers.MeansOpenInProject(wks.status, config): self.open_statuses.append(item) else: self.closed_statuses.append(item) is_member = framework_bizobj.UserIsInProject(mr.project, mr.auth.effective_ids) template_set = services.template.GetTemplateSetForProject( mr.cnxn, config.project_id) # Filter non-viewable templates self.template_names = [] for _, template_name, members_only in template_set: if members_only and not is_member: continue self.template_names.append(template_name) if load_all_templates: templates = services.template.GetProjectTemplates( mr.cnxn, config.project_id) self.templates = [ IssueTemplateView(mr, tmpl, services.user, config) for tmpl in templates ] for index, template_view in enumerate(self.templates): template_view.index = index if template: self.template_view = IssueTemplateView(mr, template, services.user, config) self.field_names = [ # TODO(jrobbins): field-level controls fd.field_name for fd in config.field_defs if fd.field_type is tracker_pb2.FieldTypes.ENUM_TYPE and not fd.is_deleted ] self.issue_labels = tracker_helpers.LabelsNotMaskedByFields( config, self.field_names) self.excl_prefixes = [ prefix.lower() for prefix in config.exclusive_label_prefixes ] self.restrict_to_known = ezt.boolean(config.restrict_to_known) self.default_col_spec = (config.default_col_spec or tracker_constants.DEFAULT_COL_SPEC)
def __init__(self, issue, users_by_id, config): """Store relevant values for later display by EZT. Args: issue: An Issue protocol buffer. users_by_id: dict {user_id: UserViews} for all users mentioned in issue. config: ProjectIssueConfig for this issue. """ super(IssueView, self).__init__(issue) # The users involved in this issue must be present in users_by_id if # this IssueView is to be used on the issue detail or peek pages. But, # they can be absent from users_by_id if the IssueView is used as a # tile in the grid view. self.owner = users_by_id.get(issue.owner_id) self.derived_owner = users_by_id.get(issue.derived_owner_id) self.cc = [users_by_id.get(cc_id) for cc_id in issue.cc_ids if cc_id] self.derived_cc = [ users_by_id.get(cc_id) for cc_id in issue.derived_cc_ids if cc_id ] self.status = framework_views.StatusView(issue.status, config) self.derived_status = framework_views.StatusView( issue.derived_status, config) # If we don't have a config available, we don't need to access is_open, so # let it be True. self.is_open = ezt.boolean( not config or tracker_helpers.MeansOpenInProject( tracker_bizobj.GetStatus(issue), config)) self.components = sorted([ ComponentValueView(component_id, config, False) for component_id in issue.component_ids if tracker_bizobj.FindComponentDefByID(component_id, config) ] + [ ComponentValueView(component_id, config, True) for component_id in issue.derived_component_ids if tracker_bizobj.FindComponentDefByID(component_id, config) ], key=lambda cvv: cvv.path) self.fields = MakeAllFieldValueViews(config, issue.labels, issue.derived_labels, issue.field_values, users_by_id) labels, derived_labels = tracker_bizobj.ExplicitAndDerivedNonMaskedLabels( issue, config) self.labels = [ framework_views.LabelView(label, config) for label in labels ] self.derived_labels = [ framework_views.LabelView(label, config) for label in derived_labels ] self.restrictions = _RestrictionsView(issue) # TODO(jrobbins): sort by order of labels in project config self.short_summary = issue.summary[:tracker_constants. SHORT_SUMMARY_LENGTH] if issue.closed_timestamp: self.closed = timestr.FormatAbsoluteDate(issue.closed_timestamp) else: self.closed = '' self.blocked_on = [] self.has_dangling = ezt.boolean(self.dangling_blocked_on_refs) self.blocking = [] self.detail_relative_url = tracker_helpers.FormatRelativeIssueURL( issue.project_name, urls.ISSUE_DETAIL, id=issue.local_id) self.crbug_url = tracker_helpers.FormatCrBugURL( issue.project_name, issue.local_id)
def convert_issue(cls, issue, mar, services): """Convert Monorail Issue PB to API IssuesGetInsertResponse.""" config = services.config.GetProjectConfig(mar.cnxn, issue.project_id) granted_perms = tracker_bizobj.GetGrantedPerms(issue, mar.auth.effective_ids, config) issue_project = services.project.GetProject(mar.cnxn, issue.project_id) component_list = [] for cd in config.component_defs: cid = cd.component_id if cid in issue.component_ids: component_list.append(cd.path) cc_list = [convert_person(p, mar.cnxn, services) for p in issue.cc_ids] cc_list = [p for p in cc_list if p is not None] field_values_list = [] fds_by_id = {fd.field_id: fd for fd in config.field_defs} phases_by_id = {phase.phase_id: phase for phase in issue.phases} for fv in issue.field_values: fd = fds_by_id.get(fv.field_id) if not fd: logging.warning('Custom field %d of project %s does not exist', fv.field_id, issue_project.project_name) continue val = None if fv.user_id: val = _get_user_email(services.user, mar.cnxn, fv.user_id) else: val = tracker_bizobj.GetFieldValue(fv, {}) if not isinstance(val, string_types): val = str(val) new_fv = api_pb2_v1.FieldValue(fieldName=fd.field_name, fieldValue=val, derived=fv.derived) if fd.approval_id: # Attach parent approval name approval_fd = fds_by_id.get(fd.approval_id) if not approval_fd: logging.warning( 'Parent approval field %d of field %s does not exist', fd.approval_id, fd.field_name) else: new_fv.approvalName = approval_fd.field_name elif fv.phase_id: # Attach phase name phase = phases_by_id.get(fv.phase_id) if not phase: logging.warning('Phase %d for field %s does not exist', fv.phase_id, fd.field_name) else: new_fv.phaseName = phase.name field_values_list.append(new_fv) approval_values_list = convert_approvals(mar.cnxn, issue.approval_values, services, config, issue.phases) phases_list = convert_phases(issue.phases) with work_env.WorkEnv(mar, services) as we: starred = we.IsIssueStarred(issue) resp = cls( kind='monorail#issue', id=issue.local_id, title=issue.summary, summary=issue.summary, projectId=issue_project.project_name, stars=issue.star_count, starred=starred, status=issue.status, state=(api_pb2_v1.IssueState.open if tracker_helpers.MeansOpenInProject( tracker_bizobj.GetStatus(issue), config) else api_pb2_v1.IssueState.closed), labels=issue.labels, components=component_list, author=convert_person(issue.reporter_id, mar.cnxn, services), owner=convert_person(issue.owner_id, mar.cnxn, services), cc=cc_list, updated=datetime.datetime.fromtimestamp(issue.modified_timestamp), published=datetime.datetime.fromtimestamp(issue.opened_timestamp), blockedOn=convert_issue_ids(issue.blocked_on_iids, mar, services), blocking=convert_issue_ids(issue.blocking_iids, mar, services), canComment=permissions.CanCommentIssue(mar.auth.effective_ids, mar.perms, issue_project, issue, granted_perms=granted_perms), canEdit=permissions.CanEditIssue(mar.auth.effective_ids, mar.perms, issue_project, issue, granted_perms=granted_perms), fieldValues=field_values_list, approvalValues=approval_values_list, phases=phases_list) if issue.closed_timestamp > 0: resp.closed = datetime.datetime.fromtimestamp(issue.closed_timestamp) if issue.merged_into: resp.mergedInto = convert_issue_ids([issue.merged_into], mar, services)[0] if issue.owner_modified_timestamp: resp.owner_modified = datetime.datetime.fromtimestamp( issue.owner_modified_timestamp) if issue.status_modified_timestamp: resp.status_modified = datetime.datetime.fromtimestamp( issue.status_modified_timestamp) if issue.component_modified_timestamp: resp.component_modified = datetime.datetime.fromtimestamp( issue.component_modified_timestamp) return resp
def _ApplyCond( cnxn, services, project, term, issue, label_set, config, owner_id, cc_ids, status): """Return True if the given issue satisfied the given predicate term.""" op = term.op vals = term.str_values or term.int_values # Since rules are per-project, there'll be exactly 1 field fd = term.field_defs[0] field = fd.field_name if field == 'label': return _Compare(op, vals, label_set) if field == 'component': return _CompareComponents(config, op, vals, issue.component_ids) if field == 'any_field': return _Compare(op, vals, label_set) or _Compare(op, vals, [issue.summary]) if field == 'attachments': return _Compare(op, term.int_values, [issue.attachment_count]) if field == 'blocked': return _Compare(op, vals, issue.blocked_on_iids) if field == 'blockedon': return _CompareIssueRefs( cnxn, services, project, op, term.str_values, issue.blocked_on_iids) if field == 'blocking': return _CompareIssueRefs( cnxn, services, project, op, term.str_values, issue.blocking_iids) if field == 'cc': return _CompareUsers(cnxn, services.user, op, vals, cc_ids) if field == 'closed': return (issue.closed_timestamp and _Compare(op, vals, [issue.closed_timestamp])) if field == 'id': return _Compare(op, vals, [issue.local_id]) if field == 'mergedinto': return _CompareIssueRefs( cnxn, services, project, op, term.str_values, [issue.merged_into or 0]) if field == 'modified': return (issue.modified_timestamp and _Compare(op, vals, [issue.modified_timestamp])) if field == 'open': # TODO(jrobbins): this just checks the explicit status, not the result # of any previous rules. return tracker_helpers.MeansOpenInProject(status, config) if field == 'opened': return (issue.opened_timestamp and _Compare(op, vals, [issue.opened_timestamp])) if field == 'owner': return _CompareUsers(cnxn, services.user, op, vals, [owner_id]) if field == 'reporter': return _CompareUsers(cnxn, services.user, op, vals, [issue.reporter_id]) if field == 'stars': return _Compare(op, term.int_values, [issue.star_count]) if field == 'status': return _Compare(op, vals, [status.lower()]) if field == 'summary': return _Compare(op, vals, [issue.summary]) # Since rules are per-project, it makes no sense to support field project. # We would need to load comments to support fields comment, commentby, # description, attachment. # Supporting starredby is probably not worth the complexity. logging.info('Rule with unsupported field %r was False', field) return False
def convert_issue(cls, issue, mar, services): """Convert Monorail Issue PB to API IssuesGetInsertResponse.""" config = services.config.GetProjectConfig(mar.cnxn, issue.project_id) granted_perms = tracker_bizobj.GetGrantedPerms( issue, mar.auth.effective_ids, config) issue_project = services.project.GetProject(mar.cnxn, issue.project_id) component_list = [] for cd in config.component_defs: cid = cd.component_id if cid in issue.component_ids: component_list.append(cd.path) cc_list = [convert_person(p, mar.cnxn, services) for p in issue.cc_ids] cc_list = [p for p in cc_list if p is not None] field_values_list = [] field_id_dict = { fd.field_id: fd.field_name for fd in config.field_defs} for fv in issue.field_values: field_name = field_id_dict.get(fv.field_id) if not field_name: logging.warning('Custom field %d of project %s does not exist', fv.field_id, issue_project.project_name) continue val = None if fv.user_id: val = _get_user_email( services.user, mar.cnxn, fv.user_id) elif fv.str_value: val = fv.str_value elif fv.int_value: val = str(fv.int_value) new_fv = api_pb2_v1.FieldValue( fieldName=field_name, fieldValue=val, derived=fv.derived) field_values_list.append(new_fv) resp = cls( kind='monorail#issue', id=issue.local_id, title=issue.summary, summary=issue.summary, projectId=issue_project.project_name, stars=issue.star_count, starred=services.issue_star.IsItemStarredBy( mar.cnxn, issue.issue_id, mar.auth.user_id), status=issue.status, state=(api_pb2_v1.IssueState.open if tracker_helpers.MeansOpenInProject( tracker_bizobj.GetStatus(issue), config) else api_pb2_v1.IssueState.closed), labels=issue.labels, components=component_list, author=convert_person(issue.reporter_id, mar.cnxn, services), owner=convert_person(issue.owner_id, mar.cnxn, services), cc=cc_list, updated=datetime.datetime.fromtimestamp(issue.modified_timestamp), published=datetime.datetime.fromtimestamp(issue.opened_timestamp), blockedOn=convert_issue_ids(issue.blocked_on_iids, mar, services), blocking=convert_issue_ids(issue.blocking_iids, mar, services), canComment=permissions.CanCommentIssue( mar.auth.effective_ids, mar.perms, issue_project, issue, granted_perms=granted_perms), canEdit=permissions.CanEditIssue( mar.auth.effective_ids, mar.perms, issue_project, issue, granted_perms=granted_perms), fieldValues=field_values_list) if issue.closed_timestamp > 0: resp.closed = datetime.datetime.fromtimestamp(issue.closed_timestamp) if issue.merged_into: resp.mergedInto=convert_issue_ids([issue.merged_into], mar, services)[0] if issue.owner_modified_timestamp: resp.owner_modified = datetime.datetime.fromtimestamp( issue.owner_modified_timestamp) if issue.status_modified_timestamp: resp.status_modified = datetime.datetime.fromtimestamp( issue.status_modified_timestamp) if issue.component_modified_timestamp: resp.component_modified = datetime.datetime.fromtimestamp( issue.component_modified_timestamp) return resp
def __init__( self, issue, users_by_id, config, open_related=None, closed_related=None, all_related=None): """Store relevant values for later display by EZT. Args: issue: An Issue protocol buffer. users_by_id: dict {user_id: UserViews} for all users mentioned in issue. config: ProjectIssueConfig for this issue. open_related: dict of visible open issues that are related to this issue. closed_related: dict {issue_id: issue} of visible closed issues that are related to this issue. all_related: dict {issue_id: issue} of all blocked-on, blocking, or merged-into issues referenced from this issue, regardless of perms. """ super(IssueView, self).__init__(issue) # The users involved in this issue must be present in users_by_id if # this IssueView is to be used on the issue detail or peek pages. But, # they can be absent from users_by_id if the IssueView is used as a # tile in the grid view. self.owner = users_by_id.get(issue.owner_id) self.derived_owner = users_by_id.get(issue.derived_owner_id) self.cc = [users_by_id.get(cc_id) for cc_id in issue.cc_ids if cc_id] self.derived_cc = [users_by_id.get(cc_id) for cc_id in issue.derived_cc_ids if cc_id] self.status = framework_views.StatusView(issue.status, config) self.derived_status = framework_views.StatusView( issue.derived_status, config) # If we don't have a config available, we don't need to access is_open, so # let it be True. self.is_open = ezt.boolean( not config or tracker_helpers.MeansOpenInProject( tracker_bizobj.GetStatus(issue), config)) self.components = sorted( [ComponentValueView(component_id, config, False) for component_id in issue.component_ids if tracker_bizobj.FindComponentDefByID(component_id, config)] + [ComponentValueView(component_id, config, True) for component_id in issue.derived_component_ids if tracker_bizobj.FindComponentDefByID(component_id, config)], key=lambda cvv: cvv.path) self.fields = [ MakeFieldValueView( fd, config, issue.labels, issue.derived_labels, issue.field_values, users_by_id) # TODO(jrobbins): field-level view restrictions, display options for fd in config.field_defs if not fd.is_deleted] self.fields = sorted( self.fields, key=lambda f: (f.applicable_type, f.field_name)) field_names = [fd.field_name.lower() for fd in config.field_defs if not fd.is_deleted] # TODO(jrobbins): restricts self.labels = [ framework_views.LabelView(label, config) for label in tracker_bizobj.NonMaskedLabels(issue.labels, field_names)] self.derived_labels = [ framework_views.LabelView(label, config) for label in issue.derived_labels if not tracker_bizobj.LabelIsMaskedByField(label, field_names)] self.restrictions = _RestrictionsView(issue) # TODO(jrobbins): sort by order of labels in project config self.short_summary = issue.summary[:tracker_constants.SHORT_SUMMARY_LENGTH] if issue.closed_timestamp: self.closed = timestr.FormatAbsoluteDate(issue.closed_timestamp) else: self.closed = '' blocked_on_iids = issue.blocked_on_iids blocking_iids = issue.blocking_iids # Note that merged_into_str and blocked_on_str includes all issue # references, even those referring to issues that the user can't view, # so open_related and closed_related cannot be used. if all_related is not None: all_blocked_on_refs = [ (all_related[ref_iid].project_name, all_related[ref_iid].local_id) for ref_iid in issue.blocked_on_iids] all_blocked_on_refs.extend([ (r.project, r.issue_id) for r in issue.dangling_blocked_on_refs]) self.blocked_on_str = ', '.join( tracker_bizobj.FormatIssueRef( ref, default_project_name=issue.project_name) for ref in all_blocked_on_refs) all_blocking_refs = [ (all_related[ref_iid].project_name, all_related[ref_iid].local_id) for ref_iid in issue.blocking_iids] all_blocking_refs.extend([ (r.project, r.issue_id) for r in issue.dangling_blocking_refs]) self.blocking_str = ', '.join( tracker_bizobj.FormatIssueRef( ref, default_project_name=issue.project_name) for ref in all_blocking_refs) if issue.merged_into: merged_issue = all_related[issue.merged_into] merged_into_ref = merged_issue.project_name, merged_issue.local_id else: merged_into_ref = None self.merged_into_str = tracker_bizobj.FormatIssueRef( merged_into_ref, default_project_name=issue.project_name) self.blocked_on = [] self.has_dangling = ezt.boolean(self.dangling_blocked_on_refs) self.blocking = [] current_project_name = issue.project_name if open_related is not None and closed_related is not None: self.merged_into = IssueRefView( current_project_name, issue.merged_into, open_related, closed_related) self.blocked_on = [ IssueRefView(current_project_name, iid, open_related, closed_related) for iid in blocked_on_iids] self.blocked_on.extend( [DanglingIssueRefView(ref.project, ref.issue_id) for ref in issue.dangling_blocked_on_refs]) self.blocked_on = [irv for irv in self.blocked_on if irv.visible] # TODO(jrobbins): sort by irv project_name and local_id self.blocking = [ IssueRefView(current_project_name, iid, open_related, closed_related) for iid in blocking_iids] self.blocking.extend( [DanglingIssueRefView(ref.project, ref.issue_id) for ref in issue.dangling_blocking_refs]) self.blocking = [irv for irv in self.blocking if irv.visible] # TODO(jrobbins): sort by irv project_name and local_id self.multiple_blocked_on = ezt.boolean(len(self.blocked_on) >= 2) self.detail_relative_url = tracker_helpers.FormatRelativeIssueURL( issue.project_name, urls.ISSUE_DETAIL, id=issue.local_id)
def StoreIssueSnapshots(self, cnxn, issues, commit=True): """Adds an IssueSnapshot and updates the previous one for each issue.""" for issue in issues: right_now = self._currentTime() # Look for an existing (latest) IssueSnapshot with this issue_id. previous_snapshots = self.issuesnapshot_tbl.Select( cnxn, cols=ISSUESNAPSHOT_COLS, issue_id=issue.issue_id, limit=1, order_by=[('period_start DESC', [])]) if len(previous_snapshots) > 0: previous_snapshot_id = previous_snapshots[0][0] logging.info('Found previous IssueSnapshot with id: %s', previous_snapshot_id) # Update previous snapshot's end time to right now. delta = {'period_end': right_now} where = [('IssueSnapshot.id = %s', [previous_snapshot_id])] self.issuesnapshot_tbl.Update(cnxn, delta, commit=commit, where=where) config = self.config_service.GetProjectConfig( cnxn, issue.project_id) period_end = settings.maximum_snapshot_period_end is_open = tracker_helpers.MeansOpenInProject( tracker_bizobj.GetStatus(issue), config) shard = issue.issue_id % settings.num_logical_shards status = tracker_bizobj.GetStatus(issue) status_id = self.config_service.LookupStatusID( cnxn, issue.project_id, status) or None owner_id = tracker_bizobj.GetOwnerId(issue) or None issuesnapshot_rows = [(issue.issue_id, shard, issue.project_id, issue.local_id, issue.reporter_id, owner_id, status_id, right_now, period_end, is_open)] ids = self.issuesnapshot_tbl.InsertRows(cnxn, ISSUESNAPSHOT_COLS[1:], issuesnapshot_rows, replace=True, commit=commit, return_generated_ids=True) issuesnapshot_id = ids[0] # Add all labels to IssueSnapshot2Label. label_rows = [ (issuesnapshot_id, self.config_service.LookupLabelID(cnxn, issue.project_id, label)) for label in tracker_bizobj.GetLabels(issue) ] self.issuesnapshot2label_tbl.InsertRows(cnxn, ISSUESNAPSHOT2LABEL_COLS, label_rows, replace=True, commit=commit) # Add all CCs to IssueSnapshot2Cc. cc_rows = [(issuesnapshot_id, cc_id) for cc_id in tracker_bizobj.GetCcIds(issue)] self.issuesnapshot2cc_tbl.InsertRows(cnxn, ISSUESNAPSHOT2CC_COLS, cc_rows, replace=True, commit=commit) # Add all components to IssueSnapshot2Component. component_rows = [(issuesnapshot_id, component_id) for component_id in issue.component_ids] self.issuesnapshot2component_tbl.InsertRows( cnxn, ISSUESNAPSHOT2COMPONENT_COLS, component_rows, replace=True, commit=commit) # Add all components to IssueSnapshot2Hotlist. # This is raw SQL to obviate passing FeaturesService down through # the call stack wherever this function is called. # TODO(jrobbins): sort out dependencies between service classes. cnxn.Execute( ''' INSERT INTO IssueSnapshot2Hotlist (issuesnapshot_id, hotlist_id) SELECT %s, hotlist_id FROM Hotlist2Issue WHERE issue_id = %s ''', [issuesnapshot_id, issue.issue_id])