From 8dc98601496654567c04412e8d640021967c02af Mon Sep 17 00:00:00 2001
From: lfjnascimento <luiz.nascimento@profusion.mobi>
Date: Thu, 18 Jul 2024 13:49:38 -0300
Subject: [PATCH 1/3] chore: add django-query-builder dependencie

---
 backend/poetry.lock    | 69 +++++++++++++++++++++++++++++++++++++++++-
 backend/pyproject.toml |  1 +
 2 files changed, 69 insertions(+), 1 deletion(-)

diff --git a/backend/poetry.lock b/backend/poetry.lock
index d1d0a53..e2503f6 100644
--- a/backend/poetry.lock
+++ b/backend/poetry.lock
@@ -103,6 +103,22 @@ tzdata = {version = "*", markers = "sys_platform == \"win32\""}
 argon2 = ["argon2-cffi (>=19.1.0)"]
 bcrypt = ["bcrypt"]
 
+[[package]]
+name = "django-query-builder"
+version = "3.2.0"
+description = "Build complex nested queries"
+optional = false
+python-versions = "*"
+files = [
+    {file = "django-query-builder-3.2.0.tar.gz", hash = "sha256:e22228cb66305a08b2763a46c028b14c6627e7b31d536c7dd8a1c37c1a1c8c70"},
+    {file = "django_query_builder-3.2.0-py2.py3-none-any.whl", hash = "sha256:560ee939533b9e2cdd707a13a9cb79e34599d8455405934d27233532889c8abf"},
+]
+
+[package.dependencies]
+Django = ">=3.2"
+fleming = ">=0.6.0"
+pytz = ">=2015.6"
+
 [[package]]
 name = "djangorestframework"
 version = "3.15.2"
@@ -133,6 +149,21 @@ mccabe = ">=0.7.0,<0.8.0"
 pycodestyle = ">=2.12.0,<2.13.0"
 pyflakes = ">=3.2.0,<3.3.0"
 
+[[package]]
+name = "fleming"
+version = "0.7.0"
+description = "Python helpers for manipulating datetime objects relative to time zones"
+optional = false
+python-versions = "*"
+files = [
+    {file = "fleming-0.7.0-py2.py3-none-any.whl", hash = "sha256:9bdd83f8497c19be4e316726b8419be45ab5000c6398876e6aea456676345613"},
+    {file = "fleming-0.7.0.tar.gz", hash = "sha256:dd93f95f17f220d8ec3cedc2090b3703d7499e6c723bc9f45eb5f7bda8d75a63"},
+]
+
+[package.dependencies]
+python-dateutil = ">=2.2"
+pytz = ">=2013.9"
+
 [[package]]
 name = "gunicorn"
 version = "22.0.0"
@@ -305,6 +336,42 @@ files = [
     {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"},
 ]
 
+[[package]]
+name = "python-dateutil"
+version = "2.9.0.post0"
+description = "Extensions to the standard Python datetime module"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+files = [
+    {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
+    {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
+]
+
+[package.dependencies]
+six = ">=1.5"
+
+[[package]]
+name = "pytz"
+version = "2024.1"
+description = "World timezone definitions, modern and historical"
+optional = false
+python-versions = "*"
+files = [
+    {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"},
+    {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"},
+]
+
+[[package]]
+name = "six"
+version = "1.16.0"
+description = "Python 2 and 3 compatibility utilities"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+files = [
+    {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
+    {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
+]
+
 [[package]]
 name = "sqlparse"
 version = "0.5.0"
@@ -345,4 +412,4 @@ files = [
 [metadata]
 lock-version = "2.0"
 python-versions = "^3.12"
-content-hash = "6bccc453c42238e3cf955bbec77a7e023271de8f9eea93b8df215863476a7a82"
+content-hash = "fe5323a273b28a06ad10a51fbfc0a4b214e449b0f61d40e72cb4a9c7753008fa"
diff --git a/backend/pyproject.toml b/backend/pyproject.toml
index 5e4afd9..791f729 100644
--- a/backend/pyproject.toml
+++ b/backend/pyproject.toml
@@ -12,6 +12,7 @@ django = "^5.0.6"
 djangorestframework = "^3.15.2"
 gunicorn = "^22.0.0"
 psycopg = "^3.1.19"
+django-query-builder = "^3.2.0"
 
 
 [tool.poetry.group.dev.dependencies]

From 4df9528eaaded50d5e3addc5197d374628c4ff1a Mon Sep 17 00:00:00 2001
From: lfjnascimento <luiz.nascimento@profusion.mobi>
Date: Thu, 18 Jul 2024 13:50:31 -0300
Subject: [PATCH 2/3] fix: set start_time as DateTimeField

---
 backend/kernelCI_app/serializers.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/backend/kernelCI_app/serializers.py b/backend/kernelCI_app/serializers.py
index 334fa57..401c885 100644
--- a/backend/kernelCI_app/serializers.py
+++ b/backend/kernelCI_app/serializers.py
@@ -74,7 +74,7 @@ class TreeDetailsSerializer(serializers.Serializer):
     architecture = serializers.CharField()
     config_name = serializers.CharField()
     valid = serializers.BooleanField()
-    start_time = serializers.CharField()
+    start_time = serializers.DateTimeField()
     duration = serializers.CharField()
     compiler = serializers.CharField()
     config_url = serializers.CharField()

From 3835f48d4019066b7128b0cf6cdfc2d831107ec6 Mon Sep 17 00:00:00 2001
From: lfjnascimento <luiz.nascimento@profusion.mobi>
Date: Thu, 18 Jul 2024 13:58:57 -0300
Subject: [PATCH 3/3] feat: add filter capabilities to the treeDetails endpoint

- now we can use ?filter_<field_name>=<value> to filter the response
---
 backend/kernelCI_app/views.py | 53 +++++++++++++++++++----------------
 1 file changed, 29 insertions(+), 24 deletions(-)

diff --git a/backend/kernelCI_app/views.py b/backend/kernelCI_app/views.py
index 9816083..841b35f 100644
--- a/backend/kernelCI_app/views.py
+++ b/backend/kernelCI_app/views.py
@@ -1,8 +1,8 @@
 from django.http import JsonResponse
 from django.views import View
-
+from querybuilder.query import Query
 from kernelCI_app.models import Checkouts, Builds
-from kernelCI_app.serializers import TreeSerializer, TreeDetailsSerializer
+from kernelCI_app.serializers import TreeSerializer
 from kernelCI_app.utils import get_visible_record_identifiers
 
 
@@ -108,25 +108,30 @@ def get_test_staus(self, build_id):
         return {k: getattr(builds[0], k) for k in status_keys}
 
     def get(self, request, commit_hash):
-        builds = Builds.objects.raw(
-            """
-            SELECT
-                builds.id, builds.architecture, builds.config_name, builds.misc,
-                builds.config_url, builds.compiler, builds.valid,
-                builds.start_time, builds.duration, builds.log_url
-            FROM
-                builds
-            INNER JOIN
-                checkouts ON checkouts.id = builds.checkout_id
-            WHERE checkouts.git_commit_hash = %s;
-            """,
-            [commit_hash]
-        )
-
-        for build in builds:
-            build.test_status = self.get_test_staus(build.id)
-
-        data = TreeDetailsSerializer(builds, many=True).data
-        summary = self.create_summary(data)
-        resp = {"builds": data, "summary": summary}
-        return JsonResponse(resp, safe=False)
+        build_fields = [
+            'id', 'architecture', 'config_name', 'misc', 'config_url',
+            'compiler', 'valid', 'duration', 'log_url', 'start_time']
+        checkout_fields = [
+            'git_repository_branch', 'git_repository_url', 'git_repository_branch']
+
+        query = Query().from_table(Builds, build_fields).join(
+            'checkouts',
+            condition='checkouts.id = builds.checkout_id',
+            fields=checkout_fields
+        ).where(git_commit_hash__eq=commit_hash)
+
+        for k in request.GET.keys():
+            if k.startswith('filter_'):
+                field = k[7:]
+                if field in build_fields or field in checkout_fields:
+                    filter_list = request.GET.getlist(k)
+                    query.where({field: filter_list})
+
+        records = query.select()
+        for r in records:
+            status = self.get_test_staus(r.get('id'))
+            r['status'] = status
+
+        summary = self.create_summary(records)
+
+        return JsonResponse({"builds": records, "summary": summary}, safe=False)