From 4468f5ad860671fffb56355b1de0b22a4fd9577a Mon Sep 17 00:00:00 2001
From: jkuensem <jkuensem@physik.uni-bielefeld.de>
Date: Fri, 24 Jul 2020 19:15:48 +0200
Subject: [PATCH] TMSS-221: add start/stop/duration properties to drafts and
 blueprints of task/scheduling_unit.

---
 .../src/tmss/tmssapp/models/specification.py  | 133 ++++++++++++++++++
 .../tmss/tmssapp/serializers/specification.py |   8 +-
 2 files changed, 137 insertions(+), 4 deletions(-)

diff --git a/SAS/TMSS/src/tmss/tmssapp/models/specification.py b/SAS/TMSS/src/tmss/tmssapp/models/specification.py
index 4335bd06c16..56cc5c2d4a1 100644
--- a/SAS/TMSS/src/tmss/tmssapp/models/specification.py
+++ b/SAS/TMSS/src/tmss/tmssapp/models/specification.py
@@ -12,6 +12,7 @@ from django.db.models.deletion import ProtectedError
 from lofar.sas.tmss.tmss.tmssapp.validation import validate_json_against_schema
 from django.core.exceptions import ValidationError
 from rest_framework import status
+import datetime
 
 #
 # Common
@@ -303,6 +304,38 @@ class SchedulingUnitDraft(NamedCommon):
 
         super().save(force_insert, force_update, using, update_fields)
 
+    @property
+    def duration(self) -> float or None:
+        '''return the overall duration in seconds of all tasks of this scheduling unit
+        '''
+        if self.start_time is None or self.stop_time is None:
+            # todo: calculate?
+            return None
+        else:
+            return (self.stop_time - self.start_time).total_seconds()
+
+    @property
+    def start_time(self) -> datetime or None:
+        '''return the earliest start time of all tasks of this scheduling unit
+        '''
+        tasks_with_start_time = list(filter(lambda x: x.start_time is not None, self.task_drafts.all()))
+        if tasks_with_start_time:
+            return min(tasks_with_start_time, key=lambda x: x.start_time).start_time
+        else:
+            # todo: calculate?
+            return None
+
+    @property
+    def stop_time(self) -> datetime or None:
+        '''return the latest end time of all tasks of this scheduling unit
+        '''
+        tasks_with_stop_time = list(filter(lambda x: x.stop_time is not None, self.task_drafts.all()))
+        if tasks_with_stop_time:
+            return max(tasks_with_stop_time, key=lambda x: x.stop_time).stop_time
+        else:
+            # todo: calculate?
+            return None
+
 
 class SchedulingUnitBlueprint(NamedCommon):
     requirements_doc = JSONField(help_text='Scheduling and/or quality requirements for this scheduling unit (IMMUTABLE).')
@@ -316,6 +349,38 @@ class SchedulingUnitBlueprint(NamedCommon):
 
         super().save(force_insert, force_update, using, update_fields)
 
+    @property
+    def duration(self) -> float or None:
+        '''return the overall duration in seconds of all tasks of this scheduling unit
+        '''
+        if self.start_time is None or self.stop_time is None:
+            # todo: calculate?
+            return None
+        else:
+            return (self.stop_time - self.start_time).total_seconds()
+
+    @property
+    def start_time(self) -> datetime or None:
+        '''return the earliest start time of all tasks of this scheduling unit
+        '''
+        tasks_with_start_time = list(filter(lambda x: x.start_time is not None, self.task_blueprints.all()))
+        if tasks_with_start_time:
+            return min(tasks_with_start_time, key=lambda x: x.start_time).start_time
+        else:
+            # todo: calculate?
+            return None
+
+    @property
+    def stop_time(self) -> datetime or None:
+        '''return the latest end time of all tasks of this scheduling unit
+        '''
+        tasks_with_stop_time = list(filter(lambda x: x.stop_time is not None, self.task_blueprints.all()))
+        if tasks_with_stop_time:
+            return max(tasks_with_stop_time, key=lambda x: x.stop_time).stop_time
+        else:
+            # todo: calculate?
+            return None
+
 
 class TaskDraft(NamedCommon):
     specifications_doc = JSONField(help_text='Specifications for this task.')
@@ -350,6 +415,41 @@ class TaskDraft(NamedCommon):
                                                       "INNER JOIN tmssapp_taskrelationdraft as task_rel on task_rel.producer_id = successor_task.id\n"
                                                       "WHERE task_rel.consumer_id = %s", params=[self.id]))
 
+    @property
+    def duration(self) -> float or None:
+        '''returns the overall duration in seconds of all blueprints of this draft
+        # todo: is this the wanted behavior? Do you want to consider all the blueprints created from your draft or do you want to preview a new blueprint?
+        '''
+        if self.start_time is None or self.stop_time is None:
+            # todo: calculate?
+            return None
+        else:
+            return (self.stop_time - self.start_time).total_seconds()
+
+    @property
+    def start_time(self) -> datetime or None:
+        '''return the earliest start time of all blueprints of this draft
+        # todo: is this the wanted behavior? Do you want to consider all the blueprints created from your draft or do you want to preview a new blueprint?
+        '''
+        blueprints_with_start_time = list(filter(lambda x: x.start_time is not None, self.task_blueprints.all()))
+        if blueprints_with_start_time:
+            return min(blueprints_with_start_time, key=lambda x: x.start_time).start_time
+        else:
+            # todo: calculate?
+            return None
+
+    @property
+    def stop_time(self) -> datetime or None:
+        '''return the latest end time of all blueprints of this draft
+        # todo: is this the wanted behavior? Do you want to consider all the blueprints created from your draft or do you want to preview a new blueprint?
+        '''
+        blueprints_with_stop_time = list(filter(lambda x: x.stop_time is not None, self.task_blueprints.all()))
+        if blueprints_with_stop_time:
+            return max(blueprints_with_stop_time, key=lambda x: x.stop_time).stop_time
+        else:
+            # todo: calculate?
+            return None
+
 
 class TaskBlueprint(NamedCommon):
     specifications_doc = JSONField(help_text='Schedulings for this task (IMMUTABLE).')
@@ -384,6 +484,39 @@ class TaskBlueprint(NamedCommon):
                                                           "INNER JOIN tmssapp_taskrelationblueprint as task_rel on task_rel.producer_id = predecessor_task.id\n"
                                                           "WHERE task_rel.consumer_id = %s", params=[self.id]))
 
+    @property
+    def duration(self) -> float or None:
+        '''return the overall duration in seconds of all subtasks of this task
+        '''
+        if self.start_time is None or self.stop_time is None:
+            # todo: calculate?
+            return None
+        else:
+            return (self.stop_time - self.start_time).total_seconds()
+
+    @property
+    def start_time(self) -> datetime or None:
+        '''return the earliest start time of all subtasks of this draft
+        '''
+        subtasks_with_start_time = list(filter(lambda x: x.start_time is not None, self.subtasks.all()))
+        if subtasks_with_start_time:
+            return min(subtasks_with_start_time, key=lambda x: x.start_time).start_time
+        else:
+            # todo: calculate?
+            return None
+
+
+    @property
+    def stop_time(self) -> datetime or None:
+        '''return the latest end time of all subtasks of this draft
+        '''
+        subtasks_with_stop_time = list(filter(lambda x: x.stop_time is not None, self.subtasks.all()))
+        if subtasks_with_stop_time:
+            return max(subtasks_with_stop_time, key=lambda x: x.stop_time).stop_time
+        else:
+            # todo: calculate?
+            return None
+
 
 class TaskRelationDraft(BasicCommon):
     selection_doc = JSONField(help_text='Filter for selecting dataproducts from the output role.')
diff --git a/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py b/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py
index 90152f462b3..57279ca8946 100644
--- a/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py
+++ b/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py
@@ -243,7 +243,7 @@ class SchedulingUnitDraftSerializer(RelationalHyperlinkedModelSerializer):
     class Meta:
         model = models.SchedulingUnitDraft
         fields = '__all__'
-        extra_fields = ['scheduling_unit_blueprints', 'task_drafts']
+        extra_fields = ['scheduling_unit_blueprints', 'task_drafts', 'duration', 'start_time', 'stop_time']
 
 
 class SchedulingUnitBlueprintSerializer(RelationalHyperlinkedModelSerializer):
@@ -260,7 +260,7 @@ class SchedulingUnitBlueprintSerializer(RelationalHyperlinkedModelSerializer):
     class Meta:
         model = models.SchedulingUnitBlueprint
         fields = '__all__'
-        extra_fields = ['task_blueprints']
+        extra_fields = ['task_blueprints', 'duration', 'start_time', 'stop_time']
 
 
 class TaskDraftSerializer(RelationalHyperlinkedModelSerializer):
@@ -277,7 +277,7 @@ class TaskDraftSerializer(RelationalHyperlinkedModelSerializer):
     class Meta:
         model = models.TaskDraft
         fields = '__all__'
-        extra_fields = ['task_blueprints', 'produced_by', 'consumed_by', 'first_to_connect', 'second_to_connect']
+        extra_fields = ['task_blueprints', 'produced_by', 'consumed_by', 'first_to_connect', 'second_to_connect', 'duration', 'start_time', 'stop_time']
 
 
 class TaskBlueprintSerializer(RelationalHyperlinkedModelSerializer):
@@ -294,7 +294,7 @@ class TaskBlueprintSerializer(RelationalHyperlinkedModelSerializer):
     class Meta:
         model = models.TaskBlueprint
         fields = '__all__'
-        extra_fields = ['subtasks', 'produced_by', 'consumed_by', 'first_to_connect', 'second_to_connect']
+        extra_fields = ['subtasks', 'produced_by', 'consumed_by', 'first_to_connect', 'second_to_connect', 'duration', 'start_time', 'stop_time']
 
 
 class TaskRelationDraftSerializer(RelationalHyperlinkedModelSerializer):
-- 
GitLab