in treeherder/webapp/api/push.py [0:0]
def list(self, request, project):
"""
GET method for list of ``push`` records with revisions
"""
# What is the upper limit on the number of pushes returned by the api
max_push_count = 1000
# make a mutable copy of these params
filter_params = request.query_params.copy()
# This will contain some meta data about the request and results
meta = {}
# support ranges for date as well as revisions(changes) like old tbpl
for param in [
"fromchange",
"tochange",
"startdate",
"enddate",
"revision",
"commit_revision",
]:
v = filter_params.get(param, None)
if v:
del filter_params[param]
meta[param] = v
all_repos = request.query_params.get("all_repos")
pushes = Push.objects.order_by("-time")
if not all_repos:
try:
repository = Repository.objects.get(name=project)
except Repository.DoesNotExist:
return Response(
{"detail": f"No project with name {project}"}, status=HTTP_404_NOT_FOUND
)
pushes = pushes.filter(repository=repository)
search_param = filter_params.get("search")
if search_param:
repository = Repository.objects.get(name=project)
# Subquery to check if a commit exists with the search term
commit_exists_subquery = Commit.objects.filter(
push_id=OuterRef("id"), search_vector=SearchQuery(search_param)
).values("id")
pushes = (
Push.objects.annotate(has_matching_commit=Exists(commit_exists_subquery))
.filter(
Q(repository=repository)
& (
Q(has_matching_commit=True)
| Q(author__icontains=search_param)
| Q(revision__icontains=search_param)
)
)
.distinct()
.order_by("-time")[:200]
) # Get most recent results and limit result to 200
for param, value in meta.items():
if param == "fromchange":
revision_field = "revision__startswith" if len(value) < 40 else "revision"
filter_kwargs = {revision_field: value, "repository": repository}
frompush_time = Push.objects.values_list("time", flat=True).get(**filter_kwargs)
pushes = pushes.filter(time__gte=frompush_time)
filter_params.update({"push_timestamp__gte": to_timestamp(frompush_time)})
self.report_if_short_revision(param, value)
elif param == "tochange":
revision_field = "revision__startswith" if len(value) < 40 else "revision"
filter_kwargs = {revision_field: value, "repository": repository}
topush_time = Push.objects.values_list("time", flat=True).get(**filter_kwargs)
pushes = pushes.filter(time__lte=topush_time)
filter_params.update({"push_timestamp__lte": to_timestamp(topush_time)})
self.report_if_short_revision(param, value)
elif param == "startdate":
pushes = pushes.filter(time__gte=to_datetime(value))
filter_params.update({"push_timestamp__gte": to_timestamp(to_datetime(value))})
elif param == "enddate":
real_end_date = to_datetime(value) + datetime.timedelta(days=1)
pushes = pushes.filter(time__lte=real_end_date)
filter_params.update({"push_timestamp__lt": to_timestamp(real_end_date)})
elif param == "revision":
# revision must be the tip revision of the push itself
revision_field = "revision__startswith" if len(value) < 40 else "revision"
filter_kwargs = {revision_field: value}
pushes = pushes.filter(**filter_kwargs)
rev_key = (
"revisions_long_revision"
if len(meta["revision"]) == 40
else "revisions_short_revision"
)
filter_params.update({rev_key: meta["revision"]})
self.report_if_short_revision(param, value)
elif param == "commit_revision":
# revision can be either the revision of the push itself, or
# any of the commits it refers to
pushes = pushes.filter(commits__revision=value)
self.report_if_short_revision(param, value)
for param in [
"push_timestamp__lt",
"push_timestamp__lte",
"push_timestamp__gt",
"push_timestamp__gte",
]:
if filter_params.get(param):
# translate push timestamp directly into a filter
try:
value = datetime.datetime.fromtimestamp(float(filter_params.get(param)))
except ValueError:
return Response(
{"detail": f"Invalid timestamp specified for {param}"},
status=HTTP_400_BAD_REQUEST,
)
pushes = pushes.filter(**{param.replace("push_timestamp", "time"): value})
for param in ["id__lt", "id__lte", "id__gt", "id__gte", "id"]:
try:
value = int(filter_params.get(param, 0))
except ValueError:
return Response(
{"detail": f"Invalid timestamp specified for {param}"},
status=HTTP_400_BAD_REQUEST,
)
if value:
pushes = pushes.filter(**{param: value})
id_in = filter_params.get("id__in")
if id_in:
try:
id_in_list = [int(id) for id in id_in.split(",")]
except ValueError:
return Response(
{"detail": "Invalid id__in specification"}, status=HTTP_400_BAD_REQUEST
)
pushes = pushes.filter(id__in=id_in_list)
author = filter_params.get("author")
if author:
if author.startswith("-"):
author = author[1::]
pushes = pushes.exclude(author__iexact=author)
else:
pushes = pushes.filter(author__iexact=author)
author_contains = filter_params.get("author_contains")
if author_contains:
pushes = pushes.filter(author__icontains=author_contains)
if filter_params.get("hide_reviewbot_pushes") == "true":
pushes = pushes.exclude(author="reviewbot")
try:
count = int(filter_params.get("count", 10))
except ValueError:
return Response({"detail": "Valid count value required"}, status=HTTP_400_BAD_REQUEST)
if count > max_push_count:
msg = f"Specified count exceeds api limit: {max_push_count}"
return Response({"detail": msg}, status=HTTP_400_BAD_REQUEST)
if count < 1:
msg = f"count requires a positive integer, not: {count}"
return Response({"detail": msg}, status=HTTP_400_BAD_REQUEST)
# we used to have a "full" parameter for this endpoint so you could
# specify to not fetch the revision information if it was set to
# false. however AFAIK no one ever used it (default was to fetch
# everything), so let's just leave it out. it doesn't break
# anything to send extra data when not required.
pushes = pushes.select_related("repository").prefetch_related("commits")[:count]
serializer = PushSerializer(pushes, many=True)
meta["count"] = len(pushes)
meta["repository"] = "all" if all_repos else project
meta["filter_params"] = filter_params
resp = {"meta": meta, "results": serializer.data}
return Response(resp)