Project import generated by Copybara.

GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/tracker/fltconversion.py b/tracker/fltconversion.py
new file mode 100644
index 0000000..c26ab62
--- /dev/null
+++ b/tracker/fltconversion.py
@@ -0,0 +1,599 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file or at
+# https://developers.google.com/open-source/licenses/bsd
+
+"""FLT task to be manually triggered to convert launch issues."""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+import collections
+import logging
+import re
+import settings
+import time
+
+from businesslogic import work_env
+from framework import permissions
+from framework import exceptions
+from framework import jsonfeed
+from proto import tracker_pb2
+from tracker import template_helpers
+from tracker import tracker_bizobj
+
+PM_PREFIX = 'pm-'
+TL_PREFIX = 'tl-'
+TEST_PREFIX = 'test-'
+UX_PREFIX = 'ux-'
+
+PM_FIELD = 'pm'
+TL_FIELD = 'tl'
+TE_FIELD = 'te'
+UX_FIELD = 'ux'
+MTARGET_FIELD = 'm-target'
+MAPPROVED_FIELD = 'm-approved'
+
+CONVERSION_COMMENT = 'Automatic generating of FLT Launch data.'
+
+BROWSER_APPROVALS_TO_LABELS = {
+    'Chrome-Accessibility': 'Launch-Accessibility-',
+    'Chrome-Leadership-Exp': 'Launch-Exp-Leadership-',
+    'Chrome-Leadership-Full': 'Launch-Leadership-',
+    'Chrome-Legal': 'Launch-Legal-',
+    'Chrome-Privacy': 'Launch-Privacy-',
+    'Chrome-Security': 'Launch-Security-',
+    'Chrome-Test': 'Launch-Test-',
+    'Chrome-UX': 'Launch-UI-',
+    }
+
+OS_APPROVALS_TO_LABELS = {
+    'ChromeOS-Accessibility': 'Launch-Accessibility-',
+    'ChromeOS-Leadership-Exp': 'Launch-Exp-Leadership-',
+    'ChromeOS-Leadership-Full': 'Launch-Leadership-',
+    'ChromeOS-Legal': 'Launch-Legal-',
+    'ChromeOS-Privacy': 'Launch-Privacy-',
+    'ChromeOS-Security': 'Launch-Security-',
+    'ChromeOS-Test': 'Launch-Test-',
+    'ChromeOS-UX': 'Launch-UI-',
+    }
+
+# 'NotReviewed' not included because this should be converted to
+# the template approval's default value, eg NOT_SET OR NEEDS_REVIEW
+VALUE_TO_STATUS = {
+    'ReviewRequested': tracker_pb2.ApprovalStatus.REVIEW_REQUESTED,
+    'NeedInfo': tracker_pb2.ApprovalStatus.NEED_INFO,
+    'Yes': tracker_pb2.ApprovalStatus.APPROVED,
+    'No': tracker_pb2.ApprovalStatus.NOT_APPROVED,
+    'NA': tracker_pb2.ApprovalStatus.NA,
+    # 'Started' is not a valid label value in the chromium project,
+    # but for some reason, some labels have this value.
+    'Started': tracker_pb2.ApprovalStatus.REVIEW_STARTED,
+}
+
+# This works in the Browser and OS process because
+# BROWSER_APPROVALS_TO_LABELS and OS_APPROVALS_TO_LABELS have the same values.
+# Adding '^' before each label prefix to ensure Blah-Launch-UI-Yes is ignored
+REVIEW_LABELS_RE = re.compile('^' + '|^'.join(
+    list(OS_APPROVALS_TO_LABELS.values())))
+
+# Maps template phases to channel names in 'Launch-M-Target-80-[Channel]' labels
+BROWSER_PHASE_MAP = {
+    'beta': 'beta',
+    'stable': 'stable',
+    'stable-full': 'stable',
+    'stable-exp': 'stable-exp',
+    }
+
+PHASE_PAT = '$|'.join(list(BROWSER_PHASE_MAP.values()))
+# Matches launch milestone labels, eg. Launch-M-Target-70-Stable-Exp
+BROWSER_M_LABELS_RE = re.compile(
+    r'^Launch-M-(?P<type>Approved|Target)-(?P<m>\d\d)-'
+    r'(?P<channel>%s$)' % PHASE_PAT,
+    re.IGNORECASE)
+
+OS_PHASE_MAP = {'feature freeze': '',
+                 'branch': '',
+                'stable': 'stable',
+                'stable-full': 'stable',
+                'stable-exp': 'stable-exp',}
+# We only care about Launch-M-<type>-<m>-Stable|Stable-Exp labels for OS.
+OS_M_LABELS_RE = re.compile(
+    r'^Launch-M-(?P<type>Approved|Target)-(?P<m>\d\d)-'
+    r'(?P<channel>Stable$|Stable-Exp$)', re.IGNORECASE)
+
+CAN = 2  # Query for open issues only
+# Ensure empty group_by_spec and sort_spec so issues are sorted by 'ID'.
+GROUP_BY_SPEC = ''
+SORT_SPEC = ''
+
+CONVERT_NUM = 20
+CONVERT_START = 0
+VERIFY_NUM = 400
+
+# Queries
+QUERY_MAP = {
+    'default':
+    'Type=Launch Rollout-Type=Default OS=Windows,Mac,Linux,Android,iOS',
+    'finch': 'Type=Launch Rollout-Type=Finch OS=Windows,Mac,Linux,Android,iOS',
+    'os': 'Type=Launch OS=Chrome -OS=Windows,Mac,Linux,Android,iOS'
+    ' Rollout-Type=Default',
+    'os-finch': 'Type=Launch OS=Chrome -OS=Windows,Mac,Linux,Android,iOS'
+    ' Rollout-Type=Finch'}
+
+TEMPLATE_MAP = {
+    'default': 'Chrome Launch - Default',
+    'finch': 'Chrome Launch - Experimental',
+    'os': 'Chrome OS Launch - Default',
+    'os-finch': 'Chrome OS Launch - Experimental',
+}
+
+ProjectInfo = collections.namedtuple(
+    'ProjectInfo', 'config, q, approval_values, phases, '
+    'pm_fid, tl_fid, te_fid, ux_fid, m_target_id, m_approved_id, '
+    'phase_map, approvals_to_labels, labels_re')
+
+
+class FLTConvertTask(jsonfeed.InternalTask):
+  """FLTConvert converts current Type=Launch issues into Type=FLT-Launch."""
+
+  def AssertBasePermission(self, mr):
+    super(FLTConvertTask, self).AssertBasePermission(mr)
+    if not mr.auth.user_pb.is_site_admin:
+      raise permissions.PermissionException(
+          'Only site admins may trigger conversion job')
+
+  def UndoConversion(self, mr):
+    with work_env.WorkEnv(mr, self.services) as we:
+      pipeline = we.ListIssues(
+          'Type=FLT-Launch FLT=Conversion', ['chromium'], mr.auth.user_id,
+          CONVERT_NUM, CONVERT_START, 2, GROUP_BY_SPEC, SORT_SPEC, False)
+
+    project = self.services.project.GetProjectByName(mr.cnxn, 'chromium')
+    config = self.services.config.GetProjectConfig(mr.cnxn, project.project_id)
+    pm_id = tracker_bizobj.FindFieldDef('PM', config).field_id
+    tl_id = tracker_bizobj.FindFieldDef('TL', config).field_id
+    te_id = tracker_bizobj.FindFieldDef('TE', config).field_id
+    ux_id = tracker_bizobj.FindFieldDef('UX', config).field_id
+    for possible_stale_issue in pipeline.visible_results:
+      issue = self.services.issue.GetIssue(
+          mr.cnxn, possible_stale_issue.issue_id, use_cache=False)
+
+      issue.approval_values = []
+      issue.phases = []
+      issue.field_values = [fv for fv in issue.field_values
+                            if fv.phase_id is None]
+      issue.field_values = [fv for fv in issue.field_values
+                            if fv.field_id not in
+                            [pm_id, tl_id, te_id, ux_id]]
+      issue.labels.remove('Type-FLT-Launch')
+      issue.labels.remove('FLT-Conversion')
+      issue.labels.append('Type-Launch')
+
+      self.services.issue._UpdateIssuesApprovals(mr.cnxn, issue)
+      self.services.issue.UpdateIssue(mr.cnxn, issue)
+    return {'deleting': [issue.local_id for issue in pipeline.visible_results],
+            'num': len(pipeline.visible_results),
+    }
+
+  def VerifyConversion(self, mr):
+    """Verify that all FLT-Conversion issues were converted correctly."""
+    with work_env.WorkEnv(mr, self.services) as we:
+      pipeline = we.ListIssues(
+          'FLT=Conversion', ['chromium'], mr.auth.user_id, VERIFY_NUM,
+          CONVERT_START, 2, GROUP_BY_SPEC, SORT_SPEC, False)
+
+    project = self.services.project.GetProjectByName(mr.cnxn, 'chromium')
+    config = self.services.config.GetProjectConfig(mr.cnxn, project.project_id)
+    browser_approval_names = {fd.field_id: fd.field_name for fd
+                              in config.field_defs if fd.field_name in
+                              BROWSER_APPROVALS_TO_LABELS.keys()}
+    os_approval_names = {fd.field_id: fd.field_name for fd in config.field_defs
+                         if (fd.field_name in OS_APPROVALS_TO_LABELS.keys())
+                         or fd.field_name == 'ChromeOS-Enterprise'}
+    pm_id = tracker_bizobj.FindFieldDef('PM', config).field_id
+    tl_id = tracker_bizobj.FindFieldDef('TL', config).field_id
+    te_id = tracker_bizobj.FindFieldDef('TE', config).field_id
+    ux_id = tracker_bizobj.FindFieldDef('UX', config).field_id
+    mapproved_id = tracker_bizobj.FindFieldDef('M-Approved', config).field_id
+    mtarget_id = tracker_bizobj.FindFieldDef('M-Target', config).field_id
+
+    problems = []
+    for possible_stale_issue in pipeline.allowed_results:
+      issue = self.services.issue.GetIssue(
+          mr.cnxn, possible_stale_issue.issue_id, use_cache=False)
+      # Check correct template used
+      approval_names = browser_approval_names
+      approvals_to_labels = BROWSER_APPROVALS_TO_LABELS
+      m_labels_re = BROWSER_M_LABELS_RE
+      label_channel_to_phase_id = {
+          phase.name.lower(): phase.phase_id for phase in issue.phases}
+      if [l for l in issue.labels if l.startswith('OS-')] == ['OS-Chrome']:
+        approval_names = os_approval_names
+        m_labels_re = OS_M_LABELS_RE
+        approvals_to_labels = OS_APPROVALS_TO_LABELS
+        # OS default launch
+        if 'Rollout-Type-Default' in issue.labels:
+          if not all(phase.name in ['Feature Freeze', 'Branch', 'Stable']
+                     for phase in issue.phases):
+            problems.append((
+                issue.local_id, 'incorrect phases for OS default launch.'))
+        # OS finch launch
+        elif 'Rollout-Type-Finch' in issue.labels:
+          if not all(phase.name in (
+              'Feature Freeze', 'Branch', 'Stable-Exp', 'Stable-Full')
+                     for phase in issue.phases):
+            problems.append((
+                issue.local_id, 'incorrect phases for OS finch launch.'))
+        else:
+          problems.append((
+              issue.local_id,
+              'no rollout-type; should not have been converted'))
+      # Browser default launch
+      elif 'Rollout-Type-Default' in issue.labels:
+        if not all(phase.name.lower() in ['beta', 'stable']
+                   for phase in issue.phases):
+          problems.append((
+              issue.local_id, 'incorrect phases for Default rollout'))
+      # Browser finch launch
+      elif 'Rollout-Type-Finch' in issue.labels:
+        if not all(phase.name.lower() in ['beta', 'stable-exp', 'stable-full']
+                   for phase in issue.phases):
+          problems.append((
+              issue.local_id, 'incorrect phases for Finch rollout'))
+      else:
+        problems.append((
+            issue.local_id,
+            'no rollout-type; should not have been converted'))
+
+      # Check approval_values
+      for av in issue.approval_values:
+        name = approval_names.get(av.approval_id)
+        if name == 'ChromeOS-Enterprise':
+          if av.status != tracker_pb2.ApprovalStatus.NEEDS_REVIEW:
+            problems.append((issue.local_id, 'bad ChromeOS-Enterprise status'))
+          continue
+        label_pre = approvals_to_labels.get(name)
+        if not label_pre:
+          # either name was None or not found in APPROVALS_TO_LABELS
+          problems.append((issue.local_id, 'approval %s not recognized' % name))
+          continue
+        label_value = next((l[len(label_pre):] for l in issue.labels
+                            if l.startswith(label_pre)), None)
+        if (not label_value or label_value == 'NotReviewed') and av.status in [
+            tracker_pb2.ApprovalStatus.NOT_SET,
+            tracker_pb2.ApprovalStatus.NEEDS_REVIEW]:
+          continue
+        if av.status is VALUE_TO_STATUS.get(label_value):
+          continue
+        # neither of the above ifs passed
+        problems.append((issue.local_id,
+                         'approval %s has status %r for label value %s' % (
+                             name, av.status.name, label_value)))
+
+      # Check people field_values
+      expected_people_fvs = self.ConvertPeopleLabels(
+          mr, issue.labels, pm_id, tl_id, te_id, ux_id)
+      for people_fv in expected_people_fvs:
+        if people_fv not in issue.field_values:
+          if people_fv.field_id == tl_id:
+            role = 'TL'
+          elif people_fv.field_id == pm_id:
+            role = 'PM'
+          elif people_fv.field_id == ux_id:
+            role = 'UX'
+          else:
+            role = 'TE'
+          problems.append((issue.local_id, 'missing a field for %s' % role))
+
+      # Check M phase field_values
+      for label in issue.labels:
+        match = re.match(m_labels_re, label)
+        if match:
+          channel = match.group('channel')
+          if (channel.lower() == 'stable-exp'
+              and 'Rollout-Type-Default' in issue.labels):
+            # ignore stable-exp for default rollouts.
+            continue
+          milestone = match.group('m')
+          m_type = match.group('type')
+          m_id = mapproved_id if m_type == 'Approved' else mtarget_id
+          phase_id = label_channel_to_phase_id.get(
+              channel.lower(), label_channel_to_phase_id.get('stable-full'))
+          if not next((
+              fv for fv in issue.field_values
+              if fv.phase_id == phase_id and fv.field_id == m_id and
+              fv.int_value == int(milestone)), None):
+            problems.append((
+                issue.local_id, 'no phase field for label %s' % label))
+
+    return {
+        'problems found': ['issue %d: %s' % problem for problem in problems],
+        'issues verified': ['issue %d' % issue.local_id for
+                            issue in pipeline.allowed_results],
+        'num': len(pipeline.allowed_results),
+    }
+
+  def HandleRequest(self, mr):
+    """Convert Type=Launch issues to new Type=FLT-Launch issues."""
+    launch = mr.GetParam('launch')
+    if launch == 'delete':
+      return self.UndoConversion(mr)
+    if launch == 'verify':
+      return self.VerifyConversion(mr)
+    project_info = self.FetchAndAssertProjectInfo(mr)
+
+    # Search for issues:
+    with work_env.WorkEnv(mr, self.services) as we:
+      pipeline = we.ListIssues(
+          project_info.q, ['chromium'], mr.auth.user_id, CONVERT_NUM,
+          CONVERT_START, 2, GROUP_BY_SPEC, SORT_SPEC, False)
+
+    # Convert issues:
+    for possible_stale_issue in pipeline.visible_results:
+      # Note: These approval values and phases from templates will be used
+      # and modified to create approval values and phases for each issue.
+      # We need to create copies for each issue so changes are not carried
+      # over to the conversion of the next issue in the loop.
+      template_avs = self.CreateApprovalCopies(project_info.approval_values)
+      template_phases = self.CreatePhasesCopies(project_info.phases)
+      issue = self.services.issue.GetIssue(
+          mr.cnxn, possible_stale_issue.issue_id, use_cache=False)
+      new_approvals = ConvertLaunchLabels(
+          issue.labels, template_avs,
+          project_info.config.field_defs, project_info.approvals_to_labels)
+      m_fvs = ConvertMLabels(
+          issue.labels, template_phases,
+          project_info.m_target_id, project_info.m_approved_id,
+          project_info.labels_re, project_info.phase_map)
+      people_fvs = self.ConvertPeopleLabels(
+          mr, issue.labels,
+          project_info.pm_fid, project_info.tl_fid, project_info.te_fid,
+          project_info.ux_fid)
+      amendments = self.ExecuteIssueChanges(
+          project_info.config, issue, new_approvals,
+          template_phases, m_fvs + people_fvs)
+      logging.info(amendments)
+
+    return {
+        'converted_issues': [
+            issue.local_id for issue in pipeline.visible_results],
+        'num': len(pipeline.visible_results),
+        }
+
+  def CreateApprovalCopies(self, avs):
+    return [
+      tracker_pb2.ApprovalValue(
+          approval_id=av.approval_id,
+          status=av.status,
+          setter_id=av.setter_id,
+          set_on=av.set_on,
+          phase_id=av.phase_id) for av in avs
+    ]
+
+  def CreatePhasesCopies(self, phases):
+    return [
+      tracker_pb2.Phase(
+          phase_id=phase.phase_id,
+          name=phase.name,
+          rank=phase.rank) for phase in phases
+        ]
+
+  def FetchAndAssertProjectInfo(self, mr):
+    # Get request details
+    launch = mr.GetParam('launch')
+    logging.info(launch)
+    q = QUERY_MAP.get(launch)
+    template_name = TEMPLATE_MAP.get(launch)
+    assert q and template_name, 'bad launch type: %s' % launch
+
+    phase_map = (
+        OS_PHASE_MAP if launch in ['os', 'os-finch'] else BROWSER_PHASE_MAP)
+    approvals_to_labels = (
+        OS_APPROVALS_TO_LABELS if launch in ['os', 'os-finch']
+        else BROWSER_APPROVALS_TO_LABELS)
+    m_labels_re = (
+        OS_M_LABELS_RE if launch in ['os', 'os-finch'] else BROWSER_M_LABELS_RE)
+
+    # Get project, config, template, assert template in project
+    project = self.services.project.GetProjectByName(mr.cnxn, 'chromium')
+    config = self.services.config.GetProjectConfig(mr.cnxn, project.project_id)
+    template = self.services.template.GetTemplateByName(
+        mr.cnxn, template_name, project.project_id)
+    assert template, 'template %s not found in chromium project' % template_name
+
+    # Get template approval_values/phases and assert they are expected
+    approval_values, phases = template_helpers.FilterApprovalsAndPhases(
+        template.approval_values, template.phases, config)
+    assert approval_values and phases, (
+        'no approvals or phases in %s' % template_name)
+    assert all(phase.name.lower() in list(
+        phase_map.keys()) for phase in phases), (
+          'one or more phases not recognized')
+    if launch in ['finch', 'os', 'os-finch']:
+      assert all(
+          av.status is tracker_pb2.ApprovalStatus.NEEDS_REVIEW
+          for av in approval_values
+      ), '%s template not set up correctly' % launch
+
+    approval_fds = {fd.field_id: fd.field_name for fd in config.field_defs
+                    if fd.field_type is tracker_pb2.FieldTypes.APPROVAL_TYPE}
+    assert all(
+        approval_fds.get(av.approval_id) in list(approvals_to_labels.keys())
+        for av in approval_values
+        if approval_fds.get(av.approval_id) != 'ChromeOS-Enterprise'), (
+            'one or more approvals not recognized')
+    approval_def_ids = [ad.approval_id for ad in config.approval_defs]
+    assert all(av.approval_id in approval_def_ids for av in approval_values), (
+        'one or more approvals not in config.approval_defs')
+
+    # Get relevant USER_TYPE FieldDef ids and assert they exist
+    user_fds = {fd.field_name.lower(): fd.field_id for fd in config.field_defs
+                    if fd.field_type is tracker_pb2.FieldTypes.USER_TYPE}
+    logging.info('project USER_TYPE FieldDefs: %s' % user_fds)
+    pm_fid = user_fds.get(PM_FIELD)
+    assert pm_fid, 'project has no FieldDef %s' % PM_FIELD
+    tl_fid = user_fds.get(TL_FIELD)
+    assert tl_fid, 'project has no FieldDef %s' % TL_FIELD
+    te_fid = user_fds.get(TE_FIELD)
+    assert te_fid, 'project has no FieldDef %s' % TE_FIELD
+    ux_fid = user_fds.get(UX_FIELD)
+    assert ux_fid, 'project has no FieldDef %s' % UX_FIELD
+
+    # Get relevant M Phase INT_TYPE FieldDef ids and assert they exist
+    phase_int_fds = {fd.field_name.lower(): fd.field_id
+                     for fd in config.field_defs
+                     if fd.field_type is tracker_pb2.FieldTypes.INT_TYPE
+                     and fd.is_phase_field and fd.is_multivalued}
+    logging.info(
+        'project Phase INT_TYPE multivalued FieldDefs: %s' % phase_int_fds)
+    m_target_id = phase_int_fds.get(MTARGET_FIELD)
+    assert m_target_id, 'project has no FieldDef %s' % MTARGET_FIELD
+    m_approved_id = phase_int_fds.get(MAPPROVED_FIELD)
+    assert m_approved_id, 'project has no FieldDef %s' % MAPPROVED_FIELD
+
+    return ProjectInfo(config, q, approval_values, phases, pm_fid, tl_fid,
+                       te_fid, ux_fid, m_target_id, m_approved_id, phase_map,
+                       approvals_to_labels, m_labels_re)
+
+  # TODO(jojwang): mr needs to be passed in as arg and
+  # all self.mr should be changed to mr
+  def ExecuteIssueChanges(self, config, issue, new_approvals, phases, new_fvs):
+    # Apply Approval and phase changes
+    approval_defs_by_id = {ad.approval_id: ad for ad in config.approval_defs}
+    for av in new_approvals:
+      ad = approval_defs_by_id.get(av.approval_id)
+      if ad:
+        av.approver_ids = ad.approver_ids
+        survey = ''
+        if ad.survey:
+          questions = ad.survey.split('\n')
+          survey = '\n'.join(['<b>' + q + '</b>' for q in questions])
+        self.services.issue.InsertComment(
+            self.mr.cnxn, tracker_pb2.IssueComment(
+                issue_id=issue.issue_id, project_id=issue.project_id,
+                user_id=self.mr.auth.user_id, content=survey,
+                is_description=True, approval_id=av.approval_id,
+                timestamp=int(time.time())))
+      else:
+        logging.info(
+            'ERROR: ApprovalDef %r for ApprovalValue %r not valid', ad, av)
+    issue.approval_values = new_approvals
+    self.services.issue._UpdateIssuesApprovals(self.mr.cnxn, issue)
+
+    # Apply field value changes
+    issue.phases = phases
+    delta = tracker_bizobj.MakeIssueDelta(
+        None, None, [], [], [], [], ['Type-FLT-Launch', 'FLT-Conversion'],
+        ['Type-Launch'], new_fvs, [], [], [], [], [], [], None, None)
+    amendments, _ = self.services.issue.DeltaUpdateIssue(
+        self.mr.cnxn, self.services, self.mr.auth.user_id, issue.project_id,
+        config, issue, delta, comment=CONVERSION_COMMENT)
+
+    return amendments
+
+  def ConvertPeopleLabels(
+      self, mr, labels, pm_field_id, tl_field_id, te_field_id, ux_field_id):
+    field_values = []
+    pm_ldap, tl_ldap, test_ldaps, ux_ldaps = ExtractLabelLDAPs(labels)
+
+    pm_fv = self.CreateUserFieldValue(mr, pm_ldap, pm_field_id)
+    if pm_fv:
+      field_values.append(pm_fv)
+
+    tl_fv = self.CreateUserFieldValue(mr, tl_ldap, tl_field_id)
+    if tl_fv:
+      field_values.append(tl_fv)
+
+    for test_ldap in test_ldaps:
+      te_fv = self.CreateUserFieldValue(mr, test_ldap, te_field_id)
+      if te_fv:
+        field_values.append(te_fv)
+
+    for ux_ldap in ux_ldaps:
+      ux_fv = self.CreateUserFieldValue(mr, ux_ldap, ux_field_id)
+      if ux_fv:
+        field_values.append(ux_fv)
+    return field_values
+
+  def CreateUserFieldValue(self, mr, ldap, field_id):
+    if ldap is None:
+      return None
+    try:
+      user_id = self.services.user.LookupUserID(mr.cnxn, ldap+'@chromium.org')
+    except exceptions.NoSuchUserException:
+      try:
+        user_id = self.services.user.LookupUserID(mr.cnxn, ldap+'@google.com')
+      except exceptions.NoSuchUserException:
+        logging.info('No chromium.org or google.com accound found for %s', ldap)
+        return None
+    return tracker_bizobj.MakeFieldValue(
+        field_id, None, None, user_id, None, None, False)
+
+
+def ConvertMLabels(
+    labels, phases, m_target_id, m_approved_id, labels_re, phase_map):
+  field_values = []
+  for label in labels:
+    match = re.match(labels_re, label)
+    if match:
+      milestone = match.group('m')
+      m_type = match.group('type')
+      channel = match.group('channel')
+      for phase in phases:
+        # We know get(phase) will return something because
+        # we're checking before ConvertMLabels, that all phases
+        # exist in BROWSER_PHASE_MAP or OS_PHASE_MAP
+        if phase_map.get(phase.name.lower()) == channel.lower():
+          field_id = m_target_id if (
+              m_type.lower() == 'target') else m_approved_id
+          field_values.append(tracker_bizobj.MakeFieldValue(
+              field_id, int(milestone), None, None, None, None, False,
+              phase_id=phase.phase_id))
+          break  # exit phase loop if match is found.
+  return field_values
+
+
+def ConvertLaunchLabels(labels, approvals, project_fds, approvals_to_labels):
+  """Converts 'Launch-[Review]' values into statuses for given approvals."""
+  label_values = {}
+  for label in labels:
+    launch_match = REVIEW_LABELS_RE.match(label)
+    if launch_match:
+      prefix = launch_match.group()
+      value = label[len(prefix):]  # returns 'Yes' from 'Launch-UI-Yes'
+      label_values[prefix] = value
+
+  field_names_dict = {fd.field_id: fd.field_name for fd in project_fds}
+  for approval in approvals:
+    approval_name = field_names_dict.get(approval.approval_id, '')
+    old_prefix = approvals_to_labels.get(approval_name)
+    label_value = label_values.get(old_prefix, '')
+    # if label_value not found in VALUE_TO_STATUS, use current status.
+    approval.status = VALUE_TO_STATUS.get(label_value, approval.status)
+
+  return approvals
+
+
+def ExtractLabelLDAPs(labels):
+  """Extracts LDAPs from labels 'PM-', 'TL-', 'UX-', and 'test-'"""
+
+  pm_ldap = None
+  tl_ldap = None
+  test_ldaps = []
+  ux_ldaps = []
+  for label in labels:
+    label = label.lower()
+    if label.startswith(PM_PREFIX):
+      pm_ldap = label[len(PM_PREFIX):]
+    elif label.startswith(TL_PREFIX):
+      tl_ldap = label[len(TL_PREFIX):]
+    elif label.startswith(TEST_PREFIX):
+      ldap = label[len(TEST_PREFIX):]
+      if ldap:
+        test_ldaps.append(ldap)
+    elif label.startswith(UX_PREFIX):
+      ldap = label[len(UX_PREFIX):]
+      if ldap:
+        ux_ldaps.append(ldap)
+  return pm_ldap, tl_ldap, test_ldaps, ux_ldaps