Skip to content
This repository has been archived by the owner on Dec 15, 2018. It is now read-only.

Commit

Permalink
expose API endpoint for bazel targets
Browse files Browse the repository at this point in the history
Summary: This exposes per-build targets at /builds/{build_id}/targets/

Test Plan: unit tests

Reviewers: anupc

Reviewed By: anupc

Subscribers: changesbot, anupc, kylec, wwu

Differential Revision: https://tails.corp.dropbox.com/D229202
  • Loading branch information
Naphat Sanguansin committed Sep 15, 2016
1 parent e74b9c0 commit bc3eff8
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 0 deletions.
68 changes: 68 additions & 0 deletions changes/api/build_target_index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from __future__ import absolute_import, division, unicode_literals

from flask.ext.restful import reqparse, types
from sqlalchemy.orm import contains_eager
from sqlalchemy.sql import func, asc, desc

from changes.api.base import APIView
from changes.constants import Result
from changes.models.bazeltarget import BazelTarget
from changes.models.build import Build
from changes.models.job import Job


SORT_CHOICES = (
'duration',
'name'
)

RESULT_CHOICES = [r.name for r in Result] + ['']


class BuildTargetIndexAPIView(APIView):
parser = reqparse.RequestParser()
parser.add_argument('query', type=unicode, location='args')
parser.add_argument('result', type=unicode, location='args',
choices=RESULT_CHOICES)
parser.add_argument('sort', type=unicode, location='args',
choices=SORT_CHOICES, default='duration')
parser.add_argument('reverse', type=types.boolean, location='args',
default=False)

def get(self, build_id):
build = Build.query.get(build_id)
if build is None:
return self.respond({}, status_code=404)

args = self.parser.parse_args()

target_list = BazelTarget.query.options(
contains_eager('job')
).join(
Job, BazelTarget.job_id == Job.id,
).filter(
Job.build_id == build.id,
)

if args.query:
target_list = target_list.filter(
func.lower(BazelTarget.name).contains(args.query.lower()),
)

if args.result:
target_list = target_list.filter(
BazelTarget.result == Result[args.result],
)

sort_col, sort_dir = None, None
if args.sort == 'duration':
sort_col, sort_dir = BazelTarget.duration, desc
elif args.sort == 'name':
sort_col, sort_dir = BazelTarget.name, asc

if args.reverse:
sort_dir = {asc: desc, desc: asc}[sort_dir]

target_list = target_list.order_by(sort_dir(sort_col))

return self.paginate(target_list, max_per_page=None)
17 changes: 17 additions & 0 deletions changes/api/serializer/models/bazeltarget.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from changes.api.serializer import Crumbler, register
from changes.models.bazeltarget import BazelTarget


@register(BazelTarget)
class BazelTargetCrumbler(Crumbler):
def crumble(self, instance, attrs):
return {
'id': instance.id.hex,
'job': {'id': instance.job_id.hex},
'step': {'id': instance.step_id.hex},
'name': instance.name,
'duration': instance.duration or 0,
'status': instance.status,
'result': instance.result,
'dateCreated': instance.date_created,
}
2 changes: 2 additions & 0 deletions changes/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,7 @@ def configure_api_routes(app):
from changes.api.build_restart import BuildRestartAPIView
from changes.api.build_retry import BuildRetryAPIView
from changes.api.build_tag import BuildTagAPIView
from changes.api.build_target_index import BuildTargetIndexAPIView
from changes.api.build_test_index import BuildTestIndexAPIView
from changes.api.build_test_index_failures import BuildTestIndexFailuresAPIView
from changes.api.build_test_index_counts import BuildTestIndexCountsAPIView
Expand Down Expand Up @@ -679,6 +680,7 @@ def configure_api_routes(app):
api.add_resource(BuildRestartAPIView, '/builds/<uuid:build_id>/restart/')
api.add_resource(BuildRetryAPIView, '/builds/<uuid:build_id>/retry/')
api.add_resource(BuildTagAPIView, '/builds/<uuid:build_id>/tags')
api.add_resource(BuildTargetIndexAPIView, '/builds/<uuid:build_id>/targets/')
api.add_resource(BuildTestIndexAPIView, '/builds/<uuid:build_id>/tests/')
api.add_resource(BuildTestIndexFailuresAPIView, '/builds/<uuid:build_id>/tests/failures')
api.add_resource(BuildTestIndexCountsAPIView, '/builds/<uuid:build_id>/tests/counts')
Expand Down
29 changes: 29 additions & 0 deletions tests/changes/api/serializer/models/test_bazeltarget.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from datetime import datetime

from changes.api.serializer import serialize
from changes.constants import Result, Status
from changes.testutils import TestCase


class BazelTargetCrumblerTestCase(TestCase):
def test_simple(self):
project = self.create_project()
build = self.create_build(project=project)
job = self.create_job(build=build)
phase = self.create_jobphase(job)
step = self.create_jobstep(phase)
target = self.create_target(job, step,
name='target_foo',
duration=134,
result=Result.failed,
status=Status.finished,
date_created=datetime(2013, 9, 19, 22, 15, 22),
)
result = serialize(target)
assert result['id'] == str(target.id.hex)
assert result['job']['id'] == str(job.id.hex)
assert result['name'] == 'target_foo'
assert result['dateCreated'] == '2013-09-19T22:15:22'
assert result['result']['id'] == 'failed'
assert result['status']['id'] == 'finished'
assert result['duration'] == 134
102 changes: 102 additions & 0 deletions tests/changes/api/test_build_target_index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
from uuid import uuid4

from changes.constants import Result, Status
from changes.testutils import APITestCase


class BuildTargetIndexTest(APITestCase):
def test_simple(self):
fake_id = uuid4()

project = self.create_project()
build = self.create_build(project, result=Result.failed)
job = self.create_job(build, status=Status.finished)
phase = self.create_jobphase(job)
step = self.create_jobstep(phase)
target = self.create_target(
job=job,
jobstep=step,
name='bar',
result=Result.failed,
status=Status.finished,
duration=15,
)
target2 = self.create_target(
job=job,
jobstep=step,
name='foo',
result=Result.failed,
status=Status.finished,
duration=10,
)

path = '/api/0/builds/{0}/targets/'.format(fake_id.hex)

resp = self.client.get(path)
assert resp.status_code == 404

# test each sort option just to ensure it doesnt straight up fail
path = '/api/0/builds/{0}/targets/?sort=duration'.format(build.id.hex)

resp = self.client.get(path)
assert resp.status_code == 200
data = self.unserialize(resp)
assert len(data) == 2
assert data[0]['id'] == target.id.hex
assert data[1]['id'] == target2.id.hex

path = '/api/0/builds/{0}/targets/?sort=name'.format(build.id.hex)

resp = self.client.get(path)
assert resp.status_code == 200
data = self.unserialize(resp)
assert len(data) == 2
assert data[0]['id'] == target.id.hex
assert data[1]['id'] == target2.id.hex

path = '/api/0/builds/{0}/targets/?sort=name&reverse=true'.format(build.id.hex)

resp = self.client.get(path)
assert resp.status_code == 200
data = self.unserialize(resp)
assert len(data) == 2
assert data[0]['id'] == target2.id.hex
assert data[1]['id'] == target.id.hex

path = '/api/0/builds/{0}/targets/?per_page='.format(build.id.hex)

resp = self.client.get(path)
assert resp.status_code == 200
data = self.unserialize(resp)
assert len(data) == 2
assert data[0]['id'] == target.id.hex
assert data[1]['id'] == target2.id.hex

path = '/api/0/builds/{0}/targets/?query=foo'.format(build.id.hex)

resp = self.client.get(path)
assert resp.status_code == 200
data = self.unserialize(resp)
assert len(data) == 1
assert data[0]['id'] == target2.id.hex

path = '/api/0/builds/{0}/targets/?query=somethingelse'.format(build.id.hex)

resp = self.client.get(path)
assert resp.status_code == 200
data = self.unserialize(resp)
assert len(data) == 0

path = '/api/0/builds/{0}/targets/?result='.format(build.id.hex)

resp = self.client.get(path)
assert resp.status_code == 200
data = self.unserialize(resp)
assert len(data) == 2

path = '/api/0/builds/{0}/targets/?result=passed'.format(build.id.hex)

resp = self.client.get(path)
assert resp.status_code == 200
data = self.unserialize(resp)
assert len(data) == 0

0 comments on commit bc3eff8

Please sign in to comment.