diff --git a/cibyl/models/ci/zuul/build.py b/cibyl/models/ci/zuul/build.py index 40091f48..bc8f26ae 100644 --- a/cibyl/models/ci/zuul/build.py +++ b/cibyl/models/ci/zuul/build.py @@ -44,6 +44,10 @@ class Data: """Result of the build.""" duration: float """Time, in seconds, the build took to complete.""" + start_time: str + """Date at which the build begun, in 'YYYY-MM-DD HH:MM:SS' format.""" + end_time: str + """Date at which the build ended, in 'YYYY-MM-DD HH:MM:SS' format.""" API = { 'build_id': { @@ -72,6 +76,14 @@ class Data: 'attr_type': float, 'arguments': [], }, + 'start_time': { + 'attr_type': str, + 'arguments': [] + }, + 'end_time': { + 'attr_type': str, + 'arguments': [] + }, 'suites': { 'attr_type': TestSuite, 'attribute_value_class': AttributeListValue, @@ -101,6 +113,8 @@ def __init__(self, data, suites=None): 'pipeline': data.pipeline, 'status': data.result, 'duration': data.duration, + 'start_time': data.start_time, + 'end_time': data.end_time, 'suites': suites } ) @@ -119,6 +133,8 @@ def __eq__(self, other): self.pipeline == other.pipeline and \ self.status == other.status and \ self.duration == other.duration and \ + self.start_time == other.start_time and \ + self.end_time == other.end_time and \ self.suites == other.suites def add_suite(self, suite: TestSuite) -> None: diff --git a/cibyl/outputs/cli/ci/system/impls/zuul/colored/cascades/job.py b/cibyl/outputs/cli/ci/system/impls/zuul/colored/cascades/job.py index 7a0cb462..79b78b01 100644 --- a/cibyl/outputs/cli/ci/system/impls/zuul/colored/cascades/job.py +++ b/cibyl/outputs/cli/ci/system/impls/zuul/colored/cascades/job.py @@ -119,6 +119,14 @@ def print_build(self, build: Build) -> str: unit="s") result.add(build_duration, 1) + if build.start_time.value: + result.add(self.palette.blue('Start Time: '), 1) + result[-1].append(build.start_time.value) + + if build.end_time.value: + result.add(self.palette.blue('End Time: '), 1) + result[-1].append(build.end_time.value) + if self.query >= QueryType.TESTS: result.add(self.palette.blue('Test Suites: '), 1) diff --git a/cibyl/outputs/cli/ci/system/impls/zuul/serialized.py b/cibyl/outputs/cli/ci/system/impls/zuul/serialized.py index 6b7a8ab3..124a3ed4 100644 --- a/cibyl/outputs/cli/ci/system/impls/zuul/serialized.py +++ b/cibyl/outputs/cli/ci/system/impls/zuul/serialized.py @@ -175,6 +175,8 @@ def print_build(self, build: Build) -> str: 'pipeline': build.pipeline.value, 'status': build.status.value, 'duration': build.duration.value, + 'start_time': build.start_time.value, + 'end_time': build.end_time.value, 'test_suites': [] } diff --git a/cibyl/sources/zuul/apis/__init__.py b/cibyl/sources/zuul/apis/__init__.py index 5b3508ae..d6e8abc8 100644 --- a/cibyl/sources/zuul/apis/__init__.py +++ b/cibyl/sources/zuul/apis/__init__.py @@ -97,6 +97,23 @@ def duration(self): """ return self._build['duration'] + @property + def start_time(self): + """ + :return: Date, following ISO 8601, at which the build started running. + :rtype: str + """ + return self._build['start_time'] + + @property + def end_time(self): + """ + :return: Date, following ISO 8601, at which the build finished + running. 'None' if it still is. + :rtype: str or None + """ + return self._build['end_time'] + @property def artifacts(self): """ diff --git a/cibyl/sources/zuul/output.py b/cibyl/sources/zuul/output.py index 10f2bcf0..fe323dd5 100644 --- a/cibyl/sources/zuul/output.py +++ b/cibyl/sources/zuul/output.py @@ -16,6 +16,8 @@ from dataclasses import dataclass, field from typing import Dict, Optional +import dateparser + from cibyl.models.ci.zuul.build import Build from cibyl.models.ci.zuul.job import Job from cibyl.models.ci.zuul.pipeline import Pipeline @@ -196,6 +198,10 @@ def with_build(self, build): :return: Model for this build. :rtype: :class:`Build` """ + + def format_iso(date): + return str(dateparser.parse(date)) + # Register this build's job job = self.with_job(build.job) @@ -208,7 +214,9 @@ def with_build(self, build): pipeline=build.data['pipeline'], uuid=build.data['uuid'], result=build.data['result'], - duration=build.data['duration'] + duration=build.data['duration'], + start_time=format_iso(build.data['start_time']), + end_time=format_iso(build.data['end_time']) ) ) ) diff --git a/requirements.txt b/requirements.txt index 58629ecf..bfb1a6ec 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,3 +20,4 @@ xsdata~=22.7 StrEnum~=0.4.8 pbr>=2.0.0 anytree~=2.8.0 +dateparser~=1.1.2 diff --git a/tests/cibyl/intr/sources/zuul/queries/composition/test_quick.py b/tests/cibyl/intr/sources/zuul/queries/composition/test_quick.py index b759a045..9592a92d 100644 --- a/tests/cibyl/intr/sources/zuul/queries/composition/test_quick.py +++ b/tests/cibyl/intr/sources/zuul/queries/composition/test_quick.py @@ -370,7 +370,9 @@ def test_builds_query(self): 'pipeline': 'pipeline', 'uuid': '1234', 'result': 'success', - 'duration': 10 + 'duration': 10, + 'start_time': '1970-01-01T00:00:00', + 'end_time': '1970-01-01T00:00:00' } api = Mock() @@ -401,6 +403,10 @@ def test_builds_query(self): result = source.get_builds(**kwargs) models = result.value + + expected_start = build.raw['start_time'].replace('T', ' ') + expected_end = build.raw['end_time'].replace('T', ' ') + expected = { tenant.name: Tenant( name=tenant.name, @@ -415,7 +421,9 @@ def test_builds_query(self): pipeline=build.raw['pipeline'], uuid=build.raw['uuid'], result=build.raw['result'], - duration=build.raw['duration'] + duration=build.raw['duration'], + start_time=expected_start, + end_time=expected_end ) ) } @@ -454,7 +462,9 @@ def test_tests_query(self): 'pipeline': 'pipeline', 'uuid': '1234', 'result': 'success', - 'duration': 10 + 'duration': 10, + 'start_time': '1970-01-01T00:00:00', + 'end_time': '1970-01-01T00:00:00' } build.tests = Mock() build.tests.return_value = [suite] @@ -502,6 +512,10 @@ def test_tests_query(self): result = source.get_tests(**kwargs) models = result.value + + expected_start = build.raw['start_time'].replace('T', ' ') + expected_end = build.raw['end_time'].replace('T', ' ') + expected = { tenant.name: Tenant( name=tenant.name, @@ -516,7 +530,9 @@ def test_tests_query(self): pipeline=build.raw['pipeline'], uuid=build.raw['uuid'], result=build.raw['result'], - duration=build.raw['duration'] + duration=build.raw['duration'], + start_time=expected_start, + end_time=expected_end ), suites=[ TestSuite( diff --git a/tests/cibyl/unit/models/ci/zuul/test_build.py b/tests/cibyl/unit/models/ci/zuul/test_build.py index 59fd8b36..9aabf8e4 100644 --- a/tests/cibyl/unit/models/ci/zuul/test_build.py +++ b/tests/cibyl/unit/models/ci/zuul/test_build.py @@ -32,8 +32,19 @@ def test_attributes(self): uuid = 'uuid' status = 'STATUS' duration = 1 + start_time = '1970-01-01T00:00:00' + end_time = '1970-01-01T00:00:00' + + data = Build.Data( + uuid, + project, + pipeline, + status, + duration, + start_time, + end_time + ) - data = Build.Data(uuid, project, pipeline, status, duration) build = Build(data, suites) self.assertEqual(uuid, build.build_id.value) @@ -46,7 +57,16 @@ def test_attributes(self): def test_equality_by_type(self): """Checks that a build is not equal to something not of its type. """ - data = Build.Data('uuid', 'project', 'pipeline', 'status', 0) + data = Build.Data( + 'uuid', + 'project', + 'pipeline', + 'status', + 0, + '1970-01-01T00:00:00', + '1970-01-01T00:00:00' + ) + build = Build(data) other = Mock() @@ -55,7 +75,16 @@ def test_equality_by_type(self): def test_equality_by_reference(self): """Checks that a build is equal to itself. """ - data = Build.Data('uuid', 'project', 'pipeline', 'status', 0) + data = Build.Data( + 'uuid', + 'project', + 'pipeline', + 'status', + 0, + '1970-01-01T00:00:00', + '1970-01-01T00:00:00' + ) + build = Build(data) self.assertEqual(build, build) @@ -63,7 +92,16 @@ def test_equality_by_reference(self): def test_equality_by_contents(self): """Checks that a build equals another whose contents are the same. """ - data = Build.Data('uuid', 'project', 'pipeline', 'status', 0) + data = Build.Data( + 'uuid', + 'project', + 'pipeline', + 'status', + 0, + '1970-01-01T00:00:00', + '1970-01-01T00:00:00' + ) + build1 = Build(data) build2 = Build(data) diff --git a/tests/cibyl/unit/sources/zuul/test_output.py b/tests/cibyl/unit/sources/zuul/test_output.py index 4b9a47c0..3d516d23 100644 --- a/tests/cibyl/unit/sources/zuul/test_output.py +++ b/tests/cibyl/unit/sources/zuul/test_output.py @@ -149,7 +149,9 @@ def test_with_build_of_unknown_job(self): 'result': 'SUCCESS', 'project': 'project', 'pipeline': 'pipeline', - 'duration': 0 + 'duration': 0, + 'start_time': '1970-01-01T00:00:00', + 'end_time': '1970-01-01T00:00:00' } builder = QueryOutputBuilder() @@ -169,3 +171,13 @@ def test_with_build_of_unknown_job(self): self.assertEqual(build.data['uuid'], result_build.build_id.value) self.assertEqual(build.data['result'], result_build.status.value) self.assertEqual(build.data['duration'], result_build.duration.value) + + self.assertEqual( + build.data['start_time'].replace('T', ' '), + result_build.start_time.value + ) + + self.assertEqual( + build.data['end_time'].replace('T', ' '), + result_build.end_time.value + )