github.com/shashidharatd/test-infra@v0.0.0-20171006011030-71304e1ca560/gubernator/view_pr.py (about)

     1  # Copyright 2016 The Kubernetes Authors.
     2  #
     3  # Licensed under the Apache License, Version 2.0 (the "License");
     4  # you may not use this file except in compliance with the License.
     5  # You may obtain a copy of the License at
     6  #
     7  #     http://www.apache.org/licenses/LICENSE-2.0
     8  #
     9  # Unless required by applicable law or agreed to in writing, software
    10  # distributed under the License is distributed on an "AS IS" BASIS,
    11  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  # See the License for the specific language governing permissions and
    13  # limitations under the License.
    14  
    15  import datetime
    16  import json
    17  import logging
    18  import os
    19  import time
    20  
    21  import filters
    22  import gcs_async
    23  import github.models as ghm
    24  import pull_request
    25  import view_base
    26  
    27  
    28  @view_base.memcache_memoize('pr-details://', expires=60 * 3)
    29  def pr_builds(path):
    30      """Return {job: [(build, {started.json}, {finished.json})]} for each job under gcs path."""
    31      jobs_dirs_fut = gcs_async.listdirs(path)
    32  
    33      def base(path):
    34          return os.path.basename(os.path.dirname(path))
    35  
    36      jobs_futures = [(job, gcs_async.listdirs(job)) for job in jobs_dirs_fut.get_result()]
    37      futures = []
    38  
    39      for job, builds_fut in jobs_futures:
    40          for build in builds_fut.get_result():
    41              futures.append([
    42                  base(job),
    43                  base(build),
    44                  gcs_async.read('/%sstarted.json' % build),
    45                  gcs_async.read('/%sfinished.json' % build)])
    46  
    47      futures.sort(key=lambda (job, build, s, f): (job, view_base.pad_numbers(build)), reverse=True)
    48  
    49      jobs = {}
    50      for job, build, started_fut, finished_fut in futures:
    51          started = started_fut.get_result()
    52          finished = finished_fut.get_result()
    53          if started is not None:
    54              started = json.loads(started)
    55          if finished is not None:
    56              finished = json.loads(finished)
    57          jobs.setdefault(job, []).append((build, started, finished))
    58  
    59      return jobs
    60  
    61  
    62  def pr_path(org, repo, pr, default_org, default_repo, pull_prefix):
    63      """Builds the correct gs://prefix/maybe_kubernetes/maybe_repo_org/pr."""
    64      if org == default_org and repo == default_repo:
    65          return '%s/%s' % (pull_prefix, pr)
    66      if org == default_org:
    67          return '%s/%s/%s' % (pull_prefix, repo, pr)
    68      return '%s/%s_%s/%s' % (pull_prefix, org, repo, pr)
    69  
    70  
    71  def org_repo(path, default_org, default_repo):
    72      """Converts /maybe_org/maybe_repo into (org, repo)."""
    73      parts = path.split('/')[1:]
    74      if len(parts) == 2:
    75          org, repo = parts
    76      elif len(parts) == 1:
    77          org = default_org
    78          repo = parts[0]
    79      else:
    80          org = default_org
    81          repo = default_repo
    82      return org, repo
    83  
    84  
    85  class PRHandler(view_base.BaseHandler):
    86      """Show a list of test runs for a PR."""
    87      def get(self, path, pr):
    88          # pylint: disable=too-many-locals
    89          org, repo = org_repo(path=path,
    90              default_org=self.app.config['default_org'],
    91              default_repo=self.app.config['default_repo'],
    92          )
    93          path = pr_path(org=org, repo=repo, pr=pr,
    94              pull_prefix=self.app.config['external_services'][org]['gcs_pull_prefix'],
    95              default_org=self.app.config['default_org'],
    96              default_repo=self.app.config['default_repo'],
    97          )
    98          builds = pr_builds(path)
    99          # TODO(fejta): assume all builds are monotonically increasing.
   100          for bs in builds.itervalues():
   101              if any(len(b) > 8 for b, _, _ in bs):
   102                  bs.sort(key=lambda (b, s, f): -(s or {}).get('timestamp', 0))
   103          if pr == 'batch':  # truncate batch results to last day
   104              cutoff = time.time() - 60 * 60 * 24
   105              builds = {}
   106              for job, job_builds in builds.iteritems():
   107                  builds[job] = [
   108                      (b, s, f) for b, s, f in job_builds
   109                      if not s or s.get('timestamp') > cutoff
   110                  ]
   111  
   112          max_builds, headings, rows = pull_request.builds_to_table(builds)
   113          digest = ghm.GHIssueDigest.get('%s/%s' % (org, repo), pr)
   114          self.render(
   115              'pr.html',
   116              dict(
   117                  pr=pr,
   118                  digest=digest,
   119                  max_builds=max_builds,
   120                  header=headings,
   121                  org=org,
   122                  repo=repo,
   123                  rows=rows,
   124                  path=path,
   125              )
   126          )
   127  
   128  
   129  def get_acks(login, prs):
   130      acks = {}
   131      result = ghm.GHUserState.make_key(login).get()
   132      if result:
   133          acks = result.acks
   134          if prs:
   135              # clear acks for PRs that user is no longer involved in.
   136              stale = set(acks) - set(pr.key.id() for pr in prs)
   137              if stale:
   138                  for key in stale:
   139                      result.acks.pop(key)
   140                  result.put()
   141      return acks
   142  
   143  
   144  class PRDashboard(view_base.BaseHandler):
   145      def get(self, user=None):
   146          # pylint: disable=singleton-comparison
   147          login = self.session.get('user')
   148          if not user:
   149              user = login
   150              if not user:
   151                  self.redirect('/github_auth/pr')
   152                  return
   153              logging.debug('user=%s', user)
   154          elif user == 'all':
   155              user = None
   156          qs = [ghm.GHIssueDigest.is_pr == True]
   157          if not self.request.get('all', False):
   158              qs.append(ghm.GHIssueDigest.is_open == True)
   159          if user:
   160              qs.append(ghm.GHIssueDigest.involved == user)
   161          prs = list(ghm.GHIssueDigest.query(*qs))
   162          prs.sort(key=lambda x: x.updated_at, reverse=True)
   163  
   164          acks = None
   165          if login and user == login:  # user getting their own page
   166              acks = get_acks(login, prs)
   167  
   168          fmt = self.request.get('format', 'html')
   169          if fmt == 'json':
   170              self.response.headers['Content-Type'] = 'application/json'
   171              def serial(obj):
   172                  if isinstance(obj, datetime.datetime):
   173                      return obj.isoformat()
   174                  elif isinstance(obj, ghm.GHIssueDigest):
   175                      # pylint: disable=protected-access
   176                      keys = ['repo', 'number'] + list(obj._values)
   177                      return {k: getattr(obj, k) for k in keys}
   178                  raise TypeError
   179              self.response.write(json.dumps(prs, sort_keys=True, default=serial))
   180          elif fmt == 'html':
   181              if user:
   182                  def acked(p):
   183                      if 'lgtm' in p.payload.get('labels', {}):
   184                          return True  # LGTM is an implicit Ack
   185                      if acks is None:
   186                          return False
   187                      return filters.do_get_latest(p.payload, user) <= acks.get(p.key.id(), 0)
   188                  cats = [
   189                      ('Needs Attention', lambda p: user in p.payload['attn'] and not acked(p), ''),
   190                      ('Approvable', lambda p: user in p.payload.get('approvers', []),
   191                       'is:open is:pr ("additional approvers: {0}" ' +
   192                       'OR "additional approver: {0}")'.format(user)),
   193                      ('Incoming', lambda p: user != p.payload['author'] and
   194                                             user in p.payload['assignees'],
   195                       'is:open is:pr user:kubernetes assignee:%s' % user),
   196                      ('Outgoing', lambda p: user == p.payload['author'],
   197                       'is:open is:pr user:kubernetes author:%s' % user),
   198                  ]
   199              else:
   200                  cats = [('Open Kubernetes PRs', lambda x: True,
   201                      'is:open is:pr user:kubernetes')]
   202  
   203              self.render('pr_dashboard.html', dict(
   204                  prs=prs, cats=cats, user=user, login=login, acks=acks))
   205          else:
   206              self.abort(406)
   207  
   208      def post(self):
   209          login = self.session.get('user')
   210          if not login:
   211              self.abort(403)
   212          state = ghm.GHUserState.make_key(login).get()
   213          if state is None:
   214              state = ghm.GHUserState.make(login)
   215          body = json.loads(self.request.body)
   216          if body['command'] == 'ack':
   217              delta = {'%s %s' % (body['repo'], body['number']): body['latest']}
   218              state.acks.update(delta)
   219              state.put()
   220          elif body['command'] == 'ack-clear':
   221              state.acks = {}
   222              state.put()
   223          else:
   224              self.abort(400)
   225  
   226  
   227  class PRBuildLogHandler(view_base.BaseHandler):
   228      def get(self, path):
   229          org, _ = org_repo(path=path,
   230              default_org=self.app.config['default_org'],
   231              default_repo=self.app.config['default_repo'],
   232          )
   233          self.redirect('https://storage.googleapis.com/%s/%s' % (
   234              self.app.config['external_services'][org]['gcs_pull_prefix'], path
   235          ))