Display test result separately
Add option to mark them as reviewed to sort them by priority
PRODX-35953
Change-Id: If45d137b234db0fa42d5fb4316497989c5cd12bf
diff --git a/testrail_bot/control/celery_tasks/schedules_pipeline.py b/testrail_bot/control/celery_tasks/schedules_pipeline.py
index 080519a..8539357 100644
--- a/testrail_bot/control/celery_tasks/schedules_pipeline.py
+++ b/testrail_bot/control/celery_tasks/schedules_pipeline.py
@@ -1,10 +1,13 @@
import os
+import logging
from datetime import datetime, timedelta
from .. import models
from . import tasks
from .test_rail_api import get_planid_by_name
+LOG = logging.getLogger(__name__)
+
def task_to_check_today_testplan():
"""
@@ -106,8 +109,15 @@
"""
today = datetime.today()
border_date = (today - timedelta(days=30 * 2)).strftime("%Y-%m-%d")
+
reports = models.TestRailReport.objects.order_by("-created_at").all()
for report in reports:
if report.created_at.strftime("%Y-%m-%d") < border_date:
+ LOG.info(f"Deleting {report=}")
report.delete()
- print(report.created_at.strftime("%Y-%m-%d"))
+
+ test_results = models.TestResult.objects.order_by("-updated_at").all()
+ for result in test_results:
+ if result.updated_at.strftime("%Y-%m-%d") < border_date:
+ LOG.info(f"Deleting {result=}")
+ result.delete()
diff --git a/testrail_bot/control/celery_tasks/testrail_pipeline.py b/testrail_bot/control/celery_tasks/testrail_pipeline.py
index 472046d..a4f99f3 100644
--- a/testrail_bot/control/celery_tasks/testrail_pipeline.py
+++ b/testrail_bot/control/celery_tasks/testrail_pipeline.py
@@ -1,6 +1,7 @@
import datetime
import difflib
import json
+import logging
from datetime import datetime as dt
from datetime import timedelta
from itertools import islice
@@ -10,6 +11,7 @@
from ..jira_manager import JiraIssue
from . import filters, test_rail_api
from .enums import StatusEnum
+from ..utils import DBlogger
__all__ = ("process_test_run",)
@@ -226,7 +228,7 @@
def process_old_test(
- f: TextIO,
+ logger: logging.Logger,
case_id: int,
last_comment: str,
run_id: int,
@@ -248,11 +250,10 @@
per = round(100.0 * ratio, 2)
run_link = test_rail_api.html_link("run", old_run_id, old_run_id)
if type(sim_result) is not dict:
- f.write(
+ logger.error(
f"Similarity not found due to similarity: {per}, "
f"in run {run_link}\n"
)
- f.flush()
return False
prod_link = (
@@ -269,20 +270,15 @@
StatusEnum.failed,
StatusEnum.blocked,
]:
- f.write(
+ logger.info(
f"Found a similar result on the test "
f"{test_link} with similarity {per}% and "
f"{StatusEnum(status_id).name} status and {prod_link} "
- f"defect. <i>Continuing...</i>\n"
+ f"defect. <i>Continuing...</i>"
)
- f.flush()
found_unknown_fail += 1
if found_unknown_fail >= 10:
- f.write(
- "<b style='color:red;'>"
- "Detected 10+ consecutive unknown failures\n </b>"
- )
- f.flush()
+ logger.error("Detected 10+ consecutive unknown failures")
return False
continue
elif ratio > 0.9:
@@ -303,63 +299,70 @@
"comment": comment,
"defects": sim_result["defects"],
}
- f.write(
+ logger.info(
f"Found a similar result on the test "
f"{test_link} with similarity {per}% and "
f"{StatusEnum(status_id).name} status and {prod_link} "
f"defect\n"
f"<i style='color:ForestGreen;'>Pushing to TestRail "
f"{update_dict}"
- f"</i>\n\n"
+ f"</i>"
)
- f.flush()
test_rail_api.add_result(test["id"], update_dict)
+ if "Completed" in prod_link:
+ return False
return True
elif ratio > 0.7:
- f.write(
- f"<b style='color:red;'> "
+ logger.error(
f"Found a similar result on the test "
f"{test_link} with similarity {per}% and "
f"{StatusEnum(status_id).name} status and {prod_link} "
- f"defect,\n but NOT marked by "
+ f"defect, but NOT marked by "
f"TestRailBot because of similarity only, "
- f"you can update manually \n </b>"
+ f"you can update manually"
)
- f.flush()
- return True
+ return False
def process_test(
- f: TextIO, test: dict, testrail_filters: dict, text_filters: dict
+ test: dict, testrail_filters: dict, text_filters: dict
) -> None:
"""
- Starts processing for the TestCase for each TestPlan
+ Starts processing for the TestResult
"""
+ test_result_report, _ = models.TestResult.objects.get_or_create(
+ result_id=test["id"]
+ )
+ LOG = DBlogger(name=str(test["id"]), storage=test_result_report)
+
case_id = test["case_id"]
run_id = test["run_id"]
run_name = test_rail_api.get_run_name(run_id)
test_link = test_rail_api.html_link("test", test["id"], test["title"])
run_link = test_rail_api.html_link("run", run_id, run_name)
- f.write(
- f"<br><b>Proceeding test {test_link} <br>" f"in {run_link} run</b>\n"
- )
- f.flush()
+ LOG.info(f"<b>Proceeding test {test_link} <br> in {run_link} run</b>")
last_comment = get_last_comment(case_id, run_id, text_filters)
found = process_old_test(
- f, case_id, last_comment, run_id, test, testrail_filters, text_filters
+ logger=LOG,
+ case_id=case_id,
+ last_comment=last_comment,
+ run_id=run_id,
+ test=test,
+ testrail_filters=testrail_filters,
+ text_filters=text_filters,
)
if found:
- return
+ test_result_report.action_needed = not found
else:
- f.write(
- f"<b style='color:red;'>Automatic test processing failed. "
- "Please process test manually "
- f"{test_link}</b>\n\n"
+ LOG.error(
+ f"<b>Automatic test processing failed. "
+ f"Please process test manually {test_link}</b>"
)
- f.flush()
+ test_result_report.action_needed = True
+ test_result_report.save()
def process_test_run(
@@ -377,8 +380,12 @@
:param is_testplan: flag to show that TestPlan will be proceeded instead
of TestRun
"""
- report = models.TestRailReport.objects.get(pk=report_id)
- bot_test_run = models.TestRailTestRun.objects.get(pk=bot_run_id)
+ report: models.TestRailReport = models.TestRailReport.objects.get(
+ pk=report_id
+ )
+ bot_test_run: models.TestRailTestRun = models.TestRailTestRun.objects.get(
+ pk=bot_run_id
+ )
with open(path, "a") as f:
if is_testplan:
test_run = test_rail_api.get_plan_by_id(bot_test_run.run_id)
@@ -406,8 +413,9 @@
and test["id"] in bot_test_run.checked_tests
):
continue
+ report.test_results.append(test["id"])
+ report.save()
process_test(
- f,
test,
bot_test_run.testrail_filters,
bot_test_run.text_filters,
diff --git a/testrail_bot/control/migrations/0014_testresult_testrailreport_test_results.py b/testrail_bot/control/migrations/0014_testresult_testrailreport_test_results.py
new file mode 100644
index 0000000..5555151
--- /dev/null
+++ b/testrail_bot/control/migrations/0014_testresult_testrailreport_test_results.py
@@ -0,0 +1,37 @@
+# Generated by Django 4.2.13 on 2024-07-06 15:35
+
+import control.models
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("control", "0013_testrailtestrun_created_at_and_more"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="TestResult",
+ fields=[
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("result_id", models.CharField(max_length=50)),
+ ("text", models.CharField(default="", max_length=10000)),
+ ("action_needed", models.BooleanField(default=True)),
+ ("updated_at", models.DateTimeField(auto_now=True)),
+ ],
+ ),
+ migrations.AddField(
+ model_name="testrailreport",
+ name="test_results",
+ field=control.models.IntegerListField(default=[], editable=False),
+ ),
+ ]
diff --git a/testrail_bot/control/models.py b/testrail_bot/control/models.py
index f613292..b3e84fb 100644
--- a/testrail_bot/control/models.py
+++ b/testrail_bot/control/models.py
@@ -82,10 +82,18 @@
class TestRailReport(models.Model):
path = models.FileField(storage=fs, null=True, blank=True, max_length=500)
report_name = models.CharField(max_length=300)
+ test_results = IntegerListField(default=list())
finished = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
+class TestResult(models.Model):
+ result_id = models.CharField(max_length=50)
+ text = models.CharField(max_length=10000, default="")
+ action_needed = models.BooleanField(default=True)
+ updated_at = models.DateTimeField(auto_now=True)
+
+
class ActionLog(models.Model):
name = models.CharField(max_length=500)
date = models.DateTimeField(null=True)
diff --git a/testrail_bot/control/templates/control/report.html b/testrail_bot/control/templates/control/report.html
index bbe8b01..f82247d 100644
--- a/testrail_bot/control/templates/control/report.html
+++ b/testrail_bot/control/templates/control/report.html
@@ -23,7 +23,7 @@
function send(){
$.ajax({
type: "post",
- url: "{% url 'single_report' report_id %}",
+ url: "{% url 'single_report' report_obj.id %}",
dataType: "json",
headers: {'X-CSRFToken': csrftoken},
success: function(data) {
@@ -40,7 +40,9 @@
};
window.onload = function() {send()};</script>
-<a href="{% url 'delete_report' report_id %}">
+
+
+<a href="{% url 'delete_report' report_obj.id %}">
<button class="btn btn-danger m-1">
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="1em"
height="1em" viewBox="0,0,256,256">
@@ -52,4 +54,31 @@
</a>
<pre id="data" class="p-4 border">{{ report | safe }}</pre>
+
+
+{% for test_result in test_results|dictsortreversed:"action_needed" %}
+
+<div class="card m-3
+{% if not test_result.action_needed %}
+border-success
+{% endif %}
+" data-width="100%">
+ <div class="card-header
+{% if not test_result.action_needed %} bg-success
+{% endif %}
+" >
+ <a style="text-decoration: none; color:inherit" href="#{{ test_result.result_id }}" id="{{ test_result.result_id }}">{{ test_result.result_id }}</a>
+ </div>
+ <div class="card-body">
+ <pre style="overflow: auto;white-space: pre-wrap;">{{ test_result.text | safe }}</pre>
+
+ <a href="{% url 'update_test_result' report_obj.id test_result.result_id test_result.action_needed|yesno:'0,1' %}"
+ {% if test_result.action_needed %}
+ class="btn btn-primary">Mark as reviewed</a>
+ {% else %}
+ class="btn btn-primary warning">Move to work</a>
+ {% endif %}
+ </div>
+</div>
+{% endfor %}
{% endblock %}
\ No newline at end of file
diff --git a/testrail_bot/control/urls.py b/testrail_bot/control/urls.py
index d0249f7..90f7375 100644
--- a/testrail_bot/control/urls.py
+++ b/testrail_bot/control/urls.py
@@ -17,6 +17,11 @@
views.delete_report,
name="delete_report",
),
+ path(
+ "reports/<int:report_id>/update/<int:result_id>/<int:action_needed>",
+ views.update_test_result,
+ name="update_test_result",
+ ),
path("index/", views.index, name="index"),
path("help/", views.show_help, name="help"),
path(
diff --git a/testrail_bot/control/utils.py b/testrail_bot/control/utils.py
index 6c41f58..73ea55f 100644
--- a/testrail_bot/control/utils.py
+++ b/testrail_bot/control/utils.py
@@ -1,6 +1,8 @@
+import logging
from typing import Any, Callable, Dict, List
from django.core.cache import cache
+from django.db.models import Model
from parse import parse
@@ -109,5 +111,35 @@
return decorator
+class DBHandler(logging.Handler):
+
+ def __init__(self, storage: Model) -> None:
+ logging.Handler.__init__(self)
+ self.storage = storage
+
+ def emit(self, record: logging.LogRecord) -> None:
+ msg = self.format(record)
+ color = "black"
+ if record.levelno == logging.ERROR:
+ color = "red"
+ if record.levelno == logging.DEBUG:
+ color = "grey"
+ self.storage.text += f"<a style='color:{color}'>{msg} </a>\n"
+ self.storage.save()
+ print(self.storage.text)
+
+
+def DBlogger(name: str, storage: Model) -> logging.Logger:
+ _log = logging.getLogger(name)
+ _log.setLevel(logging.DEBUG)
+ formatter = logging.Formatter(
+ "[%(asctime)s] %(message)s", "%d %b %H:%M:%S"
+ )
+ db_h = DBHandler(storage)
+ db_h.setFormatter(formatter)
+ _log.addHandler(db_h)
+ return _log
+
+
if __name__ == "__main__":
pass
diff --git a/testrail_bot/control/views.py b/testrail_bot/control/views.py
index 8a362ef..350f9da 100644
--- a/testrail_bot/control/views.py
+++ b/testrail_bot/control/views.py
@@ -63,7 +63,9 @@
def single_report(request, report_id):
- report = models.TestRailReport.objects.get(pk=report_id)
+ report: models.TestRailReport = models.TestRailReport.objects.get(
+ pk=report_id
+ )
data = report.path.read().decode("utf-8")
if (
request.method == "POST"
@@ -74,13 +76,25 @@
content_type="application/json",
)
+ test_results = [
+ models.TestResult.objects.filter(result_id=test_id)[0]
+ for test_id in report.test_results
+ ]
return render(
request,
"control/report.html",
- {"report_id": report.id, "report": data, "finished": report.finished},
+ {"report_obj": report, "report": data, "test_results": test_results},
)
+def update_test_result(request, report_id, result_id, action_needed):
+ result = models.TestResult.objects.get(result_id=result_id)
+ result.action_needed = bool(action_needed)
+ result.save()
+ print(result.__dict__)
+ return redirect("single_report", report_id)
+
+
def delete_report(request, report_id):
report: models.TestRailReport = models.TestRailReport.objects.get(
pk=report_id
@@ -298,7 +312,6 @@
def save_scheduler(request, pk=None):
- print(f"{request.POST=}")
minute, hour, day_of_month, month_of_year, day_of_week = request.POST.get(
"cron", "* * * * *"
).split(" ")