This is an automated email from the ASF dual-hosted git repository.

jorisvandenbossche pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/master by this push:
     new c5a175dd61 ARROW-16358: [CI][Dev] Allow archery crossbow to generate a 
CSV report for nightly builds
c5a175dd61 is described below

commit c5a175dd61b3fa94a38b918c1cc3d7b4ffaefba7
Author: Raúl Cumplido <[email protected]>
AuthorDate: Thu May 19 18:24:11 2022 +0200

    ARROW-16358: [CI][Dev] Allow archery crossbow to generate a CSV report for 
nightly builds
    
    This PR will allow us to generate a CSV with the nightly builds status. 
This will be used in order to generate a static page with the different builds 
information and will allow us to analyze historical information from the 
different builds like since when they have been failing, last successful build 
commit, some tags, etcetera.
    
    An example of a CSV generated can be seen on the following gist:
    https://gist.github.com/raulcd/1799c8499ef228dfdf1ab0d986367d2a
    
    Closes #13165 from raulcd/ARROW-16358
    
    Authored-by: Raúl Cumplido <[email protected]>
    Signed-off-by: Joris Van den Bossche <[email protected]>
---
 dev/archery/archery/crossbow/cli.py                | 25 ++++++++++++
 dev/archery/archery/crossbow/core.py               |  2 -
 dev/archery/archery/crossbow/reports.py            | 46 +++++++++++++++++++++-
 dev/archery/archery/crossbow/tests/test_reports.py | 27 +++++++++++--
 4 files changed, 93 insertions(+), 7 deletions(-)

diff --git a/dev/archery/archery/crossbow/cli.py 
b/dev/archery/archery/crossbow/cli.py
index c8ae55903c..7507d4d39a 100644
--- a/dev/archery/archery/crossbow/cli.py
+++ b/dev/archery/archery/crossbow/cli.py
@@ -339,6 +339,31 @@ def report_chat(obj, job_name, send, webhook, 
extra_message_success,
         output.write(report_chat.render("text"))
 
 
[email protected]()
[email protected]('job-name', required=True)
[email protected]('--save/--dry-run', default=False,
+              help='Just display the report, don\'t save it')
[email protected]('--fetch/--no-fetch', default=True,
+              help='Fetch references (branches and tags) from the remote')
[email protected]_obj
+def report_csv(obj, job_name, save, fetch):
+    """
+    Generates a CSV report with the different tasks information
+    from a Crossbow run.
+    """
+    output = obj['output']
+    queue = obj['queue']
+    if fetch:
+        queue.fetch()
+
+    job = queue.get(job_name)
+    report = Report(job)
+    if save:
+        ReportUtils.write_csv(report)
+    else:
+        output.write("\n".join([str(row) for row in report.rows]))
+
+
 @crossbow.command()
 @click.argument('job-name', required=True)
 @click.option('-t', '--target-dir',
diff --git a/dev/archery/archery/crossbow/core.py 
b/dev/archery/archery/crossbow/core.py
index 40b8f5b68b..55cb87e77f 100644
--- a/dev/archery/archery/crossbow/core.py
+++ b/dev/archery/archery/crossbow/core.py
@@ -957,8 +957,6 @@ class Job(Serializable):
             raise ValueError('each `tasks` mus be an instance of Task')
         if not isinstance(target, Target):
             raise ValueError('`target` must be an instance of Target')
-        if not isinstance(target, Target):
-            raise ValueError('`target` must be an instance of Target')
         if not isinstance(params, dict):
             raise ValueError('`params` must be an instance of dict')
 
diff --git a/dev/archery/archery/crossbow/reports.py 
b/dev/archery/archery/crossbow/reports.py
index 111a11d043..88ecefda91 100644
--- a/dev/archery/archery/crossbow/reports.py
+++ b/dev/archery/archery/crossbow/reports.py
@@ -15,12 +15,13 @@
 # specific language governing permissions and limitations
 # under the License.
 
-import click
 import collections
+import csv
 import operator
 import fnmatch
 import functools
 
+import click
 import requests
 
 from archery.utils.report import JinjaReport
@@ -29,6 +30,17 @@ from archery.utils.report import JinjaReport
 # TODO(kszucs): use archery.report.JinjaReport instead
 class Report:
 
+    ROW_HEADERS = [
+        "task_name",
+        "task_status",
+        "build_links",
+        "crossbow_branch_url",
+        "ci_system",
+        "extra_params",
+        "template",
+        "arrow_commit",
+    ]
+
     def __init__(self, job, task_filters=None):
         self.job = job
 
@@ -83,6 +95,30 @@ class Report:
     def show(self):
         raise NotImplementedError()
 
+    @property
+    def rows(self):
+        """
+        Produces a generator that allow us to iterate over
+        the job tasks as a list of rows.
+        Row headers are defined at Report.ROW_HEADERS.
+        """
+        for task_name, task in sorted(self.job.tasks.items()):
+            task_status = task.status()
+            row = [
+                task_name,
+                task_status.combined_state,
+                task_status.build_links,
+                self.branch_url(task.branch),
+                task.ci,
+                # We want this to be serialized as a dict instead
+                # of an orderedict.
+                {k: v for k, v in task.params.items()},
+                task.template,
+                # Arrow repository commit
+                self.job.target.head
+            ]
+            yield row
+
 
 class ConsoleReport(Report):
     """Report the status of a Job to the console using click"""
@@ -210,6 +246,14 @@ class ReportUtils:
         server.sendmail(smtp_user, recipient_email, message)
         server.close()
 
+    @classmethod
+    def write_csv(cls, report, add_headers=True):
+        with open(f'{report.job.branch}.csv', 'w') as csvfile:
+            task_writer = csv.writer(csvfile)
+            if add_headers:
+                task_writer.writerow(report.ROW_HEADERS)
+            task_writer.writerows(report.rows)
+
 
 class EmailReport(JinjaReport):
     templates = {
diff --git a/dev/archery/archery/crossbow/tests/test_reports.py 
b/dev/archery/archery/crossbow/tests/test_reports.py
index 19e52b8b46..6f9eeec5c9 100644
--- a/dev/archery/archery/crossbow/tests/test_reports.py
+++ b/dev/archery/archery/crossbow/tests/test_reports.py
@@ -76,8 +76,27 @@ def test_crossbow_email_report(load_fixture):
     job = load_fixture('crossbow-job.yaml', decoder=yaml.load)
     report = Report(job)
     assert report.tasks_by_state is not None
-    empail_report = EmailReport(report=report, sender_name="Sender Reporter",
-                                sender_email="[email protected]",
-                                recipient_email="[email protected]")
+    email_report = EmailReport(report=report, sender_name="Sender Reporter",
+                               sender_email="[email protected]",
+                               recipient_email="[email protected]")
 
-    assert empail_report.render("text") == textwrap.dedent(expected_msg)
+    assert email_report.render("text") == textwrap.dedent(expected_msg)
+
+
+def test_crossbow_export_report(load_fixture):
+    job = load_fixture('crossbow-job.yaml', decoder=yaml.load)
+    report = Report(job)
+    assert len(list(report.rows)) == 4
+    expected_first_row = [
+        'docker-cpp-cmake32',
+        'success',
+        ['https://github.com/apache/crossbow/runs/1'],
+        'https://github.com/apache/crossbow/tree/'
+        'ursabot-1-circle-docker-cpp-cmake32',
+        'circle',
+        {'commands': ['docker-compose build cpp-cmake32',
+                      'docker-compose run cpp-cmake32']},
+        'docker-tests/circle.linux.yml',
+        'f766a1d615dd1b7ee706d05102e579195951a61c'
+    ]
+    assert next(report.rows) == expected_first_row

Reply via email to