Imlement result searching based on pattern

Related-prod: PRODX-30060
Change-Id: I4495db03a23c4edadfcf938a598ec84376c73ff9
diff --git a/testrail_bot/control/celery_tasks/test_rail_api.py b/testrail_bot/control/celery_tasks/test_rail_api.py
index 5bb0647..7a27ac9 100644
--- a/testrail_bot/control/celery_tasks/test_rail_api.py
+++ b/testrail_bot/control/celery_tasks/test_rail_api.py
@@ -19,6 +19,17 @@
     else:
         return None
 
+def get_suite_by_id(suite_id):
+    return api.suites.get_suite(suite_id)
+
+
+def get_suite_name_by_id(suite_id):
+    return api.suites.get_suite(suite_id)['name']
+
+def get_suite_test_type(suite_id):
+    suite_name = get_suite_name_by_id(suite_id)
+    return suite_name.split(']')[1]
+
 
 def get_plans(project_id, plan_name, **kwargs):
     plans = api.plans.get_plans(project_id, **kwargs)['plans']
@@ -29,9 +40,11 @@
 def get_entries(plan_id):
     return api.plans.get_plan(plan_id)["entries"]
 
+
 def get_run_by_id(run_id):
     return api.runs.get_run(run_id)
 
+
 def get_run_id(entries, run_name):
     entries = list(filter(
         lambda x: x["name"] == run_name,
diff --git a/testrail_bot/control/celery_tasks/testrail_pipeline.py b/testrail_bot/control/celery_tasks/testrail_pipeline.py
index f102bac..829e054 100644
--- a/testrail_bot/control/celery_tasks/testrail_pipeline.py
+++ b/testrail_bot/control/celery_tasks/testrail_pipeline.py
@@ -29,29 +29,52 @@
         data = locals()["custom_filter"](data)
     return data
 
-def process_plan(plan_id, case_id, last_comment, bot_run):
+def get_runs_by_pattern(runs_in_plan, test_pattern, suite_id):
+    run = []
+    for t_run in runs_in_plan:
+        if test_pattern in t_run['name'] and t_run['suite_id'] == suite_id:
+            run.append( t_run['runs'][0]['id'])
+    return run
 
+def process_plan(plan_id, case_id, last_comment, bot_run):
+    """
+    This function performs a search for a similar failure within a test plan.
+
+    :param plan_id: id of test plan
+    :param case_id: id of test case for failed test
+    :param last_comment: last trace for failed test
+    :param bot_run: number of result reports from tab 'Reports'
+    """
+    runs = []
     testrail_run =  test_rail_api.get_run_by_id(bot_run.run_id)
     suite_id = testrail_run['suite_id']
     runs_in_plan = test_rail_api.get_entries(plan_id)
+    test_pattern = bot_run.test_pattern
+    if test_pattern:
+        runs = get_runs_by_pattern(runs_in_plan, test_pattern, suite_id)
+    else:
+        runs = [t_run['runs'][0]['id'] for t_run in runs_in_plan if suite_id == t_run['suite_id']]
+
+
     results = []
     ratio = -1.0
-    for t_run in runs_in_plan:
-        if suite_id == t_run['suite_id']:
-            results = test_rail_api.get_result_for_case(t_run['runs'][0]['id'], case_id)
-            if not results:
-                return None, -2.0
+    run_id = 0
 
-            status_code = str(results[0]["status_id"])
-            if status_code not in [StatusEnum.test_failed, StatusEnum.product_failed]:
-                return None, -3.0
-            comment = apply_filters(results[-1]["comment"], bot_run)
-            ratio = difflib.SequenceMatcher(
-                lambda symbol: symbol in [" ", ",", "\n"],
-                last_comment, comment, autojunk=False).ratio()
-            if ratio > 0.7:
-                return results[0], ratio
-    return None, ratio
+    for run_id in runs:
+        results = test_rail_api.get_result_for_case(run_id, case_id)
+        if not results:
+            return None, -2.0, run_id
+
+        status_code = str(results[0]["status_id"])
+        if status_code not in [StatusEnum.test_failed, StatusEnum.product_failed]:
+            return None, -3.0, run_id
+        comment = apply_filters(results[-1]["comment"], bot_run)
+        ratio = difflib.SequenceMatcher(
+            lambda symbol: symbol in [" ", ",", "\n"],
+            last_comment, comment, autojunk=False).ratio()
+        if ratio > 0.7:
+            return results[0], ratio, run_id
+    return None, ratio, run_id
 
 
 def get_project_id(f, test_run, report):
@@ -76,18 +99,6 @@
     return test_rail_api.get_plans(project_id, test_run.plan_name, **kw)
 
 
-def get_last_run_id(f, last_plan, test_run, report):
-    last_run_id = test_rail_api.get_run_id(
-        test_rail_api.get_entries(last_plan), test_run.run_name)
-    if not last_run_id:
-        f.write("No {} in {} plan\n".format(
-            test_run.run_name, last_plan))
-        f.flush()
-        finish_report(report)
-        return None
-    return last_run_id
-
-
 def get_last_comment(case_id, test_run):
     last_result = test_rail_api.get_result_for_case(
         test_run.run_id, case_id)
@@ -96,7 +107,7 @@
         last_result[0]["comment"], test_run)
 
 def process_old_test(f, plan_id, case_id, last_comment, test_run, test):
-    sim_result, ratio = process_plan(
+    sim_result, ratio, run_id = process_plan(
         plan_id, case_id, last_comment, test_run)
     if sim_result:
         if str(sim_result["status_id"]) == StatusEnum.retest:
@@ -136,8 +147,9 @@
             f.flush()
             return True
     f.write(
-        "Similarity not found due to similarity:{}%\n".format(
-            round(100.0 * ratio, 2)))
+        "Similarity not found due to similarity:{per}, in run <a href=https://mirantis.testrail.com/"
+        "index.php?/runs/view/{run_id}>{run_id} </a>\n".format(
+            per=round(100.0 * ratio, 2), run_id=run_id))
     f.flush()
     return False
 
@@ -167,6 +179,17 @@
 
 
 def process_test_run(bot_run_id, report_id, path, run_date):
+    """
+    This function processes a created bot test run. It retrieves a list
+    of test plans to process, gathers the failed tests from the test run,
+    and passes them for processing using the 'process_test' function.
+    All the failed tests are processed
+
+    :param bot_run_id: number of result reports from tab 'Reports'
+    :param report_id: number of run from tab 'Test Run'
+    :param path: path to report results
+    :param run_date: date until which to retrieve test plans
+    """
     report = models.TestRailReport.objects.get(pk=report_id)
     with open(path, "w") as f:
         bot_test_run = models.TestRailTestRun.objects.get(pk=bot_run_id)
@@ -181,6 +204,8 @@
             return
 
         plans = get_plans(bot_test_run, run_date, project_id)
+
+        # failed_tests: all failed tests in test run
         failed_tests = test_rail_api.get_failed_tests(bot_test_run.run_id)
         for test in failed_tests:
             process_test(f, test, bot_test_run, plans)
diff --git a/testrail_bot/control/forms.py b/testrail_bot/control/forms.py
index cc516ac..51d4fb2 100644
--- a/testrail_bot/control/forms.py
+++ b/testrail_bot/control/forms.py
@@ -15,6 +15,7 @@
             "project_name": "Name of the project",
             "plan_name": "Name of the Test Plan without date",
             "run_name": "Name of the run",
+            "test_pattern": "Test run pattern",
             "run_id": "ID of the run",
             "created_by_id": "ID of the user that created Test Run",
             "filter_func": "Custom filter function",
diff --git a/testrail_bot/control/migrations/0020_auto_20231009_1423.py b/testrail_bot/control/migrations/0020_auto_20231009_1423.py
new file mode 100644
index 0000000..e2dc295
--- /dev/null
+++ b/testrail_bot/control/migrations/0020_auto_20231009_1423.py
@@ -0,0 +1,53 @@
+# Generated by Django 3.0.7 on 2023-10-09 14:23
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('control', '0019_testrailtestrun_timestamp_and_more'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='testrailtestrun',
+            name='test_pattern',
+            field=models.CharField(blank=True, max_length=300),
+        ),
+        migrations.AlterField(
+            model_name='testrailtestrun',
+            name='created_by_id',
+            field=models.IntegerField(default='109'),
+        ),
+        migrations.AlterField(
+            model_name='testrailtestrun',
+            name='ip_filter',
+            field=models.BooleanField(default=True),
+        ),
+        migrations.AlterField(
+            model_name='testrailtestrun',
+            name='plan_name',
+            field=models.CharField(default='[MCP2.0]OSCORE', max_length=300),
+        ),
+        migrations.AlterField(
+            model_name='testrailtestrun',
+            name='project_name',
+            field=models.CharField(default='Mirantis Cloud Platform', max_length=300),
+        ),
+        migrations.AlterField(
+            model_name='testrailtestrun',
+            name='run_id',
+            field=models.CharField(max_length=300),
+        ),
+        migrations.AlterField(
+            model_name='testrailtestrun',
+            name='run_name',
+            field=models.CharField(blank=True, max_length=300),
+        ),
+        migrations.AlterField(
+            model_name='testrailtestrun',
+            name='uuid_filter',
+            field=models.BooleanField(default=True),
+        ),
+    ]
diff --git a/testrail_bot/control/models.py b/testrail_bot/control/models.py
index 915fc0b..f4c846f 100644
--- a/testrail_bot/control/models.py
+++ b/testrail_bot/control/models.py
@@ -9,6 +9,7 @@
     project_name = models.CharField(max_length=300, default="Mirantis Cloud Platform")
     plan_name = models.CharField(max_length=300, default="[MCP2.0]OSCORE")
     run_name = models.CharField(max_length=300, blank=True)
+    test_pattern = models.CharField(max_length=300, blank=True)
     run_id = models.CharField(max_length=300)
     created_by_id = models.IntegerField(default='109')
     filter_func = models.TextField(null=True, blank=True)
diff --git a/testrail_bot/control/templates/control/create_run.html b/testrail_bot/control/templates/control/create_run.html
index 07c7b0a..136b7c1 100644
--- a/testrail_bot/control/templates/control/create_run.html
+++ b/testrail_bot/control/templates/control/create_run.html
@@ -11,6 +11,7 @@
       <div class="col-xs-8">{% bootstrap_field form.project_name  size='sm' %}</div>
       <div class="col-xs-8">{% bootstrap_field form.plan_name size='sm' %}</div>
       <div class="col-xs-8">{% bootstrap_field form.run_name  size='sm' %}</div>
+      <div class="col-xs-8">{% bootstrap_field form.test_pattern  size='sm' %}</div>
       <div class="col-xs-8">{% bootstrap_field form.run_id  size='sm' %}</div>
       <div class="col-xs-8">{% bootstrap_field form.created_by_id  size='sm' %}</div>
       <div class="col-xs-8">
diff --git a/testrail_bot/control/templates/control/update_run.html b/testrail_bot/control/templates/control/update_run.html
index c70ab0c..8ccd94e 100644
--- a/testrail_bot/control/templates/control/update_run.html
+++ b/testrail_bot/control/templates/control/update_run.html
@@ -11,6 +11,7 @@
       <div class="col-xs-8">{% bootstrap_field form.project_name  size='sm' %}</div>
       <div class="col-xs-8">{% bootstrap_field form.plan_name size='sm' %}</div>
       <div class="col-xs-8">{% bootstrap_field form.run_name  size='sm' %}</div>
+      <div class="col-xs-8">{% bootstrap_field form.test_pattern  size='sm' %}</div>
       <div class="col-xs-8">{% bootstrap_field form.run_id  size='sm' %}</div>
       <div class="col-xs-8">{% bootstrap_field form.created_by_id  size='sm' %}</div>
       <div class="col-xs-8">
diff --git a/testrail_bot/control/views.py b/testrail_bot/control/views.py
index bc052a1..db624a6 100644
--- a/testrail_bot/control/views.py
+++ b/testrail_bot/control/views.py
@@ -65,10 +65,13 @@
 def submit_run(request, run_id):
     run = models.TestRailTestRun.objects.get(pk=run_id)
     testrail_run = test_rail_api.get_run_by_id(run.run_id)
+    run_name = ''
     if not run.run_name:
-        run.run_name = testrail_run['name']
+        run_name += testrail_run['name']
+    if run.test_pattern:
+        run_name +=  "-" + run.test_pattern
     report_name = "{}-run_id-{}-date-{}".format(
-        run.run_name, run.run_id, datetime.datetime.isoformat(datetime.datetime.now()))
+        run_name, run.run_id, datetime.datetime.isoformat(datetime.datetime.now()))
     path = os.path.join(models.fs.location, report_name)
     with open(path, "w"):
         pass