diff --git a/SAS/TMSS/backend/services/scheduling/lib/constraints/__init__.py b/SAS/TMSS/backend/services/scheduling/lib/constraints/__init__.py index 3c1277412b49bbeafaa15f05f58014bbb88c3dc7..4b46cc74036b44625b0df5d88c1d03d07c6c8449 100644 --- a/SAS/TMSS/backend/services/scheduling/lib/constraints/__init__.py +++ b/SAS/TMSS/backend/services/scheduling/lib/constraints/__init__.py @@ -41,6 +41,7 @@ from typing import NamedTuple from lofar.sas.tmss.tmss.tmssapp import models from lofar.sas.tmss.tmss.exceptions import * +from lofar.sas.tmss.tmss.tmssapp.reservations import get_active_station_reservations_in_timewindow ################## main data struct and methods ################## @@ -238,23 +239,7 @@ def get_min_earliest_possible_start_time(scheduling_units: [models.SchedulingUni return lower_bound -def get_active_station_reservations_in_timewindow(lower_bound, upper_bound): - """ - Retrieve a list of all active stations reservations, which are reserved between a timewindow - TODO: use filter like filter(start_time__lte=upper) filter(stop_time__gte=lower) - BUT can not use filter of property, so find another 'fast' solution (no loop), therefore stop_time has to move to - to the model. See TMSS-668 - Also move this part to other module - """ - lst_active_station_reservations = [] - reservations = models.Reservation.objects.all() - for station_reservation in reservations: - if (station_reservation.duration is not None and \ - station_reservation.start_time < upper_bound and station_reservation.stop_time > lower_bound) \ - or (station_reservation.duration is None and station_reservation.start_time < upper_bound): - lst_active_station_reservations += station_reservation.specifications_doc["resources"]["stations"] - - return lst_active_station_reservations + def can_run_within_station_reservations(scheduling_unit: models.SchedulingUnitBlueprint) -> bool: diff --git a/SAS/TMSS/backend/services/scheduling/lib/constraints/template_constraints_v1.py b/SAS/TMSS/backend/services/scheduling/lib/constraints/template_constraints_v1.py index 2b68de96458c95d1cd2e068f7916dc9851ff3a45..550efab2b7be2627304ce24c24f4cf95cd5cb9c0 100644 --- a/SAS/TMSS/backend/services/scheduling/lib/constraints/template_constraints_v1.py +++ b/SAS/TMSS/backend/services/scheduling/lib/constraints/template_constraints_v1.py @@ -283,7 +283,9 @@ def can_run_anywhere_within_timewindow_with_sky_constraints(scheduling_unit: mod target_rise_and_set_times = coordinates_timestamps_and_stations_to_target_rise_and_set(angle1=angle1, angle2=angle2, direction_type=direction_type, timestamps=timestamps, stations=tuple(stations), angle_to_horizon=min_elevation) for station, times in target_rise_and_set_times.items(): for i in range(len(timestamps)): - if not (timestamps[i] > times[0]['rise'] and timestamps[i] < times[0]['set']): + if times[0]['always_above_horizon']: + continue + if times[0]['always_below_horizon'] or not (timestamps[i] > times[0]['rise'] and timestamps[i] < times[0]['set']): if task['specifications_template'] == 'calibrator observation': logger.info('min_calibrator_elevation=%s constraint is not met at timestamp=%s' % (min_elevation.rad, timestamps[i])) else: diff --git a/SAS/TMSS/backend/services/scheduling/test/t_dynamic_scheduling.py b/SAS/TMSS/backend/services/scheduling/test/t_dynamic_scheduling.py index 45efbf8ae32a51b02d26139010d6f5c125fc2334..e8fadb2c6085117007f7913c8ecee0fa3808b434 100755 --- a/SAS/TMSS/backend/services/scheduling/test/t_dynamic_scheduling.py +++ b/SAS/TMSS/backend/services/scheduling/test/t_dynamic_scheduling.py @@ -735,8 +735,11 @@ class TestSkyConstraints(unittest.TestCase): self.distance_mock.return_value = self.distance_data self.addCleanup(self.distance_patcher.stop) - self.target_rise_and_set_data = {"CS002": [{"rise": datetime(2020, 1, 1, 8, 0, 0), "set": datetime(2020, 1, 1, 12, 30, 0)}, - {"rise": datetime(2020, 1, 1, 8, 0, 0), "set": datetime(2020, 1, 1, 12, 30, 0)}]} + self.target_rise_and_set_data = {"CS002": [{"rise": datetime(2020, 1, 1, 8, 0, 0), "set": datetime(2020, 1, 1, 12, 30, 0), "always_above_horizon": False, "always_below_horizon": False}, + {"rise": datetime(2020, 1, 1, 8, 0, 0), "set": datetime(2020, 1, 1, 12, 30, 0), "always_above_horizon": False, "always_below_horizon": False}]} + self.target_rise_and_set_data_always_above = {"CS002": [{"rise": None, "set": None, "always_above_horizon": True, "always_below_horizon": False}]} + self.target_rise_and_set_data_always_below = {"CS002": [{"rise": None, "set": None, "always_above_horizon": False, "always_below_horizon": True}]} + self.target_rise_and_set_patcher = mock.patch('lofar.sas.tmss.services.scheduling.constraints.template_constraints_v1.coordinates_timestamps_and_stations_to_target_rise_and_set') self.target_rise_and_set_mock = self.target_rise_and_set_patcher.start() self.target_rise_and_set_mock.return_value = self.target_rise_and_set_data @@ -760,21 +763,45 @@ class TestSkyConstraints(unittest.TestCase): # min_target_elevation - def test_can_run_anywhere_within_timewindow_with_sky_constraints_with_min_target_elevation_constraint_returns_true_when_met(self): + def test_can_run_anywhere_within_timewindow_with_sky_constraints_with_min_target_elevation_constraint_returns_true(self): + self.target_rise_and_set_mock.return_value = self.target_rise_and_set_data + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky'] = {'min_target_elevation': 0.1} self.scheduling_unit_blueprint.save() - timestamp = datetime(2020, 1, 1, 10, 0, 0) + timestamp = datetime(2020, 1, 1, 10, 0, 0) # target sets after obs ends (mocked response) returned_value = tc1.can_run_anywhere_within_timewindow_with_sky_constraints(self.scheduling_unit_blueprint, timestamp, timestamp + timedelta(seconds=self.obs_duration)) self.assertTrue(returned_value) - def test_can_run_anywhere_within_timewindow_with_sky_constraints_with_min_target_elevation_constraint_returns_false_when_not_met(self): - self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky'] = {'min_target_elevation': 0.2} + def test_can_run_anywhere_within_timewindow_with_sky_constraints_with_min_target_elevation_when_target_always_above_returns_true(self): + self.target_rise_and_set_mock.return_value = self.target_rise_and_set_data_always_above + + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky'] = {'min_target_elevation': 0.1} self.scheduling_unit_blueprint.save() - timestamp = datetime(2020, 1, 1, 11, 0, 0) + timestamp = datetime(2020, 1, 1, 10, 0, 0) # target is always up (mocked response) + returned_value = tc1.can_run_anywhere_within_timewindow_with_sky_constraints(self.scheduling_unit_blueprint, timestamp, timestamp + timedelta(seconds=self.obs_duration)) + self.assertTrue(returned_value) + + def test_can_run_anywhere_within_timewindow_with_sky_constraints_with_min_target_elevation_constraint_returns_false(self): + self.target_rise_and_set_mock.return_value = self.target_rise_and_set_data + + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky'] = {'min_target_elevation': 0.1} + self.scheduling_unit_blueprint.save() + timestamp = datetime(2020, 1, 1, 11, 0, 0) # target sets before obs ends (mocked response) + returned_value = tc1.can_run_anywhere_within_timewindow_with_sky_constraints(self.scheduling_unit_blueprint, timestamp, timestamp + timedelta(seconds=self.obs_duration)) + self.assertFalse(returned_value) + + def test_can_run_anywhere_within_timewindow_with_sky_constraints_with_min_target_elevation_when_target_is_always_below_returns_false(self): + self.target_rise_and_set_mock.return_value = self.target_rise_and_set_data_always_below + + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky'] = {'min_target_elevation': 0.1} + self.scheduling_unit_blueprint.save() + timestamp = datetime(2020, 1, 1, 10, 0, 0) # target is never up (mocked response) returned_value = tc1.can_run_anywhere_within_timewindow_with_sky_constraints(self.scheduling_unit_blueprint, timestamp, timestamp + timedelta(seconds=self.obs_duration)) self.assertFalse(returned_value) + + class TestTimeConstraints(TestCase): """ Tests for the time constraint checkers used in dynamic scheduling with different boundaries @@ -1035,9 +1062,10 @@ class TestReservedStations(unittest.TestCase): """ @staticmethod - def create_station_reservation(additional_name, lst_stations, start_time=datetime(2100, 1, 1, 0, 0, 0), duration=86400): + def create_station_reservation(additional_name, lst_stations, start_time=datetime(2100, 1, 1, 0, 0, 0), + stop_time=datetime(2100, 1, 2, 0, 0, 0)): """ - Create a station reservation with given list of stations, start_time and duration (optional) + Create a station reservation with given list of stations, start_time and stop_time (optional) Default duration is 24 hours (defined in seconds) """ reservation_template = models.ReservationTemplate.objects.get(name="resource reservation") @@ -1048,7 +1076,7 @@ class TestReservedStations(unittest.TestCase): specifications_template=reservation_template, specifications_doc=reservation_template_spec, start_time=start_time, - duration=duration) + stop_time=stop_time) return res def setUp(self) -> None: @@ -1070,8 +1098,7 @@ class TestReservedStations(unittest.TestCase): Set (1) reservation start_time > SUB start_time and reservation stop_time > SUB stop_time """ station_reservation.start_time = self.scheduling_unit_blueprint.start_time + timedelta(minutes=5) - reservation_stop_time = self.scheduling_unit_blueprint.stop_time + timedelta(minutes=5) - station_reservation.duration = (reservation_stop_time - station_reservation.start_time).total_seconds() + station_reservation.stop_time = self.scheduling_unit_blueprint.stop_time + timedelta(minutes=5) station_reservation.save() def set_2_reservation_start_time_lt_sub_start_time_and_stop_time_lt_sub_stop_time(self, station_reservation): @@ -1079,8 +1106,7 @@ class TestReservedStations(unittest.TestCase): Set (2) reservation start_time < SUB start_time and reservation stop_time < SUB stop_time """ station_reservation.start_time = self.scheduling_unit_blueprint.start_time - timedelta(minutes=5) - reservation_stop_time = self.scheduling_unit_blueprint.stop_time - timedelta(minutes=5) - station_reservation.duration = (reservation_stop_time - station_reservation.start_time).total_seconds() + station_reservation.stop_time = self.scheduling_unit_blueprint.stop_time - timedelta(minutes=5) station_reservation.save() def set_3_reservation_start_time_gt_sub_start_time_and_stop_time_lt_sub_stop_time(self, station_reservation): @@ -1088,8 +1114,7 @@ class TestReservedStations(unittest.TestCase): Set (3) reservation start_time > SUB start_time and reservation stop_time < SUB stop_time """ station_reservation.start_time = self.scheduling_unit_blueprint.start_time + timedelta(minutes=5) - reservation_stop_time = self.scheduling_unit_blueprint.stop_time - timedelta(minutes=5) - station_reservation.duration = (reservation_stop_time - station_reservation.start_time).total_seconds() + station_reservation.stop_time = self.scheduling_unit_blueprint.stop_time - timedelta(minutes=5) station_reservation.save() def set_4_reservation_start_time_lt_sub_start_time_and_stop_time_gt_sub_stop_time(self, station_reservation): @@ -1097,8 +1122,7 @@ class TestReservedStations(unittest.TestCase): Set (4) reservation start_time < SUB start_time and reservation stop_time > SUB stop_time """ station_reservation.start_time = self.scheduling_unit_blueprint.start_time - timedelta(minutes=5) - reservation_stop_time = self.scheduling_unit_blueprint.stop_time + timedelta(minutes=5) - station_reservation.duration = (reservation_stop_time - station_reservation.start_time).total_seconds() + station_reservation.stop_time = self.scheduling_unit_blueprint.stop_time + timedelta(minutes=5) station_reservation.save() def set_5_reservation_start_time_and_stop_time_lt_sub_start_time(self, station_reservation): @@ -1106,8 +1130,7 @@ class TestReservedStations(unittest.TestCase): Set (5) reservation start_time and reservation stop_time < SUB start_time """ station_reservation.start_time = self.scheduling_unit_blueprint.start_time - timedelta(minutes=60) - reservation_stop_time = self.scheduling_unit_blueprint.start_time - timedelta(minutes=5) - station_reservation.duration = (reservation_stop_time - station_reservation.start_time).total_seconds() + station_reservation.stop_time = self.scheduling_unit_blueprint.start_time - timedelta(minutes=5) station_reservation.save() def set_6_reservation_start_time_and_stop_time_gt_sub_stop_time(self, station_reservation): @@ -1115,8 +1138,7 @@ class TestReservedStations(unittest.TestCase): Set (6) reservation start_time and reservation stop_time > SUB stop_time """ station_reservation.start_time = self.scheduling_unit_blueprint.stop_time + timedelta(minutes=5) - reservation_stop_time = self.scheduling_unit_blueprint.stop_time + timedelta(minutes=65) - station_reservation.duration = (reservation_stop_time - station_reservation.start_time).total_seconds() + station_reservation.stop_time = self.scheduling_unit_blueprint.stop_time + timedelta(minutes=65) station_reservation.save() def update_station_groups_of_scheduling_unit_blueprint(self): @@ -1234,7 +1256,7 @@ class TestReservedStations(unittest.TestCase): Test with different reservation start time and NO stop_time start_time after SUB stop_time 'can run' all others 'can NOT run' """ - reservation_two_no_duration = self.create_station_reservation("Two-NoDuration", ["CS001", "CS002"], duration=None) + reservation_two_no_duration = self.create_station_reservation("Two-NoDuration", ["CS001", "CS002"], stop_time=None) # reservation start_time > SUB start_time and < SUB stop_time reservation_two_no_duration.start_time = self.scheduling_unit_blueprint.start_time + timedelta(minutes=5) reservation_two_no_duration.save() diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/CMakeLists.txt b/SAS/TMSS/backend/src/tmss/tmssapp/CMakeLists.txt index 58c545f7ed434d8c05064e1fad48ebf0c93d821a..456c9935792dbfd31873e09098211a46c046828d 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/CMakeLists.txt +++ b/SAS/TMSS/backend/src/tmss/tmssapp/CMakeLists.txt @@ -10,6 +10,7 @@ set(_py_files subtasks.py tasks.py conversions.py + reservations.py ) python_install(${_py_files} diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/conversions.py b/SAS/TMSS/backend/src/tmss/tmssapp/conversions.py index ae926e172f4a39a4ff77a442346fbf25d4505e35..3c0e184ce79ac8e697043dcf8ced5dceba3bf1eb 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/conversions.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/conversions.py @@ -126,9 +126,10 @@ def coordinates_timestamps_and_stations_to_target_rise_and_set(angle1: float, an :param stations: tuple of station names, e.g. ("CS002",) :param angle_to_horizon: the angle between horizon and given coordinates for which rise and set times are returned :return A dict that maps station names to a list of dicts with rise and set times for each requested date. + If rise and set are None, the target is always above or below horizon, and the respective boolean is True. E.g. - {"CS002": [{"rise": datetime(2020, 1, 1, 4, 0, 0), "set": datetime(2020, 1, 1, 11, 0, 0)}, - {"rise": datetime(2020, 1, 2, 4, 0, 0), "set": datetime(2020, 1, 2, 11, 0, 0)}] + {"CS002": [{"rise": datetime(2020, 1, 1, 4, 0, 0), "set": datetime(2020, 1, 1, 11, 0, 0), "always_above_horizon": False, "always_below_horizon": False}, + {"rise": datetime(2020, 1, 2, 4, 0, 0), "set": datetime(2020, 1, 2, 11, 0, 0), "always_above_horizon": False, "always_below_horizon": False}] } """ if direction_type == "J2000": @@ -140,10 +141,29 @@ def coordinates_timestamps_and_stations_to_target_rise_and_set(angle1: float, an for timestamp in timestamps: # todo: this can probably be made faster by moving the following logic to an own function with single station/timestamp as input and putting the lru_cache on there. observer = create_astroplan_observer_for_station(station) - target_set = observer.target_set_time(target=coord, time=Time(timestamp), horizon=angle_to_horizon, which='next', n_grid_points=TARGET_SET_RISE_PRECISION) - target_rise = observer.target_rise_time(target=coord, time=Time(target_set), horizon=angle_to_horizon, which='previous', n_grid_points=TARGET_SET_RISE_PRECISION) + try: + target_set = observer.target_set_time(target=coord, time=Time(timestamp), horizon=angle_to_horizon, which='next', n_grid_points=TARGET_SET_RISE_PRECISION) + target_rise = observer.target_rise_time(target=coord, time=Time(target_set), horizon=angle_to_horizon, which='previous', n_grid_points=TARGET_SET_RISE_PRECISION) + return_dict.setdefault(station, []).append( + {"rise": target_rise.to_datetime(), + "set": target_set.to_datetime(), + "always_above_horizon": False, + "always_below_horizon": False}) + except TypeError as e: + if "numpy.float64" in str(e): + # Note: when the target is always above or below horizon, astroplan excepts with the not very + # meaningful error: 'numpy.float64' object does not support item assignment + # Determine whether the target is always above or below horizon so that we can return some useful + # additional info, e.g. for scheduling purposes. + is_up = observer.target_is_up(target=coord, time=Time(timestamp), horizon=angle_to_horizon) + return_dict.setdefault(station, []).append( + {"rise": None, + "set": None, + "always_above_horizon": is_up, + "always_below_horizon": not is_up}) + else: + raise - return_dict.setdefault(station, []).append({"rise": target_rise.to_datetime(), "set": target_set.to_datetime()}) return return_dict diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/migrations/0001_initial.py b/SAS/TMSS/backend/src/tmss/tmssapp/migrations/0001_initial.py index bc33c5696ec718a0c662eb600e2e28a9091913d0..d4095c88c950c3c628f52c4a477d6389e9dd2699 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/migrations/0001_initial.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/migrations/0001_initial.py @@ -453,7 +453,7 @@ class Migration(migrations.Migration): ('name', models.CharField(help_text='Human-readable name of this object.', max_length=128)), ('description', models.CharField(help_text='Short description for this reservation, used in overviews', max_length=255)), ('start_time', models.DateTimeField(help_text='Start of this reservation.')), - ('duration', models.IntegerField(help_text='Duration of this reservation (in seconds). If null, then this reservation is indefinitely.', null=True)), + ('stop_time', models.DateTimeField(help_text='Stop time of this reservation. If null, then this reservation is indefinitely.', null=True)), ('specifications_doc', django.contrib.postgres.fields.jsonb.JSONField(help_text='Properties of this reservation')), ], options={ diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py b/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py index b85c8621f279d382f317ad59870267b703ffefe6..9645087251cecf7f2b6c5eddd11779ee123f5f37 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py @@ -328,7 +328,7 @@ class ProjectQuota(Model): class ProjectQuotaArchiveLocation(Model): - project_quota = ForeignKey('ProjectQuota', null=False, related_name="project_quota", on_delete=PROTECT, help_text='Project to wich this quota belongs.') + project_quota = ForeignKey('ProjectQuota', null=False, related_name="project_quota_archive_location", on_delete=PROTECT, help_text='The ProjectQuota for this archive location') archive_location = ForeignKey('Filesystem', null=False, on_delete=PROTECT, help_text='Location of an archive LTA cluster.') def save(self, force_insert=False, force_update=False, using=None, update_fields=None): @@ -1060,18 +1060,18 @@ class Reservation(NamedCommon): project = ForeignKey('Project', null=True, related_name='reservations', on_delete=CASCADE, help_text='Reservation will be accounted for this project.') description = CharField(max_length=255, help_text='Short description for this reservation, used in overviews') start_time = DateTimeField(help_text='Start of this reservation.') - duration = IntegerField(null=True, help_text='Duration of this reservation (in seconds). If null, then this reservation is indefinitely.') + stop_time = DateTimeField(null=True, help_text='Stop of this reservation. If null, then this reservation is indefinitely.') specifications_doc = JSONField(help_text='Properties of this reservation') specifications_template = ForeignKey('ReservationTemplate', on_delete=CASCADE, help_text='Schema used for specifications_doc.') - # TODO add stop_time to the model and calculate either duration or stop_time (in serializer) - # See TMSS-668 @property - def stop_time(self) -> datetime.datetime: - '''The stop_time based on start_time+duration if duration is known, else None''' - if self.duration: - return self.start_time + datetime.timedelta(seconds=self.duration) - return None + def duration(self) -> int: + '''return the overall duration (in seconds) of this task, if stop_time in None than duration ia also None + ''' + if self.stop_time: + return (self.stop_time - self.start_time).total_seconds() + else: + return None def save(self, force_insert=False, force_update=False, using=None, update_fields=None): annotate_validate_add_defaults_to_doc_using_template(self, 'specifications_doc', 'specifications_template') diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/populate.py b/SAS/TMSS/backend/src/tmss/tmssapp/populate.py index 25cb33a8e3f4f580c3824bc60290cc647090ab78..55632a06799b3933e9007e1e76094a545c1bf42e 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/populate.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/populate.py @@ -136,36 +136,158 @@ def populate_test_data(): def populate_cycles(apps, schema_editor): - for nr in range(0, 18): + # Cycle 0 deviates from any patterns + cycle = models.Cycle.objects.create(name="Cycle 00", + description="Lofar Cycle 0", + start=datetime(2013, 2, 11, 0, 0, 0, 0, tzinfo=timezone.utc), + stop=datetime(2013, 11, 14, 0, 0, 0, 0, tzinfo=timezone.utc)) + + models.CycleQuota.objects.bulk_create([models.CycleQuota(cycle=cycle, + resource_type=ResourceType.objects.get( + name="LOFAR Observing Time"), + value=0.8 * cycle.duration.total_seconds()), + # rough guess. 80% of total time available for observing + models.CycleQuota(cycle=cycle, + resource_type=ResourceType.objects.get( + name="CEP Processing Time"), + value=0.8 * cycle.duration.total_seconds()), + models.CycleQuota(cycle=cycle, + resource_type=ResourceType.objects.get(name="LTA Storage"), + value=0), # needs to be filled in by user (SOS) + models.CycleQuota(cycle=cycle, + resource_type=ResourceType.objects.get( + name="LOFAR Support Time"), + value=0), # needs to be filled in by user (SOS) + + models.CycleQuota(cycle=cycle, + resource_type=ResourceType.objects.get( + name="LOFAR Observing Time Commissioning"), + value=0.05 * cycle.duration.total_seconds()), + # rough guess. 5% of total time available for observing + models.CycleQuota(cycle=cycle, + resource_type=ResourceType.objects.get( + name="LOFAR Observing Time prio A"), + value=0), # needs to be filled in by user (SOS) + models.CycleQuota(cycle=cycle, + resource_type=ResourceType.objects.get( + name="LOFAR Observing Time prio B"), + value=0) # needs to be filled in by user (SOS) + ]) + + # Cycles 1-10 follow the same pattern + for nr in range(1, 11): cycle = models.Cycle.objects.create(name="Cycle %02d" % nr, description="Lofar Cycle %s" % nr, - start=datetime(2013+nr//2, 6 if nr%2==0 else 11, 1, 0, 0, 0, 0, tzinfo=timezone.utc), - stop=datetime(2013+(nr+1)//2, 6 if nr%2==1 else 11, 1, 0, 0, 0, 0, tzinfo=timezone.utc)) + start=datetime(2013+nr//2, 5 if nr%2==0 else 11, 15, 0, 0, 0, 0, tzinfo=timezone.utc), + stop=datetime(2013+(nr+1)//2, 5 if nr%2==1 else 11, 14, 0, 0, 0, 0, tzinfo=timezone.utc)) models.CycleQuota.objects.bulk_create([models.CycleQuota(cycle=cycle, - resource_type=ResourceType.objects.get(name="observing_time"), + resource_type=ResourceType.objects.get(name="LOFAR Observing Time"), value=0.8*cycle.duration.total_seconds()), # rough guess. 80% of total time available for observing models.CycleQuota(cycle=cycle, - resource_type=ResourceType.objects.get(name="cep_processing_time"), + resource_type=ResourceType.objects.get(name="CEP Processing Time"), value=0.8*cycle.duration.total_seconds()), models.CycleQuota(cycle=cycle, - resource_type=ResourceType.objects.get(name="lta_storage"), + resource_type=ResourceType.objects.get(name="LTA Storage"), value=0), # needs to be filled in by user (SOS) models.CycleQuota(cycle=cycle, - resource_type=ResourceType.objects.get(name="support_time"), + resource_type=ResourceType.objects.get(name="LOFAR Support Time"), value=0), # needs to be filled in by user (SOS) models.CycleQuota(cycle=cycle, - resource_type=ResourceType.objects.get(name="observing_time_commissioning"), + resource_type=ResourceType.objects.get(name="LOFAR Observing Time Commissioning"), value=0.05*cycle.duration.total_seconds()), # rough guess. 5% of total time available for observing models.CycleQuota(cycle=cycle, - resource_type=ResourceType.objects.get(name="observing_time_prio_a"), + resource_type=ResourceType.objects.get(name="LOFAR Observing Time prio A"), value=0), # needs to be filled in by user (SOS) models.CycleQuota(cycle=cycle, - resource_type=ResourceType.objects.get(name="observing_time_prio_b"), + resource_type=ResourceType.objects.get(name="LOFAR Observing Time prio B"), value=0) # needs to be filled in by user (SOS) ]) + # Cycle 11 deviates from any patterns + cycle = models.Cycle.objects.create(name="Cycle 11", + description="Lofar Cycle 11", + start=datetime(2018, 11, 15, 0, 0, 0, 0, tzinfo=timezone.utc), + stop=datetime(2019, 5, 31, 0, 0, 0, 0, tzinfo=timezone.utc)) + + models.CycleQuota.objects.bulk_create([models.CycleQuota(cycle=cycle, + resource_type=ResourceType.objects.get( + name="LOFAR Observing Time"), + value=0.8 * cycle.duration.total_seconds()), + # rough guess. 80% of total time available for observing + models.CycleQuota(cycle=cycle, + resource_type=ResourceType.objects.get( + name="CEP Processing Time"), + value=0.8 * cycle.duration.total_seconds()), + models.CycleQuota(cycle=cycle, + resource_type=ResourceType.objects.get( + name="LTA Storage"), + value=0), # needs to be filled in by user (SOS) + models.CycleQuota(cycle=cycle, + resource_type=ResourceType.objects.get( + name="LOFAR Support Time"), + value=0), # needs to be filled in by user (SOS) + + models.CycleQuota(cycle=cycle, + resource_type=ResourceType.objects.get( + name="LOFAR Observing Time Commissioning"), + value=0.05 * cycle.duration.total_seconds()), + # rough guess. 5% of total time available for observing + models.CycleQuota(cycle=cycle, + resource_type=ResourceType.objects.get( + name="LOFAR Observing Time prio A"), + value=0), # needs to be filled in by user (SOS) + models.CycleQuota(cycle=cycle, + resource_type=ResourceType.objects.get( + name="LOFAR Observing Time prio B"), + value=0) # needs to be filled in by user (SOS) + ]) + + # Cycles 12-19 follow the same pattern + for nr in range(12, 20): + cycle = models.Cycle.objects.create(name="Cycle %02d" % nr, + description="Lofar Cycle %s" % nr, + start=datetime(2013 + nr // 2, 6 if nr % 2 == 0 else 12, 1, 0, 0, 0, 0, + tzinfo=timezone.utc), + stop=datetime(2013 + (nr + 1) // 2, 5 if nr % 2 == 1 else 11, + 30 if nr % 2 == 0 else 31, 0, 0, + 0, 0, tzinfo=timezone.utc)) + + models.CycleQuota.objects.bulk_create([models.CycleQuota(cycle=cycle, + resource_type=ResourceType.objects.get( + name="LOFAR Observing Time"), + value=0.8 * cycle.duration.total_seconds()), + # rough guess. 80% of total time available for observing + models.CycleQuota(cycle=cycle, + resource_type=ResourceType.objects.get( + name="CEP Processing Time"), + value=0.8 * cycle.duration.total_seconds()), + models.CycleQuota(cycle=cycle, + resource_type=ResourceType.objects.get( + name="LTA Storage"), + value=0), # needs to be filled in by user (SOS) + models.CycleQuota(cycle=cycle, + resource_type=ResourceType.objects.get( + name="LOFAR Support Time"), + value=0), # needs to be filled in by user (SOS) + + models.CycleQuota(cycle=cycle, + resource_type=ResourceType.objects.get( + name="LOFAR Observing Time Commissioning"), + value=0.05 * cycle.duration.total_seconds()), + # rough guess. 5% of total time available for observing + models.CycleQuota(cycle=cycle, + resource_type=ResourceType.objects.get( + name="LOFAR Observing Time prio A"), + value=0), # needs to be filled in by user (SOS) + models.CycleQuota(cycle=cycle, + resource_type=ResourceType.objects.get( + name="LOFAR Observing Time prio B"), + value=0) # needs to be filled in by user (SOS) + ]) + + def populate_projects(apps, schema_editor): from lofar.sas.tmss.test.tmss_test_data_django_models import SchedulingSet_test_data @@ -184,7 +306,7 @@ def populate_projects(apps, schema_editor): # for convenience, create a schedulingset for each project models.SchedulingSet.objects.create(**SchedulingSet_test_data(name="Test Scheduling Set", project=tmss_project)) - project_quota = ProjectQuota.objects.create(project=tmss_project, value=1e12, resource_type=ResourceType.objects.get(name="lta_storage")) + project_quota = ProjectQuota.objects.create(project=tmss_project, value=1e12, resource_type=ResourceType.objects.get(name="LTA Storage")) sara_fs = Filesystem.objects.get(name="Lofar Storage (SARA)") models.ProjectQuotaArchiveLocation.objects.create(project_quota=project_quota, archive_location=sara_fs) @@ -194,18 +316,7 @@ def populate_resources(apps, schema_editor): time_q = Quantity.objects.get(value=Quantity.Choices.TIME.value) number_q = Quantity.objects.get(value=Quantity.Choices.NUMBER.value) - ResourceType.objects.bulk_create([ResourceType(name="lta_storage", description="Amount of storage in the LTA (in bytes)", quantity=bytes_q), - ResourceType(name="cep_storage", description="Amount of storage on the CEP processing cluster (in bytes)", quantity=bytes_q), - ResourceType(name="cep_processing_time", description="Processing time on the CEP processing cluster (in seconds)", quantity=time_q), - ResourceType(name="observing_time", description="Observing time (in seconds)", quantity=time_q), - ResourceType(name="observing_time_prio_a", description="Observing time with priority A (in seconds)", quantity=time_q), - ResourceType(name="observing_time_prio_b", description="Observing time with priority B (in seconds)", quantity=time_q), - ResourceType(name="observing_time_commissioning", description="Observing time for Commissioning/DDT (in seconds)", quantity=time_q), - ResourceType(name="support_time", description="Support time by human (in seconds)", quantity=time_q), - ResourceType(name="number_of_triggers", description="Number of trigger events (as integer)", quantity=number_q), - # TODO these duplicates have names that front-end expects. - # TODO We should not have doubles. - ResourceType(name="LTA Storage", description="Amount of storage in the LTA (in bytes)", quantity=bytes_q), + ResourceType.objects.bulk_create([ResourceType(name="LTA Storage", description="Amount of storage in the LTA (in bytes)", quantity=bytes_q), ResourceType(name="CEP Storage", description="Amount of storage on the CEP processing cluster (in bytes)", quantity=bytes_q), ResourceType(name="CEP Processing Time", description="Processing time on the CEP processing cluster (in seconds)", quantity=time_q), ResourceType(name="LOFAR Observing Time", description="Observing time (in seconds)", quantity=time_q), diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/reservations.py b/SAS/TMSS/backend/src/tmss/tmssapp/reservations.py new file mode 100644 index 0000000000000000000000000000000000000000..3cc5cd8794191a8e2fc9ddd064e54dc120b97f42 --- /dev/null +++ b/SAS/TMSS/backend/src/tmss/tmssapp/reservations.py @@ -0,0 +1,13 @@ +from lofar.sas.tmss.tmss.tmssapp import models + + +def get_active_station_reservations_in_timewindow(lower_bound, upper_bound): + """ + Retrieve a list of all active stations reservations, which are reserved between a timewindow + """ + lst_active_station_reservations = [] + for res in models.Reservation.objects.filter(start_time__lt=upper_bound, stop_time__gt=lower_bound).values('specifications_doc'): + lst_active_station_reservations += res["specifications_doc"]["resources"]["stations"] + for res in models.Reservation.objects.filter(start_time__lt=upper_bound, stop_time=None).values('specifications_doc'): + lst_active_station_reservations += res["specifications_doc"]["resources"]["stations"] + return list(set(lst_active_station_reservations)) diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/schemas/UC1-scheduling-unit-observation-strategy.json b/SAS/TMSS/backend/src/tmss/tmssapp/schemas/UC1-scheduling-unit-observation-strategy.json index 9763e45af29ef026a061b6b801f3931a0f5b8302..07081c0e3098153f07f55d8078608ece8776bec7 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/schemas/UC1-scheduling-unit-observation-strategy.json +++ b/SAS/TMSS/backend/src/tmss/tmssapp/schemas/UC1-scheduling-unit-observation-strategy.json @@ -9,8 +9,7 @@ "pointing": { "direction_type": "J2000", "angle1": 0, - "angle2": 0, - "angle3": 0 + "angle2": 0 }, "name": "calibrator1" }, @@ -80,8 +79,7 @@ "tile_beam": { "direction_type": "J2000", "angle1": 0.42, - "angle2": 0.43, - "angle3": 0.44 + "angle2": 0.43 }, "SAPs": [ { @@ -89,8 +87,7 @@ "digital_pointing": { "direction_type": "J2000", "angle1": 0.24, - "angle2": 0.25, - "angle3": 0.26 + "angle2": 0.25 }, "subbands": [ 349, @@ -102,8 +99,7 @@ "digital_pointing": { "direction_type": "J2000", "angle1": 0.27, - "angle2": 0.28, - "angle3": 0.29 + "angle2": 0.28 }, "subbands": [ 349, @@ -169,8 +165,7 @@ "pointing": { "direction_type": "J2000", "angle1": 0, - "angle2": 0, - "angle3": 0 + "angle2": 0 }, "name": "calibrator2" }, diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/schemas/common_schema_template-pointing-1.json b/SAS/TMSS/backend/src/tmss/tmssapp/schemas/common_schema_template-pointing-1.json index 88668838c82f03c889baee2825b7f8bf9823d3a4..75e850155bd192c799fc8e659516ac23c9ee2f2d 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/schemas/common_schema_template-pointing-1.json +++ b/SAS/TMSS/backend/src/tmss/tmssapp/schemas/common_schema_template-pointing-1.json @@ -42,12 +42,6 @@ "title": "Angle 2", "description": "Second angle (e.g. DEC)", "default": 0 - }, - "angle3": { - "type": "number", - "title": "Angle 3", - "description": "Third angle (e.g. N in LMN)", - "default": 0 } }, "required": [ diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/schemas/scheduling_constraints_template-constraints-1.json b/SAS/TMSS/backend/src/tmss/tmssapp/schemas/scheduling_constraints_template-constraints-1.json index 9caf086d923d583720925e44d47dfbc255f95885..732e7c01dc4b52dab7e4bf0b55c0972de92ea8d4 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/schemas/scheduling_constraints_template-constraints-1.json +++ b/SAS/TMSS/backend/src/tmss/tmssapp/schemas/scheduling_constraints_template-constraints-1.json @@ -111,13 +111,13 @@ "properties": { "from": { "type": "number", - "minimum": -0.20943951, - "maximum": 0.20943951 + "minimum": -86400, + "maximum": 86400 }, "to": { "type": "number", - "minimum": -0.20943951, - "maximum": 0.20943951 + "minimum": -86400, + "maximum": 86400 } }, "additionalProperties": false diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/schemas/short-observation-pipeline-ingest-scheduling-unit-observation-strategy.json b/SAS/TMSS/backend/src/tmss/tmssapp/schemas/short-observation-pipeline-ingest-scheduling-unit-observation-strategy.json index 23756bfb22a1d883279f592b8c01d7b453847e7b..ac3277566c7e385713036301a3c2a6af7bd3c911 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/schemas/short-observation-pipeline-ingest-scheduling-unit-observation-strategy.json +++ b/SAS/TMSS/backend/src/tmss/tmssapp/schemas/short-observation-pipeline-ingest-scheduling-unit-observation-strategy.json @@ -30,8 +30,7 @@ "tile_beam": { "direction_type": "J2000", "angle1": 5.233660650313663, - "angle2": 0.7109404782526458, - "angle3": 0 + "angle2": 0.7109404782526458 }, "SAPs": [ { @@ -40,8 +39,7 @@ "digital_pointing": { "direction_type": "J2000", "angle1": 5.233660650313663, - "angle2": 0.7109404782526458, - "angle3": 0 + "angle2": 0.7109404782526458 }, "subbands": [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243] } diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/schemas/simple-beamforming-observation-scheduling-unit-observation-strategy.json b/SAS/TMSS/backend/src/tmss/tmssapp/schemas/simple-beamforming-observation-scheduling-unit-observation-strategy.json index a711c97f18d6bb9f3facbe17f4f5f1a15e41f423..f74ee652b3c73ffbedb2451edce6531cf93f8990 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/schemas/simple-beamforming-observation-scheduling-unit-observation-strategy.json +++ b/SAS/TMSS/backend/src/tmss/tmssapp/schemas/simple-beamforming-observation-scheduling-unit-observation-strategy.json @@ -14,8 +14,7 @@ "digital_pointing": { "direction_type": "J2000", "angle1": 5.233660650313663, - "angle2": 0.7109404782526458, - "angle3": 0 + "angle2": 0.7109404782526458 }, "subbands": [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243] } @@ -26,8 +25,7 @@ "tile_beam": { "direction_type": "J2000", "angle1": 5.233660650313663, - "angle2": 0.7109404782526458, - "angle3": 0 + "angle2": 0.7109404782526458 }, "beamformers": [ {} ] }, diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/schemas/simple-observation-scheduling-unit-observation-strategy.json b/SAS/TMSS/backend/src/tmss/tmssapp/schemas/simple-observation-scheduling-unit-observation-strategy.json index 06e34636cc16cd68ccfc10c68b3b7180634d9070..4ea17e719fad83f17b9746f474f1761f9682a48f 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/schemas/simple-observation-scheduling-unit-observation-strategy.json +++ b/SAS/TMSS/backend/src/tmss/tmssapp/schemas/simple-observation-scheduling-unit-observation-strategy.json @@ -30,8 +30,7 @@ "tile_beam": { "direction_type": "J2000", "angle1": 5.233660650313663, - "angle2": 0.7109404782526458, - "angle3": 0 + "angle2": 0.7109404782526458 }, "SAPs": [ { @@ -40,8 +39,7 @@ "digital_pointing": { "direction_type": "J2000", "angle1": 5.233660650313663, - "angle2": 0.7109404782526458, - "angle3": 0 + "angle2": 0.7109404782526458 }, "subbands": [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243] } diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/serializers/specification.py b/SAS/TMSS/backend/src/tmss/tmssapp/serializers/specification.py index 188494e0936defd8c69f473f887661876a8ad5d1..47086104958108a4cc364a1c07c84c200d909d64 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/serializers/specification.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/serializers/specification.py @@ -171,10 +171,12 @@ class ProjectSerializer(DynamicRelationalHyperlinkedModelSerializer): class ProjectQuotaSerializer(DynamicRelationalHyperlinkedModelSerializer): + project_quota_archive_location = serializers.HyperlinkedRelatedField('projectquotaarchivelocation-detail', source='*', read_only=True) + class Meta: model = models.ProjectQuota fields = '__all__' - extra_fields = ['resource_type'] + extra_fields = ['resource_type', 'project_quota_archive_location'] class ProjectQuotaArchiveLocationSerializer(DynamicRelationalHyperlinkedModelSerializer): @@ -388,7 +390,7 @@ class ReservationSerializer(DynamicRelationalHyperlinkedModelSerializer): class Meta: model = models.Reservation fields = '__all__' - extra_fields = ['stop_time'] + extra_fields = ['duration'] class TaskBlueprintExtendedSerializer(TaskBlueprintSerializer): diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/subtasks.py b/SAS/TMSS/backend/src/tmss/tmssapp/subtasks.py index 243cb8b3ddbc8729a94b61606a3a7a4c93b5be42..60499017cdc4a4247f7716881ca2840f45ffb96a 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/subtasks.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/subtasks.py @@ -99,7 +99,7 @@ def _add_pointings(pointing_a, pointing_b): raise SubtaskCreationException( "Cannot add pointings because direction types differ pointing_a=%s; pointing_b=%s" % (pointing_a, pointing_b)) pointing = {"direction_type": pointing_a['direction_type']} - for angle in ['angle1', 'angle2', 'angle3']: + for angle in ['angle1', 'angle2']: pointing[angle] = pointing_a.get(angle, 0.0) + pointing_b.get(angle, 0.0) return pointing diff --git a/SAS/TMSS/backend/test/t_adapter.py b/SAS/TMSS/backend/test/t_adapter.py index f67d013c90e33f656334d3416444ec70006f7ffb..772a2d43ed706e328371dc2cdb048f38f65db9ed 100755 --- a/SAS/TMSS/backend/test/t_adapter.py +++ b/SAS/TMSS/backend/test/t_adapter.py @@ -214,7 +214,7 @@ class SIPadapterTest(unittest.TestCase): specifications_doc['stations']['filter'] = "HBA_210_250" feedback_template = models.DataproductFeedbackTemplate.objects.get(name='feedback') # feedback_doc = get_default_json_object_for_schema(feedback_template.schema) # todo <- fix the default generator, for some reason it does not produce valid json here... - feedback_doc = {'percentage_written': 100, 'frequency': {'subbands': [156], 'central_frequencies': [33593750.0], 'channel_width': 6103.515625, 'channels_per_subband': 32}, 'time': {'start_time': '2013-02-16T17:00:00', 'duration': 5.02732992172, 'sample_width': 2.00278016}, 'antennas': {'set': 'HBA_DUAL', 'fields': [{'type': 'HBA', 'field': 'HBA0', 'station': 'CS001'}, {'type': 'HBA', 'field': 'HBA1', 'station': 'CS001'}]}, 'target': {'pointing': {'angle1': 0, 'angle2': 0, 'angle3': 0, 'direction_type': 'J2000'}}, 'samples': {'polarisations': ['XX', 'XY', 'YX', 'YY'], 'type': 'float', 'bits': 32, 'writer': 'standard', 'writer_version': '2.2.0', 'complex': True}, '$schema': 'http://127.0.0.1:8001/api/schemas/dataproductfeedbacktemplate/feedback/1#'} + feedback_doc = {'percentage_written': 100, 'frequency': {'subbands': [156], 'central_frequencies': [33593750.0], 'channel_width': 6103.515625, 'channels_per_subband': 32}, 'time': {'start_time': '2013-02-16T17:00:00', 'duration': 5.02732992172, 'sample_width': 2.00278016}, 'antennas': {'set': 'HBA_DUAL', 'fields': [{'type': 'HBA', 'field': 'HBA0', 'station': 'CS001'}, {'type': 'HBA', 'field': 'HBA1', 'station': 'CS001'}]}, 'target': {'pointing': {'angle1': 0, 'angle2': 0, 'direction_type': 'J2000'}}, 'samples': {'polarisations': ['XX', 'XY', 'YX', 'YY'], 'type': 'float', 'bits': 32, 'writer': 'standard', 'writer_version': '2.2.0', 'complex': True}, '$schema': 'http://127.0.0.1:8001/api/schemas/dataproductfeedbacktemplate/feedback/1#'} for dp in specifications_doc['stations']['digital_pointings']: dp['subbands'] = list(range(8)) # Create SubTask(output) diff --git a/SAS/TMSS/backend/test/t_conversions.py b/SAS/TMSS/backend/test/t_conversions.py index 7f8d66d6e4b8758b3cf13bf04bf3d8488deb89ad..1773168c7b1ded14c41aee27f0fddd6683d9f9f7 100755 --- a/SAS/TMSS/backend/test/t_conversions.py +++ b/SAS/TMSS/backend/test/t_conversions.py @@ -362,6 +362,35 @@ class UtilREST(unittest.TestCase): self.assertNotEqual(rise, rise_last) rise_last = rise + def test_util_target_rise_and_set_detects_when_target_above_horizon(self): + + # assert always below and always above are usually false + r = requests.get(BASE_URL + '/util/target_rise_and_set?angle1=0.5&angle2=0.8×tamps=2020-01-01&horizon=0.2', auth=AUTH) + self.assertEqual(r.status_code, 200) + r_dict = json.loads(r.content.decode('utf-8')) + self.assertIsNotNone(r_dict['CS002'][0]['rise']) + self.assertIsNotNone(r_dict['CS002'][0]['set']) + self.assertFalse(r_dict['CS002'][0]['always_below_horizon']) + self.assertFalse(r_dict['CS002'][0]['always_above_horizon']) + + # assert rise and set are None and flag is true when target is always above horizon + r = requests.get(BASE_URL + '/util/target_rise_and_set?angle1=0.5&angle2=0.8×tamps=2020-01-01&horizon=0.1', auth=AUTH) + self.assertEqual(r.status_code, 200) + r_dict = json.loads(r.content.decode('utf-8')) + self.assertIsNone(r_dict['CS002'][0]['rise']) + self.assertIsNone(r_dict['CS002'][0]['set']) + self.assertTrue(r_dict['CS002'][0]['always_above_horizon']) + self.assertFalse(r_dict['CS002'][0]['always_below_horizon']) + + # assert rise and set are None and flag is true when target is always below horizon + r = requests.get(BASE_URL + '/util/target_rise_and_set?angle1=0.5&angle2=-0.5×tamps=2020-01-01&horizon=0.2', auth=AUTH) + self.assertEqual(r.status_code, 200) + r_dict = json.loads(r.content.decode('utf-8')) + self.assertIsNone(r_dict['CS002'][0]['rise']) + self.assertIsNone(r_dict['CS002'][0]['set']) + self.assertFalse(r_dict['CS002'][0]['always_above_horizon']) + self.assertTrue(r_dict['CS002'][0]['always_below_horizon']) + if __name__ == "__main__": os.environ['TZ'] = 'UTC' diff --git a/SAS/TMSS/backend/test/t_reservations.py b/SAS/TMSS/backend/test/t_reservations.py index ae8a41b8a47b88c94a826d1bd0454e2254b856a5..c19f18ef56a8a3ec29bff4f43fb243487618607e 100755 --- a/SAS/TMSS/backend/test/t_reservations.py +++ b/SAS/TMSS/backend/test/t_reservations.py @@ -46,6 +46,200 @@ rest_data_creator = TMSSRESTTestDataCreator(BASE_URL, AUTH) from lofar.sas.tmss.tmss.tmssapp import models + +from lofar.sas.tmss.tmss.tmssapp.reservations import get_active_station_reservations_in_timewindow + + +class TestStationReservations(unittest.TestCase): + """ + Tests for the active station reservations + """ + + def setUp(self) -> None: + # wipe all reservations in between tests, so the tests don't influence each other + for reservation in models.Reservation.objects.all(): + reservation.delete() + + @staticmethod + def create_station_reservation(additional_name, lst_stations, start_time, stop_time=None): + """ + Create a station reservation with given list of stations, start_time and stop_time + """ + reservation_template = models.ReservationTemplate.objects.get(name="resource reservation") + reservation_template_spec = get_default_json_object_for_schema(reservation_template.schema) + reservation_template_spec["resources"] = {"stations": lst_stations } + res = models.Reservation.objects.create(name="Station Reservation %s" % additional_name, + description="Station reservation for testing", + specifications_template=reservation_template, + specifications_doc=reservation_template_spec, + start_time=start_time, + stop_time=stop_time) + + def test_no_stations_reservation(self): + """ + Check that creating 'default' reservation with no additional station reservation added, we still can + call 'get_active_station_reservations_in_timewindow' and it will return an empty list + """ + reservation_template = models.ReservationTemplate.objects.get(name="resource reservation") + reservation_template_spec = get_default_json_object_for_schema(reservation_template.schema) + res = models.Reservation.objects.create(name="AnyReservation", + description="Reservation of something else", + specifications_template=reservation_template, + specifications_doc=reservation_template_spec, + start_time=datetime.now(), + stop_time=None) + self.assertCountEqual([], + get_active_station_reservations_in_timewindow(datetime.now(), datetime.now()+timedelta(weeks=53))) + + def test_active_station_reservation(self): + """ + Test station reservation when 2 stations are reserved for 24hr with both same start and stop time + Check 'get_active_station_reservations_in_timewindow' with different time ranges + """ + reservation_start_time = datetime(2020, 1, 1, 0, 0, 0) + reservation_stop_time = datetime(2020, 1, 2, 0, 0, 0) + reservation_stations = ["CS001", "CS002"] + self.create_station_reservation("two_stations", reservation_stations, reservation_start_time, reservation_stop_time) + self.assertCountEqual([], + get_active_station_reservations_in_timewindow(datetime.now(), datetime.now())) + self.assertCountEqual(reservation_stations, + get_active_station_reservations_in_timewindow(reservation_start_time, + reservation_stop_time)) + self.assertCountEqual([], + get_active_station_reservations_in_timewindow(reservation_start_time, + reservation_start_time)) + self.assertCountEqual(reservation_stations, + get_active_station_reservations_in_timewindow(reservation_start_time, + reservation_start_time+timedelta(seconds=1))) + self.assertCountEqual([], + get_active_station_reservations_in_timewindow(reservation_stop_time, + reservation_stop_time)) + self.assertCountEqual(reservation_stations, + get_active_station_reservations_in_timewindow(reservation_stop_time-timedelta(seconds=1), + reservation_stop_time)) + self.assertCountEqual(reservation_stations, + get_active_station_reservations_in_timewindow(reservation_start_time-timedelta(weeks=53), + reservation_stop_time+timedelta(weeks=53))) + self.assertCountEqual(reservation_stations, + get_active_station_reservations_in_timewindow(reservation_start_time, + datetime.now()+timedelta(weeks=53))) + self.assertCountEqual(reservation_stations, + get_active_station_reservations_in_timewindow(datetime(2020, 1, 1, 12, 0, 0), + datetime(2020, 1, 2, 12, 0, 0))) + + def test_active_station_reservation_with_same_station_overlap(self): + """ + Test station reservation when 2 stations are reserved for 24hr with both same start and stop time + Same stations are reserved separately as well, which should result NOT result in having the station twice in + the resulting active station list + Check 'get_active_station_reservations_in_timewindow' with different time ranges + """ + reservation_start_time = datetime(2020, 1, 1, 0, 0, 0) + reservation_stop_time = datetime(2020, 1, 2, 0, 0, 0) + reservation_stations = ["CS001", "CS002"] + self.create_station_reservation("two_stations", reservation_stations, reservation_start_time, reservation_stop_time) + self.create_station_reservation("cs1", ["CS001"], reservation_start_time, reservation_stop_time) + self.create_station_reservation("cs2", ["CS002"], reservation_start_time, reservation_stop_time) + + # same lower_bound as upper_bound, empty list + self.assertCountEqual([], + get_active_station_reservations_in_timewindow(datetime.now(), datetime.now())) + self.assertCountEqual(reservation_stations, + get_active_station_reservations_in_timewindow(reservation_start_time, + reservation_stop_time)) + self.assertCountEqual([], + get_active_station_reservations_in_timewindow(reservation_start_time, + reservation_start_time)) + self.assertCountEqual(reservation_stations, + get_active_station_reservations_in_timewindow(reservation_start_time, + reservation_start_time + timedelta(seconds=1))) + self.assertCountEqual([], + get_active_station_reservations_in_timewindow(reservation_stop_time, + reservation_stop_time + timedelta(seconds=1))) + self.assertCountEqual(reservation_stations, + get_active_station_reservations_in_timewindow(reservation_stop_time - timedelta(seconds=1), + reservation_stop_time)) + self.assertCountEqual(reservation_stations, + get_active_station_reservations_in_timewindow(reservation_start_time - timedelta(weeks=53), + reservation_stop_time + timedelta(weeks=53))) + self.assertCountEqual(reservation_stations, + get_active_station_reservations_in_timewindow(reservation_start_time, + datetime.now() + timedelta(weeks=53))) + self.assertCountEqual(reservation_stations, + get_active_station_reservations_in_timewindow(datetime(2020, 1, 1, 12, 0, 0), + datetime(2020, 1, 2, 12, 0, 0))) + + def test_active_station_reservation_with_station_no_stop_time(self): + """ + Test station reservation when 2 stations are reserved forever both same start and stop time + Check 'get_active_station_reservations_in_timewindow' with different time ranges + """ + reservation_start_time = datetime(2020, 1, 1, 0, 0, 0) + reservation_stations = ["CS001", "CS002"] + self.create_station_reservation("two_stations_no_end_time", reservation_stations, reservation_start_time) + + # we are now still in reservation started in 2020 + self.assertCountEqual(reservation_stations, + get_active_station_reservations_in_timewindow(datetime.now(), datetime.now())) + self.assertCountEqual(reservation_stations, + get_active_station_reservations_in_timewindow(reservation_start_time, + reservation_start_time + timedelta(seconds=1))) + # before start time, always empty + self.assertCountEqual([], + get_active_station_reservations_in_timewindow(reservation_start_time - timedelta(seconds=1), + reservation_start_time)) + self.assertCountEqual(reservation_stations, + get_active_station_reservations_in_timewindow(reservation_start_time - timedelta(weeks=53), + reservation_start_time + timedelta(weeks=53))) + self.assertCountEqual(reservation_stations, + get_active_station_reservations_in_timewindow(reservation_start_time, + datetime.now() + timedelta(weeks=53))) + self.assertCountEqual(reservation_stations, + get_active_station_reservations_in_timewindow(datetime(2020, 1, 1, 12, 0, 0), + datetime(2020, 1, 2, 12, 0, 0))) + + def test_active_station_reservation_every_hour_one_station(self): + """ + Test station reservation when 1 station is reserved for 1 hour + Check 'get_active_station_reservations_in_timewindow' with different time ranges + """ + first_day = 1 + last_day = 7 + reservation_start_time = datetime(2020, 1, first_day, 0, 0, 0) + reservation_stop_time = datetime(2020, 1, last_day+1, 0, 0, 0) + for day_nbr in range(first_day, last_day+1): + self.create_station_reservation("cs%s" % day_nbr, ["CS00%d" % day_nbr], + datetime(2020, 1, day_nbr, 12, 0, 0), datetime(2020, 1, day_nbr+1, 0, 0, 0)) + + self.assertCountEqual([], + get_active_station_reservations_in_timewindow(datetime.now(), datetime.now())) + self.assertCountEqual(["CS001","CS002","CS003","CS004","CS005","CS006","CS007"], + get_active_station_reservations_in_timewindow(reservation_start_time, reservation_stop_time)) + self.assertCountEqual(["CS002","CS003","CS004","CS005","CS006","CS007"], + get_active_station_reservations_in_timewindow(reservation_start_time+timedelta(days=1), reservation_stop_time)) + self.assertCountEqual(["CS003","CS004","CS005","CS006","CS007"], + get_active_station_reservations_in_timewindow(reservation_start_time+timedelta(days=2), reservation_stop_time)) + self.assertCountEqual(["CS004","CS005","CS006","CS007"], + get_active_station_reservations_in_timewindow(reservation_start_time+timedelta(days=3), reservation_stop_time)) + self.assertCountEqual(["CS005","CS006","CS007"], + get_active_station_reservations_in_timewindow(reservation_start_time+timedelta(days=4), reservation_stop_time)) + self.assertCountEqual(["CS006","CS007"], + get_active_station_reservations_in_timewindow(reservation_start_time+timedelta(days=5), reservation_stop_time)) + self.assertCountEqual(["CS007"], + get_active_station_reservations_in_timewindow(reservation_start_time+timedelta(days=6), reservation_stop_time)) + + self.assertCountEqual(["CS001","CS002","CS003","CS004","CS005","CS006"], + get_active_station_reservations_in_timewindow(reservation_start_time, reservation_stop_time-timedelta(days=1))) + self.assertCountEqual(["CS001","CS002","CS003","CS004","CS005"], + get_active_station_reservations_in_timewindow(reservation_start_time, reservation_stop_time-timedelta(days=2))) + self.assertCountEqual(["CS001","CS002","CS003","CS004"], + get_active_station_reservations_in_timewindow(reservation_start_time, reservation_stop_time-timedelta(days=3))) + self.assertCountEqual(["CS001","CS002","CS003"], + get_active_station_reservations_in_timewindow(reservation_start_time, reservation_stop_time-timedelta(days=4))) + self.assertCountEqual(["CS001","CS002"], + get_active_station_reservations_in_timewindow(reservation_start_time, reservation_stop_time-timedelta(days=5))) + self.assertCountEqual(["CS001"], + get_active_station_reservations_in_timewindow(reservation_start_time, reservation_stop_time-timedelta(days=6))) from lofar.sas.tmss.tmss.exceptions import SchemaValidationException from django.core.exceptions import ValidationError diff --git a/SAS/TMSS/backend/test/t_subtasks.py b/SAS/TMSS/backend/test/t_subtasks.py index c3a8c261bd74c019f4728aa4d525e376aad875cf..806fcd682579d20829b1b010f5548fb530ae73e1 100755 --- a/SAS/TMSS/backend/test/t_subtasks.py +++ b/SAS/TMSS/backend/test/t_subtasks.py @@ -230,10 +230,10 @@ class SubTasksCreationFromTaskBluePrint(unittest.TestCase): def test_create_subtasks_from_task_blueprint_translates_SAP_names(self): task_blueprint = create_task_blueprint_object_for_testing('target observation') task_blueprint.specifications_doc['SAPs'] = [{'name': 'target1', 'target': '', 'subbands': [], - 'digital_pointing': {'angle1': 0.1, 'angle2': 0.1, 'angle3': 0.1, + 'digital_pointing': {'angle1': 0.1, 'angle2': 0.1, 'direction_type': 'J2000'}}, {'name': 'target2', 'target': '', 'subbands': [], - 'digital_pointing': {'angle1': 0.2, 'angle2': 0.2, 'angle3': 0.2, + 'digital_pointing': {'angle1': 0.2, 'angle2': 0.2, 'direction_type': 'J2000'}}] subtask = create_observation_control_subtask_from_task_blueprint(task_blueprint) i = 0 @@ -386,9 +386,9 @@ class SettingTest(unittest.TestCase): class SubTaskCreationFromTaskBlueprintBeamformer(unittest.TestCase): saps = [{"name": "target1", "target": "", "subbands": [349, 372], - "digital_pointing": {"angle1": 0.24, "angle2": 0.25, "angle3": 0.26, "direction_type": "J2000"}}, + "digital_pointing": {"angle1": 0.24, "angle2": 0.25, "direction_type": "J2000"}}, {"name": "target2", "target": "", "subbands": [309, 302], - "digital_pointing": {"angle1": 0.42, "angle2": 0.52, "angle3": 0.62, "direction_type": "J2000"}} + "digital_pointing": {"angle1": 0.42, "angle2": 0.52, "direction_type": "J2000"}} ] beamformers = [{"name": "beamformer1", "coherent": {"settings": {"stokes": "I", "time_integration_factor": 8, "subbands_per_file": 244, @@ -447,7 +447,7 @@ class SubTaskCreationFromTaskBlueprintBeamformer(unittest.TestCase): def test_generate_tab_ring_pointings_returns_correct_pointings(self): - pointing = {"angle1": 0.11, "angle2": 0.22, "angle3": 0.33, "direction_type": "J2000"} + pointing = {"angle1": 0.11, "angle2": 0.22, "direction_type": "J2000"} tab_rings = {"width": 1, "count": 1} # assert center pointing is returned @@ -469,9 +469,9 @@ class SubTaskCreationFromTaskBlueprintBeamformer(unittest.TestCase): def test_add_pointings_adds_correctly(self): pointing_a = {"angle1": 0.11, "angle2": 0.22, "direction_type": "J2000"} - pointing_b = {"angle1": 0.88, "angle2": 0.66, "angle3": 0.77, "direction_type": "J2000"} + pointing_b = {"angle1": 0.88, "angle2": 0.66, "direction_type": "J2000"} pointing_sum = _add_pointings(pointing_a, pointing_b) - self.assertEqual(pointing_sum, {"angle1": 0.99, "angle2": 0.88, "angle3": 0.77, "direction_type": "J2000"}) + self.assertEqual(pointing_sum, {"angle1": 0.99, "angle2": 0.88, "direction_type": "J2000"}) def test_filter_subbands_filters_correctly(self): subbands = [1,3,4,5,10,11,12,13,19,20] diff --git a/SAS/TMSS/backend/test/t_tmssapp_specification_REST_API.py b/SAS/TMSS/backend/test/t_tmssapp_specification_REST_API.py index 5fd6407a83fab52cadb1226617d3eba5e3822c8c..d3da150deaa98063eb7c714b99090c37447c8597 100755 --- a/SAS/TMSS/backend/test/t_tmssapp_specification_REST_API.py +++ b/SAS/TMSS/backend/test/t_tmssapp_specification_REST_API.py @@ -2898,7 +2898,7 @@ class ReservationTestCase(unittest.TestCase): GET_OK_and_assert_equal_expected_response(self, url, reservation_test_data) test_patch = {"description": "This is a new and improved description", - "duration": 90} + "stop_time": None} # PATCH item and verify expected_patch_data = test_data_creator.update_schema_from_template("reservationtemplate", test_patch) diff --git a/SAS/TMSS/backend/test/tmss_test_data_django_models.py b/SAS/TMSS/backend/test/tmss_test_data_django_models.py index 8535c5e223890e3be48467853ecb486f073985fe..03bd63e347821654485556b3e2c146e9aea2d92b 100644 --- a/SAS/TMSS/backend/test/tmss_test_data_django_models.py +++ b/SAS/TMSS/backend/test/tmss_test_data_django_models.py @@ -562,6 +562,11 @@ def Reservation_test_data(name="MyReservation", duration=None, start_time=None, if start_time is None: start_time = datetime.utcnow() + timedelta(hours=12) + if duration is None: + stop_time = None + else: + stop_time = start_time + timedelta(seconds=duration) + specifications_template = models.ReservationTemplate.objects.create(**ReservationTemplate_test_data()) specifications_doc = get_default_json_object_for_schema(specifications_template.schema) @@ -570,7 +575,7 @@ def Reservation_test_data(name="MyReservation", duration=None, start_time=None, "description": "Test Reservation", "tags": ["TMSS", "TESTING"], "start_time": start_time, - "duration": duration, # can be None + "stop_time": stop_time, # can be None "specifications_doc": specifications_doc, "specifications_template": specifications_template} diff --git a/SAS/TMSS/backend/test/tmss_test_data_rest.py b/SAS/TMSS/backend/test/tmss_test_data_rest.py index afd7c5d57cd097130cf4707066e6a8b92b952f2a..551a251a68857807ceb4e4bf63699d6bd575c44d 100644 --- a/SAS/TMSS/backend/test/tmss_test_data_rest.py +++ b/SAS/TMSS/backend/test/tmss_test_data_rest.py @@ -851,6 +851,11 @@ class TMSSRESTTestDataCreator(): if start_time is None: start_time = datetime.utcnow() + timedelta(hours=12) + if duration is None: + stop_time = None + else: + stop_time = start_time + timedelta(seconds=duration) + if specifications_template_url is None: specifications_template_url = self.post_data_and_get_url(self.ReservationTemplate(), '/reservation_template/') @@ -860,12 +865,15 @@ class TMSSRESTTestDataCreator(): if isinstance(start_time, datetime): start_time = start_time.isoformat() + if isinstance(stop_time, datetime): + stop_time = stop_time.isoformat() + return {"name": name, "project": project_url, "description": "Test Reservation", "tags": ["TMSS", "TESTING"], "start_time": start_time, - "duration": duration, # can be None + "stop_time": stop_time, # can be None "specifications_doc": specifications_doc, "specifications_template": specifications_template_url} diff --git a/SAS/TMSS/frontend/tmss_webapp/package.json b/SAS/TMSS/frontend/tmss_webapp/package.json index e9cc1d244a28ffcb034292897693fb7875c7a0f9..5de0cf8841f3e116bcd8cf264c26613650b2467f 100644 --- a/SAS/TMSS/frontend/tmss_webapp/package.json +++ b/SAS/TMSS/frontend/tmss_webapp/package.json @@ -38,6 +38,7 @@ "react-bootstrap-datetimepicker": "0.0.22", "react-calendar-timeline": "^0.27.0", "react-dom": "^16.13.1", + "react-flatpickr": "^3.10.7", "react-frame-component": "^4.1.2", "react-json-to-table": "^0.1.7", "react-json-view": "^1.19.1", diff --git a/SAS/TMSS/frontend/tmss_webapp/src/App.js b/SAS/TMSS/frontend/tmss_webapp/src/App.js index 12ca8a5aa7118073ce961d010b690f1d54143a1a..74a6f8c2bd0dc57fea26971a4d83c1bf3d076c4b 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/App.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/App.js @@ -8,6 +8,9 @@ import {AppFooter } from './layout/components/AppFooter'; import {RoutedContent} from './routes'; import {AppBreadcrumb } from "./layout/components/AppBreadcrumb"; import {withRouter } from 'react-router'; +import handleResponse from "./response.handler" +import { setAppGrowl } from './layout/components/AppGrowl'; +import { Growl } from 'primereact/components/growl/Growl'; import 'primeicons/primeicons.css'; import 'primereact/resources/themes/nova-light/theme.css'; @@ -139,6 +142,7 @@ class App extends Component { //console.log(this.props); return ( <React.Fragment> + <Growl ref={(el) => setAppGrowl(el)} /> <div className="App"> {/* <div className={wrapperClass} onClick={this.onWrapperClick}> */} <div className={wrapperClass}> @@ -177,4 +181,4 @@ class App extends Component { } } -export default App; +export default handleResponse(App); diff --git a/SAS/TMSS/frontend/tmss_webapp/src/components/Spreadsheet/CustomDateComp.js b/SAS/TMSS/frontend/tmss_webapp/src/components/Spreadsheet/CustomDateComp.js index 98d84429009f475b44411113fe6d7d6d319dcf88..50623578335782048c11ba4ff25bb4f182370119 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/components/Spreadsheet/CustomDateComp.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/components/Spreadsheet/CustomDateComp.js @@ -1,7 +1,11 @@ import React, { Component } from 'react'; +import Flatpickr from "react-flatpickr"; import {Calendar} from 'primereact/calendar'; import moment from 'moment'; import UIConstants from '../../utils/ui.constants'; +import UtilService from '../../services/util.service'; + +import "flatpickr/dist/flatpickr.css"; //const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'; @@ -16,9 +20,13 @@ export default class CustomDateComp extends Component { componentDidMount(){ let parentRows = this.props.agGridReact.props.rowData[this.props.node.rowIndex]; let parentCellData = parentRows[this.props.colDef.field]; - this.setState({ - date:parentCellData - }) + UtilService.getUTC() + .then(systemTime => { + this.setState({ + date:parentCellData, + systemTime: moment.utc(systemTime) + }) + }); } isPopup() { @@ -33,30 +41,30 @@ export default class CustomDateComp extends Component { } render() { - return ( - <Calendar - d dateFormat = {UIConstants.CALENDAR_DATE_FORMAT} - value= {this.state.date} - onChange= {e => {this.updateDateChanges(e)}} - // onBlur= {e => {this.updateDateChanges(e)}} - //data-testid="start" - todayButtonClassName="today-calendar-btn" - showButtonBar - showTime= {true} - showSeconds= {true} - hourFormat= "24" - showIcon= {false} inline - /> - ); + return this.state.systemTime?( + <Flatpickr + data-enable-time + options={{ + "inline": true, + "enableSeconds": true, + "time_24hr": true, + "defaultDate": this.state.systemTime?this.state.systemTime.format(UIConstants.CALENDAR_DEFAULTDATE_FORMAT):"", + "defaultHour": this.state.systemTime?this.state.systemTime.hours():12, + "defaultMinute": this.state.systemTime?this.state.systemTime.minutes():0 + }} + value={this.state.date} + onChange= {value => {this.updateDateChanges(value[0]?value[0]:this.state.date)}} + /> + ):""; } updateDateChanges(e){ - this.setState({date : e.value || ''}); + this.setState({date : e || ''}); } ondatechange(e){ - this.setState({date : e.value}); + this.setState({date : e}); } getDate() { diff --git a/SAS/TMSS/frontend/tmss_webapp/src/response.handler.js b/SAS/TMSS/frontend/tmss_webapp/src/response.handler.js new file mode 100644 index 0000000000000000000000000000000000000000..7c4da4c87de73f67983fb60f36e2c6aff269ab8d --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/response.handler.js @@ -0,0 +1,45 @@ +import React, {useEffect} from "react"; +import axios from "axios"; +import { appGrowl } from './layout/components/AppGrowl'; +import UIConstants from './utils/ui.constants'; +import Auth from './authenticate/auth'; +/** + * Trigger and validate the response for https status code + * @param {*} Wrapped + * @returns + */ +const handleResponse= Wrapped => { + function HandleResponse(props) { + useEffect(()=>{ + axios.interceptors.response.use(function (response) { + return response; + }, function (error) { + showMessage(error.response); + return Promise.reject(error); + }); + }) + return ( + <Wrapped {...props} /> + ); + } + + /** + * Catch relavent http status code details to show in growl + * @param {*} response + */ + function showMessage(response) { + const httpStatusMsg = UIConstants.httpStatusMessages[response.status]; + if(httpStatusMsg) { + appGrowl.show({severity: httpStatusMsg.severity, summary: httpStatusMsg.summary, sticky: httpStatusMsg.sticky, detail: '['+response.status+'] '+JSON.stringify(response.statusText)+ ' ['+httpStatusMsg.detail+']'}); + } else { + appGrowl.show({severity: 'error', summary: 'Error', sticky: 'true', detail: '['+response.status+'] '+JSON.stringify(response.statusText)+ ' '+JSON.stringify(response.data)}); + } + if (response.status === 401) { + Auth.logout(); + window.location.href = "/login"; + } + } + return HandleResponse; +} + +export default handleResponse; \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Cycle/edit.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Cycle/edit.js index 7ef3b05cc8bf50d554ae4f4fcd64e409b7e853c0..b64a1d66d444ac9001fe31e5ce2486d70f8c54b8 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Cycle/edit.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Cycle/edit.js @@ -318,7 +318,7 @@ export class CycleEdit extends Component { this.saveCycleQuota(cycle); } else { this.growl.show({severity: 'error', summary: 'Error Occured', detail: 'Unable to update Cycle'}); - this.setState({errors: cycle}); + //this.setState({errors: cycle}); } }); } @@ -373,7 +373,7 @@ export class CycleEdit extends Component { if (_.keys(quotaError).length === 0) { dialog = {header: 'Success', detail: 'Cycle updated successfully.'}; } else { - dialog = {header: 'Error', detail: 'Cycle updated successfully but resource allocation not updated properly. Try again!'}; + dialog = {header: 'Error', detail: 'Cycle updated successfully but resource allocation not updated properly.'}; } this.setState({dialogVisible: true, dialog: dialog}); } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/create.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/create.js index c21f5afcd834388b99fe3836cd7d598be970a608..caf0c0b6e487bff2e139cdab5f857a9cc0bca1c5 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/create.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/create.js @@ -325,12 +325,17 @@ export class ProjectCreate extends Component { } ProjectService.saveProject(this.state.project, this.defaultResourcesEnabled?projectQuota:[]) .then(project => { + if (project.url) { let dialog = {}; - if (this.defaultResourcesEnabled) { - dialog = {header: 'Success', detail: 'Project saved successfully. Do you want to create another project?'}; + if (project.isQuotaCreated) { + if (this.defaultResourcesEnabled) { + dialog = {header: 'Success', detail: 'Project saved successfully. Do you want to create another project?'}; + } else { + dialog = {header: 'Success', detail: 'Project saved successfully with default Resource allocations. Do you want to view and edit them?'}; + } } else { - dialog = {header: 'Success', detail: 'Project saved successfully with default Resource allocations. Do you want to view and edit them?'}; + dialog = {header: 'Warning', detail: 'Project saved successfully, but resource allocation not saved.'}; } this.setState({project:project, dialogVisible: true, dialog: dialog, isDirty: false}); } else { diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/edit.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/edit.js index b8bd0f3e2f9833bf6290e035240e88bad4d9695d..ac275f366da7624c2c9a2149b18690d2b9db297a 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/edit.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/edit.js @@ -335,7 +335,7 @@ export class ProjectEdit extends Component { // project['archive_subdirectory'] = (project['archive_subdirectory'].substr(-1) === '/' ? project['archive_subdirectory'] : `${project['archive_subdirectory']}/`).toLowerCase(); ProjectService.updateProject(this.props.match.params.id, project) .then(async (project) => { - if (project && this.state.project.updated_at !== project.updated_at) { + if (project && project.isUpdated && this.state.project.updated_at !== project.updated_at) { this.saveProjectQuota(project); } else { this.growl.show({severity: 'error', summary: 'Error Occured', detail: 'Unable to update Project'}); @@ -381,20 +381,20 @@ export class ProjectEdit extends Component { } for (const projectQuota of updatingProjectQuota) { const updatedProjectQuota = await ProjectService.updateProjectQuota(projectQuota); - if (!updatedProjectQuota) { + if (!updatedProjectQuota || (updatedProjectQuota.status && updatedProjectQuota.status > 299)) { quotaError[projectQuota.resource_type_id] = true; } } for (const projectQuota of newProjectQuota) { const createdProjectQuota = await ProjectService.saveProjectQuota(projectQuota); - if (!createdProjectQuota) { + if (!createdProjectQuota || (createdProjectQuota.status && createdProjectQuota.status > 299)) { quotaError[projectQuota.resource_type_id] = true; } } if (_.keys(quotaError).length === 0) { dialog = {header: 'Success', detail: 'Project updated successfully.'}; } else { - dialog = {header: 'Error', detail: 'Project updated successfully but resource allocation not updated properly. Try again!'}; + dialog = {header: 'Error', detail: 'Project updated successfully but resource allocation not updated properly.'}; } this.setState({dialogVisible: true, dialog: dialog, isDirty: false}); } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/Scheduling.Constraints.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/Scheduling.Constraints.js index 35725047eaeceb182457875029cbba90b4cd7320..396b74fd9c413e4ca93798b76ba3462889ac7dd0 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/Scheduling.Constraints.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/Scheduling.Constraints.js @@ -4,6 +4,7 @@ import _ from 'lodash'; import Jeditor from '../../components/JSONEditor/JEditor'; import UnitConversion from '../../utils/unit.converter'; import UIConstants from '../../utils/ui.constants'; +import UtilService from '../../services/util.service'; /* eslint-disable react-hooks/exhaustive-deps */ export default (props) => { @@ -11,6 +12,7 @@ export default (props) => { const { parentFunction = (editorFn) => { editorFunction = editorFn;} } = props; const [constraintSchema, setConstraintSchema] = useState(); const [initialValue, setInitialValue] = useState(); + const [systemTime, setSystemTime] = useState(); //SU Constraint Editor Property Order,format and validation const configureProperties = (properties) => { for (const propertyKey in properties) { @@ -69,7 +71,8 @@ export default (props) => { } }; //DateTime flatPicker component enabled with seconds - const setDateTimeOption = (propertyValue) => { + const setDateTimeOption = async(propertyValue) => { + const systemTime = moment.utc((await UtilService.getUTC())); propertyValue.format = 'datetime-local'; propertyValue.validationType = 'dateTime'; propertyValue.skipFormat = true; @@ -83,6 +86,9 @@ export default (props) => { "enableSeconds": true, "time_24hr": true, "allowInput": true, + "defaultDate": systemTime.format(UIConstants.CALENDAR_DEFAULTDATE_FORMAT), + "defaultHour": systemTime.hour(), + "defaultMinute": systemTime.minutes() } }; }; @@ -101,7 +107,7 @@ export default (props) => { } else if(definitionName === 'timewindow') { for (let property in schema.definitions.timewindow.properties) { if(property === 'to' || property === 'from'){ - setDateTimeOption(schema.definitions.timewindow.properties[property]); + // setDateTimeOption(schema.definitions.timewindow.properties[property]); if (property === 'from') { schema.definitions.timewindow.properties[property].propertyOrder = 1; } else { @@ -148,7 +154,9 @@ export default (props) => { } } - const constraintStrategy = () => { + const constraintStrategy = async() => { + const currentSystemTime = moment.utc(await UtilService.getUTC()) + setSystemTime(currentSystemTime); // const constraintTemplate = { ...props.constraintTemplate } const constraintTemplate = _.cloneDeep(props.constraintTemplate); if (constraintTemplate.schema) { @@ -196,6 +204,9 @@ export default (props) => { if (!props.constraintTemplate) { return; } + UtilService.getUTC().then(utcTime => { + setSystemTime(moment.utc(utcTime)); + }); if (props.initValue) { modifyInitiValue(); } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/excelview.schedulingset.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/excelview.schedulingset.js index 161657340ba91b073e152f4afd04f45212a892de..6e8e565a824195df4de6e33736a6715eea03e577 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/excelview.schedulingset.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/excelview.schedulingset.js @@ -1723,12 +1723,13 @@ export class SchedulingSetCreate extends Component { async saveSU() { let newSUCount = 0; let existingSUCount = 0; + let isUpdated = true; try{ this.setState({ // saveDialogVisible: false, confirmDialogVisible: false, showSpinner: true - }) + }); let newSU = this.state.schedulingUnit; let parameters = this.state.schedulingUnitList[0]['requirements_doc'].parameters; @@ -1911,7 +1912,10 @@ export class SchedulingSetCreate extends Component { if(taskdata){ taskDrafts = taskdata.data.results; } - await ScheduleService.updateSUDraftFromObservStrategy(observStrategy, newSU, taskDrafts, this.state.tasksToUpdate, tmpStationGroups); + let updateSu = await ScheduleService.updateSUDraftFromObservStrategy(observStrategy, newSU, taskDrafts, this.state.tasksToUpdate, tmpStationGroups); + if (updateSu && !updateSu.isSUUpdated) { + isUpdated = false; + } existingSUCount++; } else if (suRow.id === 0 && this.isNotEmpty(suRow.suname) && this.isNotEmpty(suRow.sudesc)){ @@ -1921,7 +1925,10 @@ export class SchedulingSetCreate extends Component { scheduling_constraints_template_id: newSU['scheduling_constraints_template_id'], scheduling_set_id: newSU['scheduling_set_id'] } - await ScheduleService.saveSUDraftFromObservStrategy(observStrategy, newSchedulueUnit, newConstraint, tmpStationGroups); + let updateSu = await ScheduleService.saveSUDraftFromObservStrategy(observStrategy, newSchedulueUnit, newConstraint, tmpStationGroups); + if (updateSu && !updateSu.isSUUpdated) { + isUpdated = false; + } newSUCount++; } } @@ -1932,7 +1939,13 @@ export class SchedulingSetCreate extends Component { this.dialogType = "success"; this.dialogHeader = "Success"; this.showIcon = true; - this.dialogMsg = '['+newSUCount+'] Scheduling Units are created & ['+existingSUCount+'] Scheduling Units are updated successfully.'; + if (isUpdated) { + this.dialogMsg = '['+newSUCount+'] Scheduling Units are created & ['+existingSUCount+'] Scheduling Units are updated successfully.'; + } else { + this.dialogHeader = "Warning"; + this.dialogMsg = '['+newSUCount+'] Scheduling Units are created & ['+existingSUCount+'] Scheduling Units are updated successfully, and there are some Schedule Unit/Task failed to create/update'; + } + this.dialogContent = ""; this.onCancel = this.close; this.onClose = this.close; @@ -1944,6 +1957,7 @@ export class SchedulingSetCreate extends Component { } }catch(err){ this.growl.show({severity: 'error', summary: 'Error Occured', detail: 'Unable to create/update Scheduling Units'}); + this.setState({showSpinner: false}); } } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/schedulingset.create.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/schedulingset.create.js index b9273de4b8be558f5f7cd8345c1dc7152c78f017..2b34d370565a6284dde6d7b40b222befe78a564c 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/schedulingset.create.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/schedulingset.create.js @@ -5,6 +5,7 @@ import UIConstants from '../../utils/ui.constants'; import { CustomDialog } from '../../layout/components/CustomDialog'; import ScheduleService from '../../services/schedule.service'; import { Growl } from 'primereact/components/growl/Growl'; +import { appGrowl } from './../../layout/components/AppGrowl'; export class SchedulingSet extends Component { @@ -32,13 +33,13 @@ export class SchedulingSet extends Component { schedulingSet['generator_doc'] = {}; schedulingSet['scheduling_unit_drafts'] = []; const suSet = await ScheduleService.saveSchedulingSet(schedulingSet); - if (suSet.id !== null) { - this.growl.show({severity: 'success', summary: 'Success', detail: 'Scheduling Set is created successfully.'}); + if (suSet.id && suSet.id !== null) { + appGrowl.show({severity: 'success', summary: 'Success', detail: 'Scheduling Set is created successfully.'}); this.setState({suSet: suSet, dialogVisible: true, }); this.props.onCancel(); - } else { + } /* else { this.growl.show({severity: 'error', summary: 'Error Occured', detail: schedulingSet.message || 'Unable to save Scheduling Set'}); - } + } */ } }}, {id:"no", title: 'Cancel', callback: this.props.onCancel} ]; diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/reservation.create.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/reservation.create.js index 4809304616b85fe9a91f3828a8a16f5b29f64dda..e1b884053169d5a05dd9ab001e45af3c7ae0804a 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/reservation.create.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/reservation.create.js @@ -6,6 +6,7 @@ import { Growl } from 'primereact/components/growl/Growl'; import AppLoader from '../../layout/components/AppLoader'; import PageHeader from '../../layout/components/PageHeader'; import UIConstants from '../../utils/ui.constants'; +import Flatpickr from "react-flatpickr"; import { Calendar } from 'primereact/calendar'; import { InputMask } from 'primereact/inputmask'; import { Dropdown } from 'primereact/dropdown'; @@ -18,6 +19,9 @@ import ProjectService from '../../services/project.service'; import ReservationService from '../../services/reservation.service'; import UnitService from '../../utils/unit.converter'; import Jeditor from '../../components/JSONEditor/JEditor'; +import UtilService from '../../services/util.service'; + +import "flatpickr/dist/flatpickr.css"; /** * Component to create a new Reservation @@ -38,7 +42,7 @@ export class ReservationCreate extends Component { reservation: { name: '', description: '', - start_time: '', + start_time: null, duration: '', project: (props.match?props.match.params.project:null) || null, }, @@ -78,9 +82,11 @@ export class ReservationCreate extends Component { async initReservation() { const promises = [ ProjectService.getProjectList(), ReservationService.getReservationTemplates(), + UtilService.getUTC() ]; let emptyProjects = [{url: null, name: "Select Project"}]; Promise.all(promises).then(responses => { + let systemTime = moment.utc(responses[2]); this.projects = emptyProjects.concat(responses[0]); this.reservationTemplates = responses[1]; @@ -95,8 +101,9 @@ export class ReservationCreate extends Component { paramsSchema: schema, isLoading: false, reservationTemplate: reservationTemplate, + systemTime: systemTime }); - }); + }); } @@ -209,7 +216,6 @@ export class ReservationCreate extends Component { } } } - this.setState({errors: errors, validFields: validFields}); if (Object.keys(validFields).length === Object.keys(this.formRules).length) { validForm = true; @@ -232,7 +238,7 @@ export class ReservationCreate extends Component { } } - saveReservation(){ + async saveReservation(){ let reservation = this.state.reservation; let project = this.projects.find(project => project.name === reservation.project); reservation['start_time'] = moment(reservation['start_time']).format(UIConstants.CALENDAR_DATETIME_FORMAT); @@ -240,13 +246,13 @@ export class ReservationCreate extends Component { reservation['project']= project ? project.url: null; reservation['specifications_template']= this.reservationTemplates[0].url; reservation['specifications_doc']= this.paramsOutput; - reservation = ReservationService.saveReservation(reservation); - if (reservation && reservation !== null){ + reservation = await ReservationService.saveReservation(reservation); + if (reservation && reservation.id){ const dialog = {header: 'Success', detail: 'Reservation is created successfully. Do you want to create another Reservation?'}; this.setState({ dialogVisible: true, dialog: dialog, paramsOutput: {}, showDialog: false, isDirty: false}) - } else { + }/* else { this.growl.show({severity: 'error', summary: 'Error Occured', detail: 'Unable to save Reservation', showDialog: false, isDirty: false}); - } + }*/ } /** @@ -356,20 +362,27 @@ export class ReservationCreate extends Component { <div className="p-field p-grid"> <label htmlFor="reservationName" className="col-lg-2 col-md-2 col-sm-12">From Date <span style={{color:'red'}}>*</span></label> <div className="col-lg-3 col-md-3 col-sm-12"> - <Calendar - d dateFormat="yy-mm-dd" - value= {this.state.reservation.start_time} - onChange= {e => this.setParams('start_time',e.value)} - data-testid="start_time" - tooltip="Moment at which the reservation starts from, that is, when its reservation can run." tooltipOptions={this.tooltipOptions} - showIcon={true} - showTime= {true} - showSeconds= {true} - hourFormat= "24" - /> - - <label className={this.state.errors.from?"error":"info"}> - {this.state.errors.start_time ? this.state.errors.start_time : ""} + <Flatpickr data-enable-time data-input options={{ + "inlineHideInput": true, + "wrap": true, + "enableSeconds": true, + "time_24hr": true, + "allowInput": true, + "defaultDate": this.state.systemTime.format(UIConstants.CALENDAR_DEFAULTDATE_FORMAT), + "defaultHour": this.state.systemTime.hours(), + "defaultMinute": this.state.systemTime.minutes() + }} + title="Start of this reservation" + value={this.state.reservation.start_time} + onChange= {value => {this.setParams('start_time', value[0]?value[0]:this.state.reservation.start_time); + this.setReservationParams('start_time', value[0]?value[0]:this.state.reservation.start_time)}} > + <input type="text" data-input className={`p-inputtext p-component ${this.state.errors.start_time && this.state.touched.start_time?'input-error':''}`} /> + <i className="fa fa-calendar" data-toggle style={{position: "absolute", marginLeft: '-25px', marginTop:'5px', cursor: 'pointer'}} ></i> + <i className="fa fa-times" style={{position: "absolute", marginLeft: '-50px', marginTop:'5px', cursor: 'pointer'}} + onClick={e => {this.setParams('start_time', ''); this.setReservationParams('start_time', '')}}></i> + </Flatpickr> + <label className={this.state.errors.start_time && this.state.touched.start_time?"error":"info"}> + {this.state.errors.start_time && this.state.touched.start_time ? this.state.errors.start_time : ""} </label> </div> <div className="col-lg-1 col-md-1 col-sm-12"></div> @@ -380,6 +393,8 @@ export class ReservationCreate extends Component { value={this.state.reservation.duration} mask="99:99:99" placeholder="HH:mm:ss" + tooltip="Duration of this reservation. If it is empty, then this reservation is indefinite." + tooltipOptions={this.tooltipOptions} onChange= {e => this.setParams('duration',e.value)} ref={input =>{this.input = input}} /> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js index 828386d19450af0473e199ad44430a7b4491e8ed..56437953e4ee0ed1bd91c69fda2fd9512bb7f703 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js @@ -845,9 +845,9 @@ export class TimelineView extends Component { if (this.state.redirect) { return <Redirect to={ {pathname: this.state.redirect} }></Redirect> } - if (this.state.loader) { - return <AppLoader /> - } + // if (this.state.loader) { + // return <AppLoader /> + // } const isSUDetsVisible = this.state.isSUDetsVisible; const isTaskDetsVisible = this.state.isTaskDetsVisible; const canExtendSUList = this.state.canExtendSUList; diff --git a/SAS/TMSS/frontend/tmss_webapp/src/services/cycle.service.js b/SAS/TMSS/frontend/tmss_webapp/src/services/cycle.service.js index 095f5b009d342fdc124972bcd8ece09ac4eab7d3..9e110bc09653909d2045fea90ae3b68107cefaa7 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/services/cycle.service.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/cycle.service.js @@ -100,7 +100,7 @@ const CycleService = { return response.data; } catch (error) { console.log(error.response.data); - return error.response.data; + //return error.response.data; } }, deleteCycleQuota: async function(cycleQuota) { diff --git a/SAS/TMSS/frontend/tmss_webapp/src/services/project.service.js b/SAS/TMSS/frontend/tmss_webapp/src/services/project.service.js index 2ad00beed53a2655373cb373486180ab375ef8a1..5613c91deba01e0375f622e7e16700d872c9a494 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/services/project.service.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/project.service.js @@ -68,10 +68,14 @@ const ProjectService = { saveProject: async function(project, projectQuota) { try { const response = await axios.post(('/api/project/'), project); - project = response.data - for (let quota of projectQuota) { + project = response.data; + project['isQuotaCreated'] = true; + for (let quota of projectQuota) { quota.project = project.url; - this.saveProjectQuota(quota); + let response = await this.saveProjectQuota(quota); + if (response.status > 299) { + project['isQuotaCreated'] = false; + } } return response.data; } catch (error) { @@ -83,29 +87,33 @@ const ProjectService = { updateProject: async function(id, project) { try { const response = await axios.put((`/api/project/${id}/`), project); - return response.data; + project = response.data; + project['isUpdated'] = true; + return project; } catch (error) { - // console.log(error); console.log(error.response.data); - return error.response.data; + project = error.response.data; + project['isUpdated'] = false; + return project; } }, saveProjectQuota: async function(projectQuota) { try { const response = await axios.post(('/api/project_quota/'), projectQuota); - return response.data; + return response; } catch (error) { console.error(error); - return null; + return error.response; } }, updateProjectQuota: async function(projectQuota) { + const response = null; try { const response = await axios.put(`/api/project_quota/${projectQuota.id}/`, projectQuota); return response.data; } catch (error) { console.error(error); - return null; + return response; } }, deleteProjectQuota: async function(projectQuota) { diff --git a/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js b/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js index db6284425c56b108d1b5ff8dd08a341e6a78a9ef..eb1d3364f5d71f6eb31b17ba27803daafdd6f057 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js @@ -493,9 +493,10 @@ const ScheduleService = { updateSUDraftFromObservStrategy: async function(observStrategy,schedulingUnit,tasks,tasksToUpdate,station_groups) { try { delete schedulingUnit['duration']; - + schedulingUnit['isSUUpdated'] = false; schedulingUnit = await this.updateSchedulingUnitDraft(schedulingUnit); if (!schedulingUnit.error) { + schedulingUnit['isSUUpdated'] = true; for (const taskToUpdate in tasksToUpdate) { let task = tasks.find(task => { return task.name === taskToUpdate}); task.specifications_doc = observStrategy.template.tasks[taskToUpdate].specifications_doc; @@ -515,6 +516,7 @@ const ScheduleService = { return schedulingUnit; } catch(error) { console.error(error); + schedulingUnit['isSUUpdated'] = false; return { error: true, message: 'Unable to Update Task Drafts' diff --git a/SAS/TMSS/frontend/tmss_webapp/src/utils/ui.constants.js b/SAS/TMSS/frontend/tmss_webapp/src/utils/ui.constants.js index b4ca89eb6f0b31d99092b8d978ffb1e0e9c695f3..b2cdb71562603a663bddc1420395566d4a823afb 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/utils/ui.constants.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/utils/ui.constants.js @@ -3,6 +3,15 @@ const UIConstants = { timeline: { types: { NORMAL: "NORMAL", WEEKVIEW:"WEEKVIEW"} }, + httpStatusMessages: { + 400: {severity: 'error', summary: 'Error', sticky: true, detail: 'Error while process request, please contact system admin'}, + 401: {severity: 'error', summary: 'Error', sticky: true, detail: 'Not authenticated, Please retry to login with valid credential'}, + 403: {severity: 'error', summary: 'Error', sticky: true, detail: 'Unknown request, please contact system admin'}, + 404: {severity: 'error', summary: 'Error', sticky: true, detail: 'URL is not recognized, please contact system admin'}, + 408: {severity: 'error', summary: 'Error', sticky: true, detail: 'Request is taking more time to response, please contact system admin'}, + 500: {severity: 'error', summary: 'Error', sticky: true, detail: 'Internal Server Error, URL may not exists, please contact system admin'}, + 503: {severity: 'error', summary: 'Error', sticky: true, detail: 'Server not available, please check system admin'}, + }, CALENDAR_DATE_FORMAT: 'yy-mm-dd', CALENDAR_DATETIME_FORMAT : 'YYYY-MM-DD HH:mm:ss', CALENDAR_TIME_FORMAT: 'HH:mm:ss',