diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b7cedc3c91918494814c46a19eb98f2165d6ff87..1773ab9edb8fe586f6662bfa4d7d2bd48d2ad83e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -211,8 +211,6 @@ dockerize_TMSS: needs: - job: build_SCU artifacts: true - - job: integration_test_SCU - artifacts: false # # INTEGRATION TEST STAGE @@ -317,6 +315,8 @@ deploy-tmss-ua: - ssh lofarsys@tmss-ua.control.lofar "docker-compose -f docker-compose-ua.yml up -d" needs: - dockerize_TMSS + - job: integration_test_SCU + artifacts: false when: manual only: - "master" @@ -330,6 +330,8 @@ deploy-tmss-dockerhub: - docker logout needs: - dockerize_TMSS + - job: integration_test_SCU + artifacts: false when: manual only: - "master" diff --git a/LCS/PyCommon/json_utils.py b/LCS/PyCommon/json_utils.py index 963e397174ee5943fa038d869af8c78edcaae33e..6a40f670614a047e0107b7c21059e1dce1fb2d99 100644 --- a/LCS/PyCommon/json_utils.py +++ b/LCS/PyCommon/json_utils.py @@ -16,6 +16,8 @@ # with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. import json +import time + import jsonschema from copy import deepcopy import requests @@ -159,19 +161,31 @@ def get_referenced_subschema(ref_url, cache: dict=None, max_cache_age: timedelta '''fetch the schema given by the ref_url, and get the sub-schema given by the #/ path in the ref_url''' # deduct referred schema name and version from ref-value head, anchor, tail = ref_url.partition('#') + + def _fech_url_and_update_cache_entry_if_needed(): + # try to fetch the url a few time (jsonschema.org is down quite often, but only for a brief moment) + for attempt_nr in range(5): + try: + response = requests.get(ref_url) + if response.status_code == 200: + referenced_schema = json.loads(response.text) + if isinstance(cache, dict): + cache[head] = referenced_schema, datetime.utcnow() + return referenced_schema + except requests.exceptions.RequestException as e: + time.sleep(2) # retry after a little sleep + raise Exception("Could not get: %s" % (ref_url,)) + if isinstance(cache, dict) and head in cache: # use cached value referenced_schema, last_update_timestamp = cache[head] # refresh cache if outdated if datetime.utcnow() - last_update_timestamp > max_cache_age: - referenced_schema = json.loads(requests.get(ref_url).text) - cache[head] = referenced_schema, datetime.utcnow() + referenced_schema = _fech_url_and_update_cache_entry_if_needed() else: # fetch url, and store in cache - referenced_schema = json.loads(requests.get(ref_url).text) - if isinstance(cache, dict): - cache[head] = referenced_schema, datetime.utcnow() + referenced_schema = _fech_url_and_update_cache_entry_if_needed() # extract sub-schema tail = tail.strip('/') @@ -222,13 +236,12 @@ def get_refs(schema) -> set: return refs -def validate_json_against_its_schema(json_object: dict): +def validate_json_against_its_schema(json_object: dict, cache: dict=None, max_cache_age: timedelta=DEFAULT_MAX_SCHEMA_CACHE_AGE): '''validate the give json object against its own schema (the URI/URL that its propery $schema points to)''' schema_url = json_object['$schema'] - response = requests.get(schema_url, headers={"Accept":"application/json"}) - if response.status_code == 200: - return validate_json_against_schema(json_object, response.text) - raise jsonschema.exceptions.ValidationError("Could not get schema from '%s'\n%s" % (schema_url, str(response.text))) + schema_object = get_referenced_subschema(schema_url, cache=cache, max_cache_age=max_cache_age) + return validate_json_against_schema(json_object, schema_object) + def validate_json_against_schema(json_string: str, schema: str): '''validate the given json_string against the given schema. @@ -260,13 +273,13 @@ def validate_json_against_schema(json_string: str, schema: str): raise jsonschema.exceptions.ValidationError(str(e)) -def get_default_json_object_for_schema(schema: str) -> dict: +def get_default_json_object_for_schema(schema: str, cache: dict=None, max_cache_age: timedelta=DEFAULT_MAX_SCHEMA_CACHE_AGE) -> dict: """ TMSS wrapper for TMSS 'add_defaults_to_json_object_for_schema' :param schema: :return: json_object with default values of the schema """ - data = add_defaults_to_json_object_for_schema({}, schema) + data = add_defaults_to_json_object_for_schema({}, schema, cache=cache, max_cache_age=max_cache_age) if '$id' in schema: data['$schema'] = schema['$id'] return data diff --git a/LTA/LTACommon/CMakeLists.txt b/LTA/LTACommon/CMakeLists.txt index 590e81909a57a76557c620d7509923ac90238782..fed7330d6f64b4791f34c11cc9d06d317a011080 100644 --- a/LTA/LTACommon/CMakeLists.txt +++ b/LTA/LTACommon/CMakeLists.txt @@ -1,5 +1,5 @@ lofar_package(LTACommon 1.0) -set(etc_files LTA-SIP.xsd) -lofar_add_sysconf_files(${etc_files} DESTINATION lta) +set(share_files LTA-SIP.xsd) +lofar_add_data_files(${share_files} DESTINATION lta) diff --git a/LTA/LTAIngest/LTAIngestServer/LTAIngestAdminServer/lib/ingesttmssadapter.py b/LTA/LTAIngest/LTAIngestServer/LTAIngestAdminServer/lib/ingesttmssadapter.py index 7fd829007bf08bc58122d8ba8b1ad33e8f62c1ff..0e6210ef221fac0d7b2564f0845f0cd41aa3f7bf 100644 --- a/LTA/LTAIngest/LTAIngestServer/LTAIngestAdminServer/lib/ingesttmssadapter.py +++ b/LTA/LTAIngest/LTAIngestServer/LTAIngestAdminServer/lib/ingesttmssadapter.py @@ -27,14 +27,14 @@ from lofar.lta.ingest.server.config import MAX_NR_OF_RETRIES from lofar.sas.tmss.client.tmss_http_rest_client import TMSSsession from lofar.messaging.messagebus import ToBus, DEFAULT_BROKER, DEFAULT_BUSNAME, UsingToBusMixin from lofar.messaging.messages import CommandMessage, EventMessage -from lofar.sas.tmss.client.tmssbuslistener import TMSSBusListener, TMSSEventMessageHandler, TMSS_ALL_EVENTS_FILTER +from lofar.sas.tmss.client.tmssbuslistener import TMSSBusListener, TMSSEventMessageHandler, TMSS_SUBTASK_STATUS_EVENT_PREFIX from lofar.common.datetimeutils import totalSeconds from lofar.common.dbcredentials import DBCredentials from lofar.common.util import waitForInterrupt from threading import Thread import time -from datetime import datetime +from datetime import datetime, timedelta from typing import Union import logging @@ -62,7 +62,25 @@ class IngestEventMessageHandlerForIngestTMSSAdapter(UsingToBusMixin, IngestEvent def onJobStarted(self, job_dict): if self.is_tmss_job(job_dict): - self.tmss_client.set_subtask_status(job_dict['export_id'], 'started') + subtask_id = job_dict['export_id'] + subtask = self.tmss_client.get_subtask(subtask_id) + + if subtask['state_value'] == 'started': + pass # the ingest subtask was already started + else: + # wait until subtask was fully queued (or in error/cancelled) + start_wait_timestamp = datetime.utcnow() + while subtask['state_value'] not in ('queued', 'cancelled', 'error'): + if datetime.utcnow() - start_wait_timestamp > timedelta(seconds=60): + raise TimeoutError("Timeout while waiting for ingest subtask id=%s to get status queued/cancelled/error. Current status is %s" % (subtask_id, subtask['state_value'])) + time.sleep(1) + subtask = self.tmss_client.get_subtask(subtask_id) + + if subtask['state_value'] == 'queued': + # the ingest subtask was fully queued, and this is the first ingest transfer job that started + # so, set the ingest subtask to starting->started + self.tmss_client.set_subtask_status(subtask_id, 'starting') + self.tmss_client.set_subtask_status(subtask_id, 'started') def onJobFailed(self, job_dict): if self.is_tmss_job(job_dict): @@ -150,13 +168,15 @@ class TMSSEventMessageHandlerForIngestTMSSAdapter(UsingToBusMixin, TMSSEventMess self.tmss_client.set_subtask_status(subtask['id'], 'queueing') # gather all relevant and needed info... - task_blueprint = self.tmss_client.get_url_as_json_object(subtask['task_blueprint']) + task_blueprint = self.tmss_client.get_url_as_json_object(subtask['task_blueprints'][0]) task_draft = self.tmss_client.get_url_as_json_object(task_blueprint['draft']) scheduling_unit_draft = self.tmss_client.get_url_as_json_object(task_draft['scheduling_unit_draft']) scheduling_set = self.tmss_client.get_url_as_json_object(scheduling_unit_draft['scheduling_set']) project = self.tmss_client.get_url_as_json_object(scheduling_set['project']) # create an ingest xml job for each input dataproduct + # store the jobs in a list, and submit them in one go to the queue + jobs = [] for input_dp in input_dataproducts: dp_global_identifier = self.tmss_client.get_url_as_json_object(input_dp['global_identifier']) producer = self.tmss_client.get_url_as_json_object(input_dp['producer']) @@ -169,7 +189,10 @@ class TMSSEventMessageHandlerForIngestTMSSAdapter(UsingToBusMixin, TMSSEventMess location=subtask['cluster_name']+':'+os.path.join(input_dp['directory'], input_dp['filename']), tmss_ingest_subtask_id=subtask['id'], tmss_input_dataproduct_id=input_dp['id']) + jobs.append(job) + # submit all jobs to the in one go to ingest-incoming-job-queue + for job in jobs: msg = CommandMessage(content=job, subject=DEFAULT_INGEST_INCOMING_JOB_SUBJECT) logger.info('submitting job %s to exchange %s with subject %s at broker %s', parseJobXml(job)['JobId'], self._tobus.exchange, msg.subject, self._tobus.broker) diff --git a/LTA/LTAIngest/LTAIngestServer/LTAIngestTransferServer/lib/sip.py b/LTA/LTAIngest/LTAIngestServer/LTAIngestTransferServer/lib/sip.py index 7c42d02ca0d8e9ca5522d16c1afe055d40aca316..440792e4546b6e7703dea0046dd35eb0ca1ef2d6 100755 --- a/LTA/LTAIngest/LTAIngestServer/LTAIngestTransferServer/lib/sip.py +++ b/LTA/LTAIngest/LTAIngestServer/LTAIngestTransferServer/lib/sip.py @@ -19,7 +19,7 @@ def validateSIPAgainstSchema(sip, log_prefix=''): start = time.time() lofarrootdir = os.environ.get('LOFARROOT', '/opt/lofar') - sip_xsd_path = os.path.join(lofarrootdir, 'etc', 'lta', 'LTA-SIP.xsd') + sip_xsd_path = os.path.join(lofarrootdir, 'share', 'lta', 'LTA-SIP.xsd') if not os.path.exists(sip_xsd_path): logger.error('Could not find LTA-SIP.xsd at %s', sip_xsd_path) diff --git a/LTA/sip/bin/feedback2sip b/LTA/sip/bin/feedback2sip old mode 100644 new mode 100755 diff --git a/LTA/sip/bin/validatesip b/LTA/sip/bin/validatesip old mode 100644 new mode 100755 diff --git a/LTA/sip/bin/visualizesip b/LTA/sip/bin/visualizesip old mode 100644 new mode 100755 diff --git a/LTA/sip/lib/siplib.py b/LTA/sip/lib/siplib.py index e81b00ed5576eaf33f567f4a9394e609d9e284c5..71b7c184c5004408cb40ca7e3ec4b69b7e4da9c4 100644 --- a/LTA/sip/lib/siplib.py +++ b/LTA/sip/lib/siplib.py @@ -1488,11 +1488,16 @@ class Sip(object): raise Exception("This SIP does not describe a correlated dataproduct. No subarray pointing available.") # this will also validate the document so far - def get_prettyxml(self): + def get_prettyxml(self, schema_location:str = None): try: dom = self.__sip.toDOM() dom.documentElement.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance") - dom.documentElement.setAttribute('xsi:schemaLocation', "http://www.astron.nl/SIP-Lofar LTA-SIP-2.7.0.xsd") + if schema_location is None: + # this is/was the default schema location, even though we never hosted the xsd at the astron server + # That makes xmllint fail to validate (because the schema obviously can't be found) + schema_location = "http://www.astron.nl/SIP-Lofar LTA-SIP-2.7.2.xsd" + dom.documentElement.setAttribute('xsi:schemaLocation', schema_location) + dom.documentElement.setAttribute('xmlns:sip', schema_location.split(' ')[0]) return dom.toprettyxml() except pyxb.ValidationError as err: logger.error(err.details()) diff --git a/LTA/sip/lib/validator.py b/LTA/sip/lib/validator.py index 508c2beee330b5c7a32226031eb9d95beef2c2d3..e0de12d44e2a71d200607a2d44264f082e619105 100644 --- a/LTA/sip/lib/validator.py +++ b/LTA/sip/lib/validator.py @@ -8,7 +8,7 @@ logger = logging.getLogger(__name__) d = os.path.dirname(os.path.realpath(__file__)) XSDPATH = d+"/LTA-SIP.xsd" -DEFAULT_SIP_XSD_PATH = os.path.join(os.environ.get('LOFARROOT', '/opt/lofar'), 'etc', 'lta', 'LTA-SIP.xsd') +DEFAULT_SIP_XSD_PATH = os.path.join(os.environ.get('LOFARROOT', '/opt/lofar'), 'share', 'lta', 'LTA-SIP.xsd') def validate(xmlpath, xsdpath=DEFAULT_SIP_XSD_PATH): '''validates given xml file against given xsd file''' diff --git a/MAC/APL/MainCU/src/MACScheduler/MACScheduler.cc b/MAC/APL/MainCU/src/MACScheduler/MACScheduler.cc index 070672448124bd3697786a99704e1dd4ca1c3ec9..d1a1564f304e17f25a3d742aa955a1fdfdd9ad48 100644 --- a/MAC/APL/MainCU/src/MACScheduler/MACScheduler.cc +++ b/MAC/APL/MainCU/src/MACScheduler/MACScheduler.cc @@ -577,6 +577,9 @@ GCFEvent::TResult MACScheduler::active_state(GCFEvent& event, GCFPortInterface& tm.setTreeState(theObs->second, tsc.get("queued")); #endif } else { + // TODO: set to queueing state the moment MAC knows about this upoming obs. + // I've tried that but it's realy hard to keep the MAC internal bookkeeping and TMSS in sync. + itsTMSSconnection->setSubtaskState(theObs->second, "queueing"); itsTMSSconnection->setSubtaskState(theObs->second, "queued"); } break; @@ -598,6 +601,7 @@ GCFEvent::TResult MACScheduler::active_state(GCFEvent& event, GCFPortInterface& #endif } else { + itsTMSSconnection->setSubtaskState(theObs->second, "starting"); itsTMSSconnection->setSubtaskState(theObs->second, "started"); } break; @@ -922,8 +926,6 @@ void MACScheduler::_updatePlannedList() OLiter prepIter = itsPreparedObs.find(subtask_id); if ((prepIter == itsPreparedObs.end()) || (prepIter->second.prepReady == false) || (prepIter->second.modTime != modTime)) { - itsTMSSconnection->setSubtaskState(subtask_id, "queueing"); - // create a ParameterFile for this Observation string parsetText = itsTMSSconnection->getParsetAsText(subtask_id); if(prepIter == itsPreparedObs.end()) { diff --git a/MAC/APL/MainCU/src/MACScheduler/TMSSBridge.cc b/MAC/APL/MainCU/src/MACScheduler/TMSSBridge.cc index 5a9bc3f4bea1cadc352584deeb3ff09fba52e036..2fd54053b9b58da8c84a19bf981eb8c7b6e27d50 100644 --- a/MAC/APL/MainCU/src/MACScheduler/TMSSBridge.cc +++ b/MAC/APL/MainCU/src/MACScheduler/TMSSBridge.cc @@ -236,6 +236,7 @@ bool TMSSBridge::httpQuery(const string& target, string &result, const string& q curl_global_cleanup(); LOG_INFO_STR(string("[") << query_method << "] code=" << httpCode << " " << url); + if (httpCode == 200) { return true; } 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 342b727554e0c3a5ca3212ab4008f8ecd116e752..d02f9f43afab5beeb76cb1a505c2410401d3c588 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 @@ -83,7 +83,7 @@ def can_run_within_timewindow_with_daily_constraints(scheduling_unit: models.Sch Checks whether it is possible to run the scheduling unit /somewhere/ in the given time window, considering the duration of the involved observation. :return: True if there is at least one possibility to place the scheduling unit in a way that all daily constraints are met over the runtime of the observation, else False. """ - main_observation_task_name = get_target_observation_task_name_from_requirements_doc(scheduling_unit) + main_observation_task_name = get_longest_observation_task_name_from_requirements_doc(scheduling_unit) duration = timedelta(seconds=scheduling_unit.requirements_doc['tasks'][main_observation_task_name]['specifications_doc']['duration']) window_lower_bound = lower_bound while window_lower_bound + duration < upper_bound: @@ -100,7 +100,7 @@ def can_run_anywhere_within_timewindow_with_daily_constraints(scheduling_unit: m Checks whether it is possible to place the scheduling unit arbitrarily in the given time window, i.e. the daily constraints must be met over the full time window. :return: True if all daily constraints are met over the entire time window, else False. """ - main_observation_task_name = get_target_observation_task_name_from_requirements_doc(scheduling_unit) + main_observation_task_name = get_longest_observation_task_name_from_requirements_doc(scheduling_unit) constraints = scheduling_unit.scheduling_constraints_doc if constraints['daily']['require_day'] or constraints['daily']['require_night'] or constraints['daily']['avoid_twilight']: @@ -157,7 +157,7 @@ def can_run_within_timewindow_with_time_constraints(scheduling_unit: models.Sche :return: True if there is at least one possibility to place the scheduling unit in a way that all time constraints are met over the runtime of the observation, else False. """ - main_observation_task_name = get_target_observation_task_name_from_requirements_doc(scheduling_unit) + main_observation_task_name = get_longest_observation_task_name_from_requirements_doc(scheduling_unit) constraints = scheduling_unit.scheduling_constraints_doc # Check the 'at' constraint and then only check can_run_anywhere for the single possible time window @@ -251,9 +251,6 @@ def can_run_anywhere_within_timewindow_with_sky_constraints(scheduling_unit: mod Checks whether it is possible to place the scheduling unit arbitrarily in the given time window, i.e. the sky constraints must be met over the full time window. :return: True if all sky constraints are met over the entire time window, else False. """ - # TODO: remove this shortcut after demo - return True - constraints = scheduling_unit.scheduling_constraints_doc if not "sky" in constraints: return True @@ -385,10 +382,26 @@ def get_target_observation_task_name_from_requirements_doc(scheduling_unit: mode raise TMSSException("Cannot find target observation in scheduling_unit requirements_doc") +def get_longest_observation_task_name_from_requirements_doc(scheduling_unit: models.SchedulingUnitBlueprint) -> str: + longest_observation_task_name = None + longest_observation_duration = 0 + for task_name, task in scheduling_unit.requirements_doc['tasks'].items(): + if 'observation' in task.get('specifications_template', ''): + if 'duration' in task.get('specifications_doc', {}): + duration = task['specifications_doc']['duration'] + if duration > longest_observation_duration: + longest_observation_duration = duration + longest_observation_task_name = task_name + if longest_observation_task_name is not None: + return longest_observation_task_name + raise TMSSException("Cannot find a longest observation in scheduling_unit requirements_doc") + + def get_earliest_possible_start_time(scheduling_unit: models.SchedulingUnitBlueprint, lower_bound: datetime) -> datetime: constraints = scheduling_unit.scheduling_constraints_doc - main_observation_task_name = get_target_observation_task_name_from_requirements_doc(scheduling_unit) + # TODO: for estimating the earliest_possible_start_time, we need the full duration of the scheduling unit, not just the longest one. + main_observation_task_name = get_longest_observation_task_name_from_requirements_doc(scheduling_unit) duration = timedelta(seconds=scheduling_unit.requirements_doc['tasks'][main_observation_task_name]['specifications_doc']['duration']) try: if 'at' in constraints['time']: 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 59e644b4a882c4add00baa7b495fb95f41a524df..002d6ed94053dcc868c3c1f93bb508ca589e2d65 100755 --- a/SAS/TMSS/backend/services/scheduling/test/t_dynamic_scheduling.py +++ b/SAS/TMSS/backend/services/scheduling/test/t_dynamic_scheduling.py @@ -26,9 +26,6 @@ from astropy.coordinates import Angle import logging logger = logging.getLogger(__name__) -#TODO: remove after demo -exit(3) - from lofar.common.test_utils import skip_integration_tests if skip_integration_tests(): exit(3) @@ -113,8 +110,9 @@ class TestDynamicScheduling(TestCase): # Note: we use django.test.TestCase inst for input in subtask.inputs.all(): input.delete() subtask.delete() - task_blueprint.draft.delete() + draft = task_blueprint.draft task_blueprint.delete() + draft.delete() scheduling_unit_blueprint.delete() scheduling_unit_draft.delete() @@ -348,6 +346,34 @@ class TestDynamicScheduling(TestCase): # Note: we use django.test.TestCase inst self.assertGreaterEqual(scheduling_unit_blueprint_high.start_time - scheduling_unit_blueprint_manual.stop_time, DEFAULT_INTER_OBSERVATION_GAP) + def test_can_schedule_all_observing_strategy_templates_with_default_constraints(self): + '''''' + constraints_template = models.SchedulingConstraintsTemplate.objects.get(name="constraints") + constraints = get_default_json_object_for_schema(constraints_template.schema) + + for strategy_template in models.SchedulingUnitObservingStrategyTemplate.objects.all(): + scheduling_unit_spec = add_defaults_to_json_object_for_schema(strategy_template.template, + strategy_template.scheduling_unit_template.schema) + + draft = models.SchedulingUnitDraft.objects.create(name=strategy_template.name, + scheduling_set=self.scheduling_set_high, + requirements_template=strategy_template.scheduling_unit_template, + requirements_doc=scheduling_unit_spec, + observation_strategy_template=strategy_template, + scheduling_constraints_doc=constraints, + scheduling_constraints_template=constraints_template) + blueprint = create_task_blueprints_and_subtasks_from_scheduling_unit_draft(draft) + + # call the method-under-test: do_dynamic_schedule + # this test only checks if each strategy_template *can* be scheduled by the dynamic scheduler without any exceptions/errors. The defaults should just work. + scheduled_scheduling_unit = do_dynamic_schedule() + self.assertEqual(blueprint.id, scheduled_scheduling_unit.id) + self.assertEqual("scheduled", scheduled_scheduling_unit.status) + + # wipe all entries in tmss-db/radb, and try next strategy_template + self.setUp() + + class TestDailyConstraints(TestCase): ''' Tests for the constraint checkers used in dynamic scheduling @@ -863,14 +889,14 @@ class TestSkyConstraints(unittest.TestCase): # case 1: transits at 14h, obs middle is at 13h, so we have an offset of -3600 seconds # big window - self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky'] = {'transit_offset': {'from': -43200, 'to': 43200}} # todo: use blueprint contraints after TMSS-697 was merged + self.scheduling_unit_blueprint.scheduling_constraints_doc['sky'] = {'transit_offset': {'from': -43200, 'to': 43200}} self.scheduling_unit_blueprint.save() timestamp = datetime(2020, 1, 1, 12, 0, 0) 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) # narrow window - self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky'] = {'transit_offset': {'from': -3601, 'to': -3599}} # todo: use blueprint contraints after TMSS-697 was merged + self.scheduling_unit_blueprint.scheduling_constraints_doc['sky'] = {'transit_offset': {'from': -3601, 'to': -3599}} self.scheduling_unit_blueprint.save() timestamp = datetime(2020, 1, 1, 12, 0, 0) returned_value = tc1.can_run_anywhere_within_timewindow_with_sky_constraints(self.scheduling_unit_blueprint, timestamp, timestamp + timedelta(seconds=self.obs_duration)) @@ -880,7 +906,7 @@ class TestSkyConstraints(unittest.TestCase): # window spans past 12h, so reference transit is not nearest transit to obs time self.target_transit_mock.return_value = self.target_transit_data_previous - self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky'] = {'transit_offset': {'from': -43300, 'to': -43100}} # todo: use blueprint contraints after TMSS-697 was merged + self.scheduling_unit_blueprint.scheduling_constraints_doc['sky'] = {'transit_offset': {'from': -43300, 'to': -43100}} self.scheduling_unit_blueprint.save() timestamp = datetime(2020, 1, 1, 1, 0, 0) returned_value = tc1.can_run_anywhere_within_timewindow_with_sky_constraints(self.scheduling_unit_blueprint, timestamp, timestamp + timedelta(seconds=self.obs_duration)) @@ -891,14 +917,14 @@ class TestSkyConstraints(unittest.TestCase): # transits at 14h, obs middle is at 13h, so we have an offset of -3600 seconds # window after - self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky'] = {'transit_offset': {'from': -3599, 'to': 43200}} # todo: use blueprint contraints after TMSS-697 was merged + self.scheduling_unit_blueprint.scheduling_constraints_doc['sky'] = {'transit_offset': {'from': -3599, 'to': 43200}} self.scheduling_unit_blueprint.save() timestamp = datetime(2020, 1, 1, 12, 0, 0) 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) # window before - self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky'] = {'transit_offset': {'from': -43200, 'to': -3601}} # todo: use blueprint contraints after TMSS-697 was merged + self.scheduling_unit_blueprint.scheduling_constraints_doc['sky'] = {'transit_offset': {'from': -43200, 'to': -3601}} self.scheduling_unit_blueprint.save() timestamp = datetime(2020, 1, 1, 12, 0, 0) returned_value = tc1.can_run_anywhere_within_timewindow_with_sky_constraints(self.scheduling_unit_blueprint, timestamp, timestamp + timedelta(seconds=self.obs_duration)) @@ -910,7 +936,7 @@ class TestSkyConstraints(unittest.TestCase): # obs middle is 13h, so we have an offset of -7200 seconds self.target_transit_mock.side_effect = self.target_transit_data_saps - self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky'] = {'transit_offset': {'from': -7201, 'to': -7199}} # todo: use blueprint contraints after TMSS-697 was merged + self.scheduling_unit_blueprint.scheduling_constraints_doc['sky'] = {'transit_offset': {'from': -7201, 'to': -7199}} self.scheduling_unit_blueprint.requirements_doc['tasks']['Observation']['specifications_doc']['antenna_set'] = 'LBA_INNER' self.scheduling_unit_blueprint.requirements_doc['tasks']['Observation']['specifications_doc']['SAPs'] = \ [{'name': 'CygA', 'target': 'CygA', 'subbands': [0, 1], 'digital_pointing': {'angle1': 5.233660650313663, 'angle2': 0.7109404782526458, 'direction_type': 'J2000'}}, diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/models/common.py b/SAS/TMSS/backend/src/tmss/tmssapp/models/common.py index 4eeeb68e1a42963aeabbd1111c7dcd509f0eb781..b13765d5a07282c1e9b24e71c1f3a1946cd7dd02 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/models/common.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/models/common.py @@ -203,7 +203,7 @@ class TemplateSchemaMixin(): # add defaults for missing properies, and validate on the fly # use the class's _schema_cache - document = add_defaults_to_json_object_for_schema(document, template.schema, self._schema_cache) + document = add_defaults_to_json_object_for_schema(document, template.schema, cache=self._schema_cache, max_cache_age=self._MAX_SCHEMA_CACHE_AGE) # update the model instance with the updated and validated document setattr(self, document_attr, document) diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py b/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py index ada071a865bdf4f336164fa504e62eb9f7083a87..25326994619527ce4e4278fa35ce9c04448fb1b2 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py @@ -17,6 +17,7 @@ from django.core.exceptions import ValidationError import datetime from collections import Counter from django.utils.functional import cached_property +from pprint import pformat from lofar.sas.tmss.tmss.exceptions import TMSSException # @@ -219,7 +220,13 @@ class SchedulingUnitObservingStrategyTemplate(NamedCommon): def save(self, force_insert=False, force_update=False, using=None, update_fields=None): if self.template and self.scheduling_unit_template_id and self.scheduling_unit_template.schema: - validate_json_against_schema(self.template, self.scheduling_unit_template.schema) + try: + validate_json_against_schema(self.template, self.scheduling_unit_template.schema) + except Exception as e: + # log the error for debugging and re-raise + logger.error("Error while validating SchedulingUnitObservingStrategyTemplate name='%s' id='%s' error: %s\ntemplate:\n%s", + self.name, self.id, e, pformat(self.template)) + raise super().save(force_insert, force_update, using, update_fields) diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/schemas/LoTSS-observation-scheduling-unit-observation-strategy.json b/SAS/TMSS/backend/src/tmss/tmssapp/schemas/LoTSS-observation-scheduling-unit-observation-strategy.json index ce930faebf11e9d798bfa64809f06f067e4aeefe..674c49680e4caa76246e00893a8ed0f946c729f9 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/schemas/LoTSS-observation-scheduling-unit-observation-strategy.json +++ b/SAS/TMSS/backend/src/tmss/tmssapp/schemas/LoTSS-observation-scheduling-unit-observation-strategy.json @@ -331,8 +331,8 @@ 447 ], "digital_pointing":{ - "angle1":0.24, - "angle2":0.25, + "angle1": 0.6624317181687094, + "angle2": 1.5579526427549426, "direction_type":"J2000" } }, @@ -584,8 +584,8 @@ 447 ], "digital_pointing":{ - "angle1":0.27, - "angle2":0.28, + "angle1": 0.6624317181687094, + "angle2": 1.5579526427549426, "direction_type":"J2000" } } @@ -593,8 +593,8 @@ "filter":"HBA_110_190", "duration":28800, "tile_beam":{ - "angle1":0.42, - "angle2":0.43, + "angle1": 0.6624317181687094, + "angle2": 1.5579526427549426, "direction_type":"J2000" }, "correlator":{ @@ -737,8 +737,8 @@ "name":"calibrator1", "duration":600, "pointing":{ - "angle1":0, - "angle2":0, + "angle1": 0.6624317181687094, + "angle2": 1.5579526427549426, "direction_type":"J2000" }, "autoselect":false @@ -754,8 +754,8 @@ "name":"calibrator2", "duration":600, "pointing":{ - "angle1":0, - "angle2":0, + "angle1": 0.6624317181687094, + "angle2": 1.5579526427549426, "direction_type":"J2000" }, "autoselect":false 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 9a7a4fe7b836db4579a9111af512f2d31b6e4a9c..c8cf099bb1f48f17fef7067087e7a7de7cb27271 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 @@ -8,8 +8,8 @@ "autoselect": false, "pointing": { "direction_type": "J2000", - "angle1": 0, - "angle2": 0 + "angle1": 0.6624317181687094, + "angle2": 1.5579526427549426 }, "name": "calibrator1" }, @@ -78,16 +78,16 @@ ], "tile_beam": { "direction_type": "J2000", - "angle1": 0.42, - "angle2": 0.43 + "angle1": 0.6624317181687094, + "angle2": 1.5579526427549426 }, "SAPs": [ { "name": "target1", "digital_pointing": { "direction_type": "J2000", - "angle1": 0.24, - "angle2": 0.25 + "angle1": 0.6624317181687094, + "angle2": 1.5579526427549426 }, "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] }, @@ -95,8 +95,8 @@ "name": "target2", "digital_pointing": { "direction_type": "J2000", - "angle1": 0.27, - "angle2": 0.28 + "angle1": 0.6624317181687094, + "angle2": 1.5579526427549426 }, "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] } @@ -158,8 +158,8 @@ "autoselect": false, "pointing": { "direction_type": "J2000", - "angle1": 0, - "angle2": 0 + "angle1": 0.6624317181687094, + "angle2": 1.5579526427549426 }, "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 75e850155bd192c799fc8e659516ac23c9ee2f2d..daaf144d92f22def7252cd2c259dcce965cebb26 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 @@ -35,13 +35,13 @@ "type": "number", "title": "Angle 1", "description": "First angle (e.g. RA)", - "default": 0 + "default": 0.6624317181687094 }, "angle2": { "type": "number", "title": "Angle 2", "description": "Second angle (e.g. DEC)", - "default": 0 + "default": 1.5579526427549426 } }, "required": [ diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/schemas/common_schema_template-stations-1.json b/SAS/TMSS/backend/src/tmss/tmssapp/schemas/common_schema_template-stations-1.json index e3afa001749c54992e3de0cc6938a24ac4ed2867..2fb3614642699975018bf09db55d6c2ce5595dab 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/schemas/common_schema_template-stations-1.json +++ b/SAS/TMSS/backend/src/tmss/tmssapp/schemas/common_schema_template-stations-1.json @@ -80,13 +80,17 @@ "type":"integer", "title":"Maximum number of stations to omit", "description":"Maximum number of stations that can be omitted from a group (due to maintenance for example)", - "minimum":0 + "minimum":0, + "default": 0 }, "station_group":{ "type":"object", "title": "Station group", "description": "A set of predefined list of stations, and a constraint on how many stations are allowed to be missing (due to maintenance for example)", - "default":{}, + "default":{ + "stations": ["CS002", "CS003", "CS004", "CS005", "CS006", "CS007"], + "max_nr_missing": 1 + }, "anyOf": [ { "title":"Superterp", @@ -95,17 +99,18 @@ "properties":{ "stations":{ "$ref": "http://tmss.lofar.org/api/schemas/commonschematemplate/stations/1#/definitions/station_list", - "enum": [["CS002", "CS003", "CS004", "CS005", "CS006", "CS007"]], - "default": ["CS002", "CS003", "CS004", "CS005", "CS006", "CS007"], - "uniqueItems": false + "enum": [["CS002", "CS003", "CS004", "CS005", "CS006", "CS007"]] }, "max_nr_missing":{ - "$ref": "http://tmss.lofar.org/api/schemas/commonschematemplate/stations/1#/definitions/max_number_of_missing_stations", - "default": 0 + "$ref": "http://tmss.lofar.org/api/schemas/commonschematemplate/stations/1#/definitions/max_number_of_missing_stations" } }, "required": ["stations", "max_nr_missing"], - "additionalProperties": false + "additionalProperties": false, + "default":{ + "stations": ["CS002", "CS003", "CS004", "CS005", "CS006", "CS007"], + "max_nr_missing": 0 + } }, { "title":"Core", @@ -114,17 +119,18 @@ "properties":{ "stations":{ "$ref": "http://tmss.lofar.org/api/schemas/commonschematemplate/stations/1#/definitions/station_list", - "enum": [["CS001", "CS002", "CS003", "CS004", "CS005", "CS006", "CS007", "CS011", "CS013", "CS017", "CS021", "CS024", "CS026", "CS028", "CS030", "CS031", "CS032", "CS103", "CS201", "CS301", "CS302", "CS401", "CS501"]], - "default": ["CS001", "CS002", "CS003", "CS004", "CS005", "CS006", "CS007", "CS011", "CS013", "CS017", "CS021", "CS024", "CS026", "CS028", "CS030", "CS031", "CS032", "CS103", "CS201", "CS301", "CS302", "CS401", "CS501"], - "uniqueItems": false + "enum": [["CS001", "CS002", "CS003", "CS004", "CS005", "CS006", "CS007", "CS011", "CS013", "CS017", "CS021", "CS024", "CS026", "CS028", "CS030", "CS031", "CS032", "CS103", "CS201", "CS301", "CS302", "CS401", "CS501"]] }, "max_nr_missing":{ - "$ref": "http://tmss.lofar.org/api/schemas/commonschematemplate/stations/1#/definitions/max_number_of_missing_stations", - "default": 4 + "$ref": "http://tmss.lofar.org/api/schemas/commonschematemplate/stations/1#/definitions/max_number_of_missing_stations" } }, "required": ["stations", "max_nr_missing"], - "additionalProperties": false + "additionalProperties": false, + "default":{ + "stations": ["CS001", "CS002", "CS003", "CS004", "CS005", "CS006", "CS007", "CS011", "CS013", "CS017", "CS021", "CS024", "CS026", "CS028", "CS030", "CS031", "CS032", "CS103", "CS201", "CS301", "CS302", "CS401", "CS501"], + "max_nr_missing": 4 + } }, { "title":"Remote", @@ -133,17 +139,18 @@ "properties":{ "stations":{ "$ref": "http://tmss.lofar.org/api/schemas/commonschematemplate/stations/1#/definitions/station_list", - "enum": [["RS106", "RS205", "RS208", "RS210", "RS305", "RS306", "RS307", "RS310", "RS406", "RS407", "RS409", "RS503", "RS508", "RS509"]], - "default": ["RS106", "RS205", "RS208", "RS210", "RS305", "RS306", "RS307", "RS310", "RS406", "RS407", "RS409", "RS503", "RS508", "RS509"], - "uniqueItems": false + "enum": [["RS106", "RS205", "RS208", "RS210", "RS305", "RS306", "RS307", "RS310", "RS406", "RS407", "RS409", "RS503", "RS508", "RS509"]] }, "max_nr_missing":{ - "$ref": "http://tmss.lofar.org/api/schemas/commonschematemplate/stations/1#/definitions/max_number_of_missing_stations", - "default": 4 + "$ref": "http://tmss.lofar.org/api/schemas/commonschematemplate/stations/1#/definitions/max_number_of_missing_stations" } }, "required": ["stations", "max_nr_missing"], - "additionalProperties": false + "additionalProperties": false, + "default": { + "stations": ["RS106", "RS205", "RS208", "RS210", "RS305", "RS306", "RS307", "RS310", "RS406", "RS407", "RS409", "RS503", "RS508", "RS509"], + "max_nr_missing": 4 + } }, { "title":"Dutch", @@ -152,17 +159,18 @@ "properties":{ "stations":{ "$ref": "http://tmss.lofar.org/api/schemas/commonschematemplate/stations/1#/definitions/station_list", - "enum": [["CS001", "CS002", "CS003", "CS004", "CS005", "CS006", "CS007", "CS011", "CS013", "CS017", "CS021", "CS024", "CS026", "CS028", "CS030", "CS031", "CS032", "CS103", "CS201", "CS301", "CS302", "CS401", "CS501", "RS106", "RS205", "RS208", "RS210", "RS305", "RS306", "RS307", "RS310", "RS406", "RS407", "RS409", "RS503", "RS508", "RS509"]], - "default": ["CS001", "CS002", "CS003", "CS004", "CS005", "CS006", "CS007", "CS011", "CS013", "CS017", "CS021", "CS024", "CS026", "CS028", "CS030", "CS031", "CS032", "CS103", "CS201", "CS301", "CS302", "CS401", "CS501", "RS106", "RS205", "RS208", "RS210", "RS305", "RS306", "RS307", "RS310", "RS406", "RS407", "RS409", "RS503", "RS508", "RS509"], - "uniqueItems": false + "enum": [["CS001", "CS002", "CS003", "CS004", "CS005", "CS006", "CS007", "CS011", "CS013", "CS017", "CS021", "CS024", "CS026", "CS028", "CS030", "CS031", "CS032", "CS103", "CS201", "CS301", "CS302", "CS401", "CS501", "RS106", "RS205", "RS208", "RS210", "RS305", "RS306", "RS307", "RS310", "RS406", "RS407", "RS409", "RS503", "RS508", "RS509"]] }, "max_nr_missing":{ - "$ref": "http://tmss.lofar.org/api/schemas/commonschematemplate/stations/1#/definitions/max_number_of_missing_stations", - "default": 4 + "$ref": "http://tmss.lofar.org/api/schemas/commonschematemplate/stations/1#/definitions/max_number_of_missing_stations" } }, "required": ["stations", "max_nr_missing"], - "additionalProperties": false + "additionalProperties": false, + "default": { + "stations": ["CS001", "CS002", "CS003", "CS004", "CS005", "CS006", "CS007", "CS011", "CS013", "CS017", "CS021", "CS024", "CS026", "CS028", "CS030", "CS031", "CS032", "CS103", "CS201", "CS301", "CS302", "CS401", "CS501", "RS106", "RS205", "RS208", "RS210", "RS305", "RS306", "RS307", "RS310", "RS406", "RS407", "RS409", "RS503", "RS508", "RS509"], + "max_nr_missing": 4 + } }, { "title":"International", @@ -171,17 +179,18 @@ "properties":{ "stations":{ "$ref": "http://tmss.lofar.org/api/schemas/commonschematemplate/stations/1#/definitions/station_list", - "enum": [["DE601", "DE602", "DE603", "DE604", "DE605", "DE609", "FR606", "SE607", "UK608", "PL610", "PL611", "PL612", "IE613", "LV614"]], - "default": ["DE601", "DE602", "DE603", "DE604", "DE605", "DE609", "FR606", "SE607", "UK608", "PL610", "PL611", "PL612", "IE613", "LV614"], - "uniqueItems": false + "enum": [["DE601", "DE602", "DE603", "DE604", "DE605", "DE609", "FR606", "SE607", "UK608", "PL610", "PL611", "PL612", "IE613", "LV614"]] }, "max_nr_missing":{ - "$ref": "http://tmss.lofar.org/api/schemas/commonschematemplate/stations/1#/definitions/max_number_of_missing_stations", - "default": 2 + "$ref": "http://tmss.lofar.org/api/schemas/commonschematemplate/stations/1#/definitions/max_number_of_missing_stations" } }, "required": ["stations", "max_nr_missing"], - "additionalProperties": false + "additionalProperties": false, + "default": { + "stations": ["DE601", "DE602", "DE603", "DE604", "DE605", "DE609", "FR606", "SE607", "UK608", "PL610", "PL611", "PL612", "IE613", "LV614"], + "max_nr_missing": 2 + } }, { "title":"International required", @@ -190,17 +199,18 @@ "properties":{ "stations":{ "$ref": "http://tmss.lofar.org/api/schemas/commonschematemplate/stations/1#/definitions/station_list", - "enum": [["DE601", "DE605"]], - "default": ["DE601", "DE605"], - "uniqueItems": false + "enum": [["DE601", "DE605"]] }, "max_nr_missing":{ - "$ref": "http://tmss.lofar.org/api/schemas/commonschematemplate/stations/1#/definitions/max_number_of_missing_stations", - "default": 1 + "$ref": "http://tmss.lofar.org/api/schemas/commonschematemplate/stations/1#/definitions/max_number_of_missing_stations" } }, "required": ["stations", "max_nr_missing"], - "additionalProperties": false + "additionalProperties": false, + "default": { + "stations": ["DE601", "DE605"], + "max_nr_missing": 1 + } }, { "title":"All", @@ -209,17 +219,18 @@ "properties":{ "stations":{ "$ref": "http://tmss.lofar.org/api/schemas/commonschematemplate/stations/1#/definitions/station_list", - "enum": [["CS001", "CS002", "CS003", "CS004", "CS005", "CS006", "CS007", "CS011", "CS013", "CS017", "CS021", "CS024", "CS026", "CS028", "CS030", "CS031", "CS032", "CS301", "CS302", "CS401", "CS501", "RS106", "RS205", "RS208", "RS210", "RS305", "RS306", "RS307", "RS310", "RS406", "RS407", "RS409", "RS503", "RS508", "RS509", "DE601", "DE602", "DE603", "DE604", "DE605", "DE609", "FR606", "SE607", "UK608", "PL610", "PL611", "PL612", "IE613", "LV614"]], - "default": ["CS001", "CS002", "CS003", "CS004", "CS005", "CS006", "CS007", "CS011", "CS013", "CS017", "CS021", "CS024", "CS026", "CS028", "CS030", "CS031", "CS032", "CS301", "CS302", "CS401", "CS501", "RS106", "RS205", "RS208", "RS210", "RS305", "RS306", "RS307", "RS310", "RS406", "RS407", "RS409", "RS503", "RS508", "RS509", "DE601", "DE602", "DE603", "DE604", "DE605", "DE609", "FR606", "SE607", "UK608", "PL610", "PL611", "PL612", "IE613", "LV614"], - "uniqueItems": false + "enum": [["CS001", "CS002", "CS003", "CS004", "CS005", "CS006", "CS007", "CS011", "CS013", "CS017", "CS021", "CS024", "CS026", "CS028", "CS030", "CS031", "CS032", "CS301", "CS302", "CS401", "CS501", "RS106", "RS205", "RS208", "RS210", "RS305", "RS306", "RS307", "RS310", "RS406", "RS407", "RS409", "RS503", "RS508", "RS509", "DE601", "DE602", "DE603", "DE604", "DE605", "DE609", "FR606", "SE607", "UK608", "PL610", "PL611", "PL612", "IE613", "LV614"]] }, "max_nr_missing":{ - "$ref": "http://tmss.lofar.org/api/schemas/commonschematemplate/stations/1#/definitions/max_number_of_missing_stations", - "default": 6 + "$ref": "http://tmss.lofar.org/api/schemas/commonschematemplate/stations/1#/definitions/max_number_of_missing_stations" } }, "required": ["stations", "max_nr_missing"], - "additionalProperties": false + "additionalProperties": false, + "default": { + "stations": ["CS001", "CS002", "CS003", "CS004", "CS005", "CS006", "CS007", "CS011", "CS013", "CS017", "CS021", "CS024", "CS026", "CS028", "CS030", "CS031", "CS032", "CS301", "CS302", "CS401", "CS501", "RS106", "RS205", "RS208", "RS210", "RS305", "RS306", "RS307", "RS310", "RS406", "RS407", "RS409", "RS503", "RS508", "RS509", "DE601", "DE602", "DE603", "DE604", "DE605", "DE609", "FR606", "SE607", "UK608", "PL610", "PL611", "PL612", "IE613", "LV614"], + "max_nr_missing": 6 + } }, { "title":"Custom", @@ -227,20 +238,18 @@ "type": "object", "properties":{ "stations":{ - "$ref": "http://tmss.lofar.org/api/schemas/commonschematemplate/stations/1#/definitions/station_list", - "default": ["CS001"], - "minItems": 1, - "additionalItems": false, - "additionalProperties": false, - "uniqueItems": true + "$ref": "http://tmss.lofar.org/api/schemas/commonschematemplate/stations/1#/definitions/station_list" }, "max_nr_missing":{ - "$ref": "http://tmss.lofar.org/api/schemas/commonschematemplate/stations/1#/definitions/max_number_of_missing_stations", - "default": 0 + "$ref": "http://tmss.lofar.org/api/schemas/commonschematemplate/stations/1#/definitions/max_number_of_missing_stations" } }, "required": ["stations", "max_nr_missing"], - "additionalProperties": false + "additionalProperties": false, + "default": { + "stations": ["CS001"], + "max_nr_missing": 0 + } } ] }, 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 6ae834740335d9474e7351d58c3739b1bf154a2f..768804b59e502d0d94257c219b212e232cb7e6b4 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 @@ -25,12 +25,13 @@ "antenna_set": "HBA_DUAL_INNER", "filter": "HBA_110_190", "station_groups": [ { - "stations": ["CS002", "CS003", "CS004", "CS005", "CS006", "CS007"] + "stations": ["CS002", "CS003", "CS004", "CS005", "CS006", "CS007"], + "max_nr_missing": 1 }], "tile_beam": { "direction_type": "J2000", - "angle1": 5.233660650313663, - "angle2": 0.7109404782526458 + "angle1": 0.6624317181687094, + "angle2": 1.5579526427549426 }, "SAPs": [ { @@ -38,8 +39,8 @@ "target": "CygA", "digital_pointing": { "direction_type": "J2000", - "angle1": 5.233660650313663, - "angle2": 0.7109404782526458 + "angle1": 0.6624317181687094, + "angle2": 1.5579526427549426 }, "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 4d56ae8273810ae352ab54fbab2a37c2d2913399..bc6925c79cf44060f7962678a406b58c1609123a 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 @@ -13,8 +13,8 @@ "target": "CygA", "digital_pointing": { "direction_type": "J2000", - "angle1": 5.233660650313663, - "angle2": 0.7109404782526458 + "angle1": 0.6624317181687094, + "angle2": 1.5579526427549426 }, "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 +26,8 @@ ], "tile_beam": { "direction_type": "J2000", - "angle1": 5.233660650313663, - "angle2": 0.7109404782526458 + "angle1": 0.6624317181687094, + "angle2": 1.5579526427549426 }, "beamformers": [ { @@ -38,8 +38,8 @@ "tabs": [{ "pointing": { "direction_type": "J2000", - "angle1": 0, - "angle2": 0 + "angle1": 0.6624317181687094, + "angle2": 1.5579526427549426 }, "relative": true }], 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 4ea17e719fad83f17b9746f474f1761f9682a48f..f598d9956f417f935d7af687ecd0d8ddd17d2a1b 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 @@ -29,8 +29,8 @@ }], "tile_beam": { "direction_type": "J2000", - "angle1": 5.233660650313663, - "angle2": 0.7109404782526458 + "angle1": 0.6624317181687094, + "angle2": 1.5579526427549426 }, "SAPs": [ { @@ -38,8 +38,8 @@ "target": "CygA", "digital_pointing": { "direction_type": "J2000", - "angle1": 5.233660650313663, - "angle2": 0.7109404782526458 + "angle1": 0.6624317181687094, + "angle2": 1.5579526427549426 }, "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/subtasks.py b/SAS/TMSS/backend/src/tmss/tmssapp/subtasks.py index e99dd864d74c15acb51854aa8f145c3a96bf9ea7..116fdbe86a22fa90e330fe30b53776c34ab343c2 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/subtasks.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/subtasks.py @@ -33,6 +33,9 @@ from lofar.sas.tmss.tmss.tmssapp.conversions import antennafields_for_antennaset from lofar.sas.tmss.tmss.exceptions import TMSSException from django.db import transaction +# cache for json schema's +_schema_cache = {} + # ==== various create* methods to convert/create a TaskBlueprint into one or more Subtasks ==== def check_prerequities_for_subtask_creation(task_blueprint: TaskBlueprint) -> bool: @@ -155,7 +158,7 @@ def create_observation_subtask_specifications_from_observation_task_blueprint(ta # start with an observation subtask specification with all the defaults and the right structure according to the schema subtask_template = SubtaskTemplate.objects.get(name='observation control') - subtask_spec = get_default_json_object_for_schema(subtask_template.schema) + subtask_spec = get_default_json_object_for_schema(subtask_template.schema, cache=_schema_cache) # wipe the default pointings, these should come from the task_spec subtask_spec['stations'].pop('analog_pointing', None) @@ -534,7 +537,7 @@ def create_qafile_subtask_from_observation_subtask(observation_subtask: Subtask) # step 1: create subtask in defining state, with filled-in subtask_template qafile_subtask_template = SubtaskTemplate.objects.get(name="QA file conversion") - qafile_subtask_spec = add_defaults_to_json_object_for_schema({}, qafile_subtask_template.schema) + qafile_subtask_spec = add_defaults_to_json_object_for_schema({}, qafile_subtask_template.schema, cache=_schema_cache) qafile_subtask_spec['nr_of_subbands'] = obs_task_qafile_spec.get("nr_of_subbands") qafile_subtask_spec['nr_of_timestamps'] = obs_task_qafile_spec.get("nr_of_timestamps") validate_json_against_schema(qafile_subtask_spec, qafile_subtask_template.schema) @@ -551,7 +554,7 @@ def create_qafile_subtask_from_observation_subtask(observation_subtask: Subtask) # step 2: create and link subtask input/output selection_template = TaskRelationSelectionTemplate.objects.get(name="all") - selection_doc = get_default_json_object_for_schema(selection_template.schema) + selection_doc = get_default_json_object_for_schema(selection_template.schema, cache=_schema_cache) for obs_out in observation_subtask.outputs.all(): qafile_subtask_input = SubtaskInput.objects.create(subtask=qafile_subtask, @@ -615,7 +618,7 @@ def create_qaplots_subtask_from_qafile_subtask(qafile_subtask: Subtask) -> Subta # step 1: create subtask in defining state, with filled-in subtask_template qaplots_subtask_template = SubtaskTemplate.objects.get(name="QA plots") - qaplots_subtask_spec_doc = add_defaults_to_json_object_for_schema({}, qaplots_subtask_template.schema) + qaplots_subtask_spec_doc = add_defaults_to_json_object_for_schema({}, qaplots_subtask_template.schema, cache=_schema_cache) qaplots_subtask_spec_doc['autocorrelation'] = obs_task_qaplots_spec.get("autocorrelation") qaplots_subtask_spec_doc['crosscorrelation'] = obs_task_qaplots_spec.get("crosscorrelation") validate_json_against_schema(qaplots_subtask_spec_doc, qaplots_subtask_template.schema) @@ -632,7 +635,7 @@ def create_qaplots_subtask_from_qafile_subtask(qafile_subtask: Subtask) -> Subta # step 2: create and link subtask input/output selection_template = TaskRelationSelectionTemplate.objects.get(name="all") - selection_doc = get_default_json_object_for_schema(selection_template.schema) + selection_doc = get_default_json_object_for_schema(selection_template.schema, cache=_schema_cache) qaplots_subtask_input = SubtaskInput.objects.create(subtask=qaplots_subtask, producer=qafile_subtask.outputs.first(), selection_doc=selection_doc, @@ -667,8 +670,8 @@ def create_pipeline_subtask_from_task_blueprint(task_blueprint: TaskBlueprint, s # step 1: create subtask in defining state, with filled-in subtask_template subtask_template = SubtaskTemplate.objects.get(name=subtask_template_name) - default_subtask_specs = get_default_json_object_for_schema(subtask_template.schema) - task_specs_with_defaults = add_defaults_to_json_object_for_schema(task_blueprint.specifications_doc, task_blueprint.specifications_template.schema) + default_subtask_specs = get_default_json_object_for_schema(subtask_template.schema, cache=_schema_cache) + task_specs_with_defaults = add_defaults_to_json_object_for_schema(task_blueprint.specifications_doc, task_blueprint.specifications_template.schema, cache=_schema_cache) subtask_specs = generate_subtask_specs_from_task_spec_func(task_specs_with_defaults, default_subtask_specs) cluster_name = task_blueprint.specifications_doc.get("storage_cluster", "CEP4") @@ -723,7 +726,7 @@ def create_ingest_subtask_from_task_blueprint(task_blueprint: TaskBlueprint) -> # step 1: create subtask in defining state, with filled-in subtask_template subtask_template = SubtaskTemplate.objects.get(name='ingest control') - default_subtask_specs = get_default_json_object_for_schema(subtask_template.schema) + default_subtask_specs = get_default_json_object_for_schema(subtask_template.schema, cache=_schema_cache) subtask_specs = default_subtask_specs # todo: translate specs from task to subtask once we have non-empty templates cluster_name = task_blueprint.specifications_doc.get("storage_cluster", "CEP4") subtask_data = {"start_time": None, @@ -766,7 +769,7 @@ def create_cleanup_subtask_from_task_blueprint(task_blueprint: TaskBlueprint) -> # step 1: create subtask in defining state, with filled-in subtask_template subtask_template = SubtaskTemplate.objects.get(name='cleanup') - subtask_specs = get_default_json_object_for_schema(subtask_template.schema) + subtask_specs = get_default_json_object_for_schema(subtask_template.schema, cache=_schema_cache) cluster_name = task_blueprint.specifications_doc.get("storage_cluster", "CEP4") subtask_data = {"start_time": None, "stop_time": None, @@ -1170,9 +1173,9 @@ def schedule_qafile_subtask(qafile_subtask: Subtask): dataformat=Dataformat.objects.get(value=Dataformat.Choices.QA_HDF5.value), datatype=Datatype.objects.get(value=Datatype.Choices.QUALITY.value), # todo: is this correct? producer=qafile_subtask.outputs.first(), - specifications_doc=get_default_json_object_for_schema(DataproductSpecificationsTemplate.objects.get(name="empty").schema), + specifications_doc=get_default_json_object_for_schema(DataproductSpecificationsTemplate.objects.get(name="empty").schema, cache=_schema_cache), specifications_template=DataproductSpecificationsTemplate.objects.get(name="empty"), - feedback_doc=get_default_json_object_for_schema(DataproductFeedbackTemplate.objects.get(name="empty").schema), + feedback_doc=get_default_json_object_for_schema(DataproductFeedbackTemplate.objects.get(name="empty").schema, cache=_schema_cache), feedback_template=DataproductFeedbackTemplate.objects.get(name="empty"), sap=None # todo: do we need to point to a SAP here? Of which dataproduct then? ) @@ -1223,9 +1226,9 @@ def schedule_qaplots_subtask(qaplots_subtask: Subtask): dataformat=Dataformat.objects.get(value=Dataformat.Choices.QA_PLOTS.value), datatype=Datatype.objects.get(value=Datatype.Choices.QUALITY.value), # todo: is this correct? producer=qaplots_subtask.outputs.first(), - specifications_doc=get_default_json_object_for_schema(DataproductSpecificationsTemplate.objects.get(name="empty").schema), + specifications_doc=get_default_json_object_for_schema(DataproductSpecificationsTemplate.objects.get(name="empty").schema, cache=_schema_cache), specifications_template=DataproductSpecificationsTemplate.objects.get(name="empty"), - feedback_doc=get_default_json_object_for_schema(DataproductFeedbackTemplate.objects.get(name="empty").schema), + feedback_doc=get_default_json_object_for_schema(DataproductFeedbackTemplate.objects.get(name="empty").schema, cache=_schema_cache), feedback_template=DataproductFeedbackTemplate.objects.get(name="empty"), sap=None # todo: do we need to point to a SAP here? Of which dataproduct then? ) @@ -1336,7 +1339,7 @@ def schedule_observation_subtask(observation_subtask: Subtask): specifications_doc = observation_subtask.specifications_doc dataproduct_specifications_template = DataproductSpecificationsTemplate.objects.get(name="SAP") dataproduct_feedback_template = DataproductFeedbackTemplate.objects.get(name="empty") - dataproduct_feedback_doc = get_default_json_object_for_schema(dataproduct_feedback_template.schema) + dataproduct_feedback_doc = get_default_json_object_for_schema(dataproduct_feedback_template.schema, cache=_schema_cache) # select correct output for each pointing based on name @@ -1433,7 +1436,7 @@ def schedule_observation_subtask(observation_subtask: Subtask): producer=observation_subtask.outputs.first(), # todo: select correct output. I tried "subtask_output_dict[sap['name']]" but tests fail because the sap's name is not in the task blueprint. Maybe it's just test setup and this should work? specifications_doc={"sap": specifications_doc['stations']['digital_pointings'][sap_nr]["name"], "coherent": coherent, "identifiers": {"pipeline_index": pipeline_nr, "tab_index": tab_nr, "stokes_index": stokes_nr, "part_index": part_nr}}, specifications_template=dataproduct_specifications_template_timeseries, - feedback_doc=get_default_json_object_for_schema(dataproduct_feedback_template.schema), + feedback_doc=get_default_json_object_for_schema(dataproduct_feedback_template.schema, cache=_schema_cache), feedback_template=dataproduct_feedback_template, size=0, expected_size=1024*1024*1024*tab_nr, @@ -1503,7 +1506,7 @@ def _create_preprocessing_output_dataproducts_and_transforms(pipeline_subtask: S producer=pipeline_subtask_output, specifications_doc=input_dp.specifications_doc, specifications_template=dataproduct_specifications_template, - feedback_doc=get_default_json_object_for_schema(dataproduct_feedback_template.schema), + feedback_doc=get_default_json_object_for_schema(dataproduct_feedback_template.schema, cache=_schema_cache), feedback_template=dataproduct_feedback_template, sap=input_dp.sap, global_identifier=None) for input_dp in input_dataproducts] @@ -1537,7 +1540,7 @@ def _create_pulsar_pipeline_output_dataproducts_and_transforms(pipeline_subtask: producer=pipeline_subtask_output, specifications_doc=input_dp.specifications_doc, specifications_template=dataproduct_specifications_template, - feedback_doc=get_default_json_object_for_schema(dataproduct_feedback_template.schema), + feedback_doc=get_default_json_object_for_schema(dataproduct_feedback_template.schema, cache=_schema_cache), feedback_template=dataproduct_feedback_template, sap=input_dp.sap, global_identifier=None) for input_dp in input_dataproducts] @@ -1575,7 +1578,7 @@ def _create_pulsar_pipeline_output_dataproducts_and_transforms(pipeline_subtask: producer=pipeline_subtask_output, specifications_doc={ "coherent": is_coherent, "identifiers": { "obsid": obsid } }, specifications_template=dataproduct_specifications_template, - feedback_doc=get_default_json_object_for_schema(dataproduct_feedback_template.schema), + feedback_doc=get_default_json_object_for_schema(dataproduct_feedback_template.schema, cache=_schema_cache), feedback_template=dataproduct_feedback_template, sap=None, # TODO: Can we say anything here, as summaries cover all SAPs global_identifier=None) for (obsid, is_coherent) in summaries} diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/tasks.py b/SAS/TMSS/backend/src/tmss/tmssapp/tasks.py index a43d4d81c28c4cc5138f02645d1c9a0adbb066a2..b729f07ecdd7911ffe6f4094cbc2ff7d0a32bce9 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/tasks.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/tasks.py @@ -15,6 +15,9 @@ from django.db import transaction logger = logging.getLogger(__name__) +# cache for json schema's +_schema_cache = {} + def create_scheduling_unit_blueprint_from_scheduling_unit_draft(scheduling_unit_draft: models.SchedulingUnitDraft) -> models.SchedulingUnitBlueprint: """ Create a SchedulingUnitBlueprint from the SchedulingUnitDraft @@ -146,12 +149,14 @@ def create_task_drafts_from_scheduling_unit_draft(scheduling_unit_draft: models. if len(scheduling_unit_draft.requirements_doc.get("tasks", {})) == 0: raise BlueprintCreationException("create_task_drafts_from_scheduling_unit_draft: scheduling_unit_draft.id=%s has no tasks defined in its requirements_doc" % (scheduling_unit_draft.pk,)) + schema_cache = {} + for task_name, task_definition in scheduling_unit_draft.requirements_doc["tasks"].items(): task_template_name = task_definition["specifications_template"] task_template = models.TaskTemplate.objects.get(name=task_template_name) task_specifications_doc = task_definition["specifications_doc"] - task_specifications_doc = add_defaults_to_json_object_for_schema(task_specifications_doc, task_template.schema) + task_specifications_doc = add_defaults_to_json_object_for_schema(task_specifications_doc, task_template.schema, cache=_schema_cache) try: logger.debug("creating task draft... task_name='%s', task_template_name='%s'", task_template_name, task_template_name) @@ -464,7 +469,7 @@ def create_cleanuptask_for_scheduling_unit_blueprint(scheduling_unit_blueprint: with transaction.atomic(): # create a cleanup task draft and blueprint.... cleanup_template = models.TaskTemplate.objects.get(name="cleanup") - cleanup_spec_doc = get_default_json_object_for_schema(cleanup_template.schema) + cleanup_spec_doc = get_default_json_object_for_schema(cleanup_template.schema, cache=_schema_cache) cleanup_task_draft = models.TaskDraft.objects.create( name="Cleanup", @@ -487,7 +492,7 @@ def create_cleanuptask_for_scheduling_unit_blueprint(scheduling_unit_blueprint: # ... and connect the outputs of the producing tasks to the cleanup, so the cleanup task knows what to remove. selection_template = TaskRelationSelectionTemplate.objects.get(name="all") - selection_doc = get_default_json_object_for_schema(selection_template.schema) + selection_doc = get_default_json_object_for_schema(selection_template.schema, cache=_schema_cache) for producer_task_blueprint in scheduling_unit_blueprint.task_blueprints.exclude(specifications_template__type=TaskType.Choices.CLEANUP).exclude(specifications_template__type=TaskType.Choices.INGEST).all(): for connector_type in producer_task_blueprint.specifications_template.output_connector_types.filter(iotype__value=IOType.Choices.OUTPUT.value).all(): diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/views.py b/SAS/TMSS/backend/src/tmss/tmssapp/views.py index c043399964b788b809194e49c1c0b6872e57fdfe..49d3489f2e2d71ea183a6a97490d795fcf34d08b 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/views.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/views.py @@ -12,6 +12,7 @@ from rest_framework.authtoken.models import Token from rest_framework.permissions import AllowAny from rest_framework.decorators import authentication_classes, permission_classes from django.apps import apps +import re from rest_framework.decorators import api_view from datetime import datetime @@ -78,6 +79,32 @@ def get_template_json_schema(request, template:str, name:str, version:str): return response +# Allow everybody to GET our publicly available LTA SIP XSD (XML Schema Definition for the LTA SIP) +@permission_classes([AllowAny]) +@authentication_classes([AllowAny]) +@swagger_auto_schema(#method='GET', + responses={200: 'Get the LTA SIP XSD', + 404: 'not available'}, + operation_description="Get the LTA SIP XSD.") +#@api_view(['GET']) # todo: !! decorating this as api_view somehow breaks json ref resolution !! fix this and double url issue in urls.py, then use decorator here to include in Swagger +def get_lta_sip_xsd(request): + + lta_sip_xsd_path = os.path.join(os.environ["LOFARROOT"], "share", "lta", "LTA-SIP.xsd") + with open(lta_sip_xsd_path, 'rt') as file: + xsd = file.read() + + # hacky way of setting the namespace to this url + # can/should be done with proper xml dom setAttribute on document node. + # but this string manipulation is faster, and works just as well. + # the namespace should point to the absolute url of this request, without the document name. + abs_uri = "%s://%s/%s" % (request.scheme, request.get_host().rstrip('/'), request.get_full_path().lstrip('/')) + abs_uri = abs_uri[:abs_uri.rfind('/')] + for attr in ('targetNamespace', 'xmlns'): + xsd = xsd.replace('''%s="http://www.astron.nl/SIP-Lofar"'''%attr, '''%s="%s"'''%(attr,abs_uri)) + + return HttpResponse(content=xsd, content_type='application/xml') + + # Allow everybody to GET our publicly available station group lookups @permission_classes([AllowAny]) @authentication_classes([AllowAny]) diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/scheduling.py b/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/scheduling.py index 28ad1dbc6a6174dcb31ee2db8d88e0f954228001..df92de0ce14cdcd67cc4913818ee26ea7a140127 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/scheduling.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/scheduling.py @@ -395,9 +395,24 @@ class DataproductViewSet(LOFARViewSet): operation_description="Get the Submission Information Package (SIP) for this dataproduct") @action(methods=['get'], detail=True, url_name="sip") def sip(self, request, pk=None): - dataproduct = get_object_or_404(models.Dataproduct, pk=pk) from lofar.sas.tmss.tmss.tmssapp.adapters.sip import generate_sip_for_dataproduct - return HttpResponse(generate_sip_for_dataproduct(dataproduct).get_prettyxml(), content_type='application/xml') + from lofar.sas.tmss.tmss.tmssapp import views + from django.urls import reverse + + # get the dataproduct... + dataproduct = get_object_or_404(models.Dataproduct, pk=pk) + + # construct the schema loction for the sip + lta_sip_xsd_path = reverse(views.get_lta_sip_xsd) + lta_sip_xsd_uri = "%s://%s/%s" % (request.scheme, request.get_host().rstrip('/'), lta_sip_xsd_path.lstrip('/')) + # the schema_location should point to a weird 2 part url, the path -space- document. + schema_location = lta_sip_xsd_uri[:lta_sip_xsd_uri.rfind('/')] + ' ' + lta_sip_xsd_uri[lta_sip_xsd_uri.rfind('/')+1:] + + # generate the sip + sip = generate_sip_for_dataproduct(dataproduct).get_prettyxml(schema_location=schema_location) + + # and return it + return HttpResponse(sip, content_type='application/xml') @swagger_auto_schema(responses={200: 'The SIP graph for this dataproduct', 403: 'forbidden'}, diff --git a/SAS/TMSS/backend/src/tmss/urls.py b/SAS/TMSS/backend/src/tmss/urls.py index c077e51431b29da1484c0653421d54c27a7a5f91..9d84e25fefe8577ec79a92894da50ab626b65e37 100644 --- a/SAS/TMSS/backend/src/tmss/urls.py +++ b/SAS/TMSS/backend/src/tmss/urls.py @@ -67,6 +67,7 @@ urlpatterns = [ #re_path('schemas/<str:template>/<str:name>/<str:version>', views.get_template_json_schema, name='get_template_json_schema'), # !! use of regex here breaks reverse url lookup path('schemas/<str:template>/<str:name>/<str:version>', views.get_template_json_schema, name='get_template_json_schema'), # !! two urls for same view break Swagger, one url break json ref resolution !! path('schemas/<str:template>/<str:name>/<str:version>/', views.get_template_json_schema, name='get_template_json_schema'), # !! two urls for same view break Swagger, one url break json ref resolution !! + path('xsd/LTA-SIP.xsd', views.get_lta_sip_xsd, name='get_lta_sip_xsd'), #re_path('station_groups/<str:template_name>/<str:template_version>/<str:station_group>/?', views.get_stations_in_group, name='get_stations_in_group'), # !! use of regex here somehow breaks functionality (because parameters?) -> index page path('station_groups/<str:template_name>/<str:template_version>/<str:station_group>', views.get_stations_in_group, name='get_stations_in_group'), path('station_groups/<str:template_name>/<str:template_version>/<str:station_group>/', views.get_stations_in_group, name='get_stations_in_group'), diff --git a/SAS/TMSS/backend/test/test_environment.py b/SAS/TMSS/backend/test/test_environment.py index 2cf2d6c51f8f246101f405113a20d2437bbdc8f2..7a4dd9679ff8b5590b30091c683747becab3bac4 100644 --- a/SAS/TMSS/backend/test/test_environment.py +++ b/SAS/TMSS/backend/test/test_environment.py @@ -631,7 +631,7 @@ def main_test_environment(): def create_scheduling_unit_blueprint_simulator(scheduling_unit_blueprint_id: int, stop_event: threading.Event, handle_observations: bool = True, handle_pipelines: bool = True, - handle_QA: bool = True, handle_ingest: bool = True, + handle_QA: bool = True, handle_ingest: bool = True, handle_cleanup: bool = True, auto_grant_ingest_permission: bool = True, delay: float=1, duration: float=5, create_output_dataproducts: bool=False, @@ -653,7 +653,7 @@ def create_scheduling_unit_blueprint_simulator(scheduling_unit_blueprint_id: int class SimulationEventHandler(TMSSEventMessageHandler): def __init__(self, scheduling_unit_blueprint_id: int, stop_event: threading.Event, handle_observations: bool = True, handle_pipelines: bool = True, - handle_QA: bool = True, handle_ingest: bool = True, + handle_QA: bool = True, handle_ingest: bool = True, handle_cleanup: bool = True, delay: float = 1, duration: float = 10, create_output_dataproducts: bool=False) -> None: super().__init__(log_event_messages=False) @@ -663,13 +663,14 @@ def create_scheduling_unit_blueprint_simulator(scheduling_unit_blueprint_id: int self.handle_pipelines = handle_pipelines self.handle_QA = handle_QA self.handle_ingest = handle_ingest + self.handle_cleanup = handle_cleanup self.auto_grant_ingest_permission = auto_grant_ingest_permission self.delay = delay self.duration = duration self.create_output_dataproducts = create_output_dataproducts def need_to_handle(self, subtask: models.Subtask) -> bool: - if self.scheduling_unit_blueprint_id in [tb.scheduling_unit_blueprint.id for tb in subtask.task_blueprints.all()]: + if self.scheduling_unit_blueprint_id not in [tb.scheduling_unit_blueprint.id for tb in subtask.task_blueprints.all()]: return False if subtask.specifications_template.type.value == models.SubtaskType.Choices.OBSERVATION.value and not self.handle_observations: @@ -685,6 +686,9 @@ def create_scheduling_unit_blueprint_simulator(scheduling_unit_blueprint_id: int if subtask.specifications_template.type.value == models.SubtaskType.Choices.INGEST.value and not self.handle_ingest: return False + if subtask.specifications_template.type.value == models.SubtaskType.Choices.CLEANUP.value and not self.handle_cleanup: + return False + return True def start_handling(self): @@ -694,21 +698,21 @@ def create_scheduling_unit_blueprint_simulator(scheduling_unit_blueprint_id: int logger.info("starting to simulate a run for scheduling_unit id=%s ...", self.scheduling_unit_blueprint_id) - super().start_handling() - try: # exit if already finished scheduling_unit = models.SchedulingUnitBlueprint.objects.get(id=self.scheduling_unit_blueprint_id) - if scheduling_unit.status in ["finished", "error"]: + if scheduling_unit.status in ["finished", "error", "cancelled"]: logger.info("scheduling_unit id=%s name='%s' has status=%s -> not simulating", scheduling_unit.id, scheduling_unit.name, scheduling_unit.status) self.stop_event.set() return except models.SchedulingUnitBlueprint.DoesNotExist: pass + super().start_handling() + # trick: trigger any already scheduled subtasks, cascading in events simulating the run - subtasks = models.Subtask.objects.filter(task_blueprints__scheduling_unit_blueprint_id=self.scheduling_unit_blueprint_id) - for subtask in subtasks.filter(state__value=models.SubtaskState.Choices.SCHEDULED.value): + scheduled_subtasks = models.Subtask.objects.filter(task_blueprints__scheduling_unit_blueprint_id=self.scheduling_unit_blueprint_id).filter(state__value=models.SubtaskState.Choices.SCHEDULED.value).all() + for subtask in scheduled_subtasks: self.onSubTaskStatusChanged(subtask.id, "scheduled") # schedule the defined subtasks, cascading in events simulating the run @@ -760,8 +764,7 @@ def create_scheduling_unit_blueprint_simulator(scheduling_unit_blueprint_id: int if not self.need_to_handle(subtask): return - logger.info("subtask id=%s type='%s' now has status='%s'", id, subtask.specifications_template.type.value, - status) + logger.info("subtask id=%s type='%s' has status='%s'", id, subtask.specifications_template.type.value, status) next_state = None if status == models.SubtaskState.Choices.SCHEDULED.value: @@ -856,7 +859,7 @@ def create_scheduling_unit_blueprint_simulator(scheduling_unit_blueprint_id: int return BusListenerJanitor(TMSSBusListener(SimulationEventHandler, handler_kwargs={'scheduling_unit_blueprint_id': scheduling_unit_blueprint_id, 'stop_event': stop_event, 'handle_observations': handle_observations, 'handle_pipelines': handle_pipelines, - 'handle_QA': handle_QA, 'handle_ingest': handle_ingest, + 'handle_QA': handle_QA, 'handle_ingest': handle_ingest, 'handle_cleanup': handle_cleanup, 'create_output_dataproducts': create_output_dataproducts, 'delay': delay, 'duration': duration}, exchange=exchange, broker=broker)) @@ -883,12 +886,14 @@ def main_scheduling_unit_blueprint_simulator(): group.add_option('-p', '--pipeline', dest='pipeline', action='store_true', help='simulate events for pipeline subtasks') group.add_option('-Q', '--QA', dest='QA', action='store_true', help='simulate events for QA subtasks') group.add_option('-i', '--ingest', dest='ingest', action='store_true', help='simulate events for ingest subtasks') + group.add_option('-c', '--cleanup', dest='cleanup', action='store_true', help='simulate events for cleanup subtasks') group = OptionGroup(parser, 'Simulation parameters') parser.add_option_group(group) group.add_option('-e', '--event_delay', dest='event_delay', type='float', default=1.0, help='wait <event_delay> seconds between simulating events to mimic real-world behaviour, default: %default') group.add_option('-d', '--duration', dest='duration', type='float', default=60.0, help='wait <duration> seconds while "observing"/"processing" between started and finishing state to mimic real-world behaviour, default: %default') group.add_option('-g', '--grant_ingest_permission', dest='grant_ingest_permission', action='store_true', help='automatically grant ingest permission for ingest subtasks if needed') + group.add_option('-f', '--create_output_dataproducts', dest='create_output_dataproducts', action='store_true', help='create small fake output dataproduct files for the observation and pipeline subtask(s)') group = OptionGroup(parser, 'Messaging options') parser.add_option_group(group) @@ -906,11 +911,12 @@ def main_scheduling_unit_blueprint_simulator(): scheduling_unit_blueprint_id = int(args[0]) - if not (options.observation or options.pipeline or options.QA or options.ingest): + if not (options.observation or options.pipeline or options.QA or options.ingest or options.cleanup): options.observation = True options.pipeline = True options.QA = True options.ingest = True + options.cleanup = True from lofar.sas.tmss.tmss import setup_and_check_tmss_django_database_connection_and_exit_on_error setup_and_check_tmss_django_database_connection_and_exit_on_error(options.dbcredentials) @@ -919,8 +925,9 @@ def main_scheduling_unit_blueprint_simulator(): with create_scheduling_unit_blueprint_simulator(scheduling_unit_blueprint_id, stop_event=stop_event, delay=options.event_delay, duration=options.duration, handle_observations=bool(options.observation), handle_pipelines=bool(options.pipeline), - handle_QA=bool(options.QA), handle_ingest=bool(options.ingest), + handle_QA=bool(options.QA), handle_ingest=bool(options.ingest), handle_cleanup=bool(options.cleanup), auto_grant_ingest_permission=bool(options.grant_ingest_permission), + create_output_dataproducts=bool(options.create_output_dataproducts), exchange=options.exchange, broker=options.broker): print("Press Ctrl-C to exit") try: diff --git a/SAS/TMSS/client/lib/populate.py b/SAS/TMSS/client/lib/populate.py index 952b0e27de20aa016f8d2de0c7622a59a964e657..db1898204cf2a70e0b1960207f90fbbe713acd06 100644 --- a/SAS/TMSS/client/lib/populate.py +++ b/SAS/TMSS/client/lib/populate.py @@ -100,8 +100,12 @@ def populate_schemas(schema_dir: str=None, templates_filename: str=None): # helper functions for uploading def upload_template(template: dict): - logger.info("Uploading template with name='%s' version='%s'", template['name'], template['version']) - client.post_template(template_path=template.pop('template'), **template) + try: + logger.info("Uploading template with name='%s' version='%s'", template['name'], template['version']) + client.post_template(template_path=template.pop('template'), **template) + except Exception as e: + logger.error("Error while uploading template with name='%s' version='%s': %s", + template['name'], template['version'], e) # helper functions for uploading def upload_template_if_needed_with_dependents_first(id: str): diff --git a/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js b/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js index d0108eef8d31fd90ba6c635fcf83a2854c79cfdf..c19f4fea15c48c7c1d96c9bc9e0a3b39996e35b2 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js @@ -289,7 +289,7 @@ function CalendarColumnFilter({ <div className="table-filter" onClick={e => { e.stopPropagation() }}> <Calendar value={filterValue} appendTo={document.body} dateFormat="yy-mm-dd" onChange={(e) => { const value = moment(e.value).format('YYYY-MM-DD') - setValue(value); + setValue(value); setFilter(e.value); }} showIcon></Calendar> {value && <i onClick={() => { setFilter(undefined); setValue('') }} className="tb-cal-reset fa fa-times" />} @@ -599,7 +599,51 @@ const IndeterminateCheckbox = React.forwardRef( ) // Our table component -function Table({ columns, data, defaultheader, optionalheader, tablename, defaultSortColumn, defaultpagesize, columnOrders, showAction }) { +function Table(props) { + let { columns, data, defaultheader, optionalheader, tablename, defaultSortColumn, defaultpagesize, columnOrders, showAction, toggleBySorting, onColumnToggle, lsKeySortColumn + , descendingColumn, ignoreSorting } = props; + ignoreSorting = ignoreSorting ||[]; + ignoreSorting = [...ignoreSorting,'action'] + descendingColumn = descendingColumn || []; + const checkDefaultSortColumnEmpty = () => { + return !defaultSortColumn || !defaultSortColumn[0] || Object.keys(defaultSortColumn[0]).length === 0; + } + const checkDescendingColumnExists = (value) => { + return descendingColumn.includes(value); + } + const checkToIgnoreSorting = (value) => { + return ignoreSorting.includes(value); + } + const getFirstVisibleColumn = (selectedColumn, tempAllColumns) => { + let selected = {}; + let tempColumn = {}; + let totalColumns = undefined; + + if (tempAllColumns && tempAllColumns.length > 0) { + totalColumns = tempAllColumns; + } + + if (totalColumns) { + for (let i = 0; i < totalColumns.length; i++) { + tempColumn = { ...totalColumns[i] }; + if (tempColumn.Header && typeof tempColumn.Header === "string") { + if (tempColumn.Header.toLowerCase() === selectedColumn.Header.toLowerCase()) { + tempColumn.isVisible = selectedColumn.isVisible; + } + if (!checkToIgnoreSorting(tempColumn.Header.toLowerCase()) && tempColumn.isVisible) { + selected = tempColumn; + break; + } + } + } + } + return selected; + } + + if (checkDefaultSortColumnEmpty()) { + let tempVisibleColumn = getFirstVisibleColumn({ Header: '' }, columns); + defaultSortColumn = [{ id: tempVisibleColumn.Header, desc: checkDescendingColumnExists(tempVisibleColumn.Header.toLowerCase()) }]; + } const filterTypes = React.useMemo( () => ({ @@ -629,7 +673,7 @@ function Table({ columns, data, defaultheader, optionalheader, tablename, defaul }), [] ) - + let tblinstance; const { getTableProps, getTableBodyProps, @@ -650,7 +694,7 @@ function Table({ columns, data, defaultheader, optionalheader, tablename, defaul selectedFlatRows, setColumnOrder, exportData, - } = useTable( + } = tblinstance = useTable( { columns, data, @@ -671,10 +715,13 @@ function Table({ columns, data, defaultheader, optionalheader, tablename, defaul useColumnOrder, useExportData ); + + + React.useEffect(() => { setHiddenColumns( - // columns.filter(column => !column.isVisible).map(column => column.accessor) - columns.filter(column => !column.isVisible).map(column => column.id) + // columns.filter(column => !column.isVisible).map(column => column.accessor) + columns.filter(column => !column.isVisible).map(column => column.id) ); // console.log('columns List', visibleColumns.map((d) => d.id)); if (columnOrders && columnOrders.length) { @@ -684,7 +731,7 @@ function Table({ columns, data, defaultheader, optionalheader, tablename, defaul setColumnOrder(['Select', ...columnOrders]); } } - + }, [setHiddenColumns, columns]); let op = useRef(null); @@ -693,6 +740,7 @@ function Table({ columns, data, defaultheader, optionalheader, tablename, defaul const [currentrows, setcurrentRows] = React.useState(defaultpagesize); const [custompagevalue, setcustompagevalue] = React.useState(); + const onPagination = (e) => { gotoPage(e.page); setcurrentPage(e.first); @@ -722,16 +770,48 @@ function Table({ columns, data, defaultheader, optionalheader, tablename, defaul setcustompagevalue(); }; + + + const onColumnToggleViewTable = (selectedColumn, sortedColumn) => { + let visibleColumn = {}; + let viewColumn = {}; + if (selectedColumn.Header === sortedColumn.Header && !selectedColumn.isVisible) { + visibleColumn = getFirstVisibleColumn(selectedColumn, allColumns); + viewColumn = { Header: visibleColumn.Header, desc: checkDescendingColumnExists(visibleColumn.Header.toLowerCase())}; + let tempdefaultSortColumn = [{ id: viewColumn.Header, desc: viewColumn.desc }]; + if (lsKeySortColumn && lsKeySortColumn.trim().length > 0) { + localStorage.setItem(lsKeySortColumn, JSON.stringify(tempdefaultSortColumn)); + } + } + visibleColumn.Header = visibleColumn.Header || ""; + return viewColumn; + } + const onToggleChange = (e) => { let lsToggleColumns = []; + let selectedColumn = null; + let sortedColumn = {}; allColumns.forEach(acolumn => { let jsonobj = {}; let visible = (acolumn.Header === e.target.id) ? ((acolumn.isVisible) ? false : true) : acolumn.isVisible jsonobj['Header'] = acolumn.Header; jsonobj['isVisible'] = visible; lsToggleColumns.push(jsonobj) - }) - localStorage.setItem(tablename, JSON.stringify(lsToggleColumns)) + selectedColumn = (acolumn.Header === e.target.id) ? jsonobj : selectedColumn; + if (acolumn.isSorted) { + sortedColumn['Header'] = acolumn.Header; + sortedColumn['isVisible'] = visible; + } + }); + localStorage.setItem(tablename, JSON.stringify(lsToggleColumns)); + + if (onColumnToggleViewTable) { + let columnTobeSorted = onColumnToggleViewTable(selectedColumn, sortedColumn); //onColumnToggle(selectedColumn, sortedColumn); + columnTobeSorted.Header = columnTobeSorted.Header || ""; + if (columnTobeSorted.Header.trim().length > 0) { + tblinstance.toggleSortBy(columnTobeSorted.Header, columnTobeSorted.desc); + } + } } filteredData = _.map(rows, 'values'); @@ -748,6 +828,10 @@ function Table({ columns, data, defaultheader, optionalheader, tablename, defaul parentCBonSelection(selectedRows) } + const onSortBy = () => { + sessionStorage.setItem("sortedData", tbldata); + } + return ( <> <div style={{ display: 'flex', justifyContent: 'space-between' }}> @@ -773,7 +857,7 @@ function Table({ columns, data, defaultheader, optionalheader, tablename, defaul <div key={column.id} style={{ 'display': column.id !== 'actionpath' ? 'block' : 'none' }}> <input type="checkbox" {...column.getToggleHiddenProps()} id={(defaultheader[column.id]) ? defaultheader[column.id] : (optionalheader[column.id] ? optionalheader[column.id] : column.id)} - onClick={onToggleChange} + onClick={(e) => onToggleChange(e)} /> { (defaultheader[column.id]) ? defaultheader[column.id] : (optionalheader[column.id] ? optionalheader[column.id] : column.id)} </div> @@ -827,8 +911,8 @@ function Table({ columns, data, defaultheader, optionalheader, tablename, defaul {headerGroups.map(headerGroup => ( <tr {...headerGroup.getHeaderGroupProps()}> {headerGroup.headers.map(column => ( - <th> - <div {...column.getHeaderProps(column.getSortByToggleProps())}> + <th onClick={() => toggleBySorting({ 'id': column.id, desc: (column.isSortedDesc != undefined ? !column.isSortedDesc : false) })}> + <div {...column.getHeaderProps(column.getSortByToggleProps())} > {column.Header !== 'actionpath' && column.render('Header')} {column.Header !== 'Action' ? column.isSorted ? (column.isSortedDesc ? <i className="pi pi-sort-down" aria-hidden="true"></i> : <i className="pi pi-sort-up" aria-hidden="true"></i>) : "" @@ -910,7 +994,8 @@ filterGreaterThan.autoRemove = val => typeof val !== 'number' function ViewTable(props) { const history = useHistory(); // Data to show in table - tbldata = props.data; + console.log("sessionStorage", JSON.parse(sessionStorage.getItem("sortedData"))); + tbldata = JSON.parse(sessionStorage.getItem("sortedData")) || props.data; showCSV = (props.showCSV) ? props.showCSV : false; parentCallbackFunction = props.filterCallback; @@ -967,7 +1052,7 @@ function ViewTable(props) { }); } - if (props.showaction === 'true') { + if (props.showaction) { columns.push({ Header: 'Action', id: 'Action', @@ -975,7 +1060,8 @@ function ViewTable(props) { Cell: props => <button className='p-link' onClick={navigateTo(props)} ><i className="fa fa-eye" style={{ cursor: 'pointer' }}></i></button>, disableFilters: true, disableSortBy: true, - isVisible: true//defaultdataheader.includes(props.keyaccessor), + //isVisible: defaultdataheader.includes(props.keyaccessor), + isVisible: true }) } @@ -989,7 +1075,7 @@ function ViewTable(props) { } }) } else { - window.open(cellProps.cell.row.values['actionpath'] , '_blank'); + window.open(cellProps.cell.row.values['actionpath'], '_blank'); } } // Object.entries(props.paths[0]).map(([key,value]) =>{}) @@ -1032,17 +1118,11 @@ function ViewTable(props) { let togglecolumns = localStorage.getItem(tablename); if (togglecolumns) { - togglecolumns = JSON.parse(togglecolumns); - columns.forEach(column => { - let tcolumn = _.find(togglecolumns, {Header: column.Header}); - column['isVisible'] = (tcolumn)? tcolumn.isVisible: column.isVisible; - }); - /*columns.forEach(column => { - togglecolumns.filter(tcol => { - column.isVisible = (tcol.Header === column.Header) ? tcol.isVisible : column.isVisible; - return tcol; - }); - });*/ + togglecolumns = JSON.parse(togglecolumns); + columns.forEach(column => { + let tcolumn = _.find(togglecolumns, { Header: column.Header }); + column['isVisible'] = (tcolumn) ? tcolumn.isVisible : column.isVisible; + }); } function updatedCellvalue(key, value, properties) { @@ -1063,13 +1143,13 @@ function ViewTable(props) { return retval; } else if (typeof value == "boolean") { return value.toString(); - }else if (typeof value == "string") { + } else if (typeof value == "string") { const format = properties ? properties.format : 'YYYY-MM-DD HH:mm:ss'; const dateval = moment(value, moment.ISO_8601).format(format); if (dateval !== 'Invalid date') { return dateval; } - } + } } catch (err) { console.error('Error', err) } @@ -1079,7 +1159,11 @@ function ViewTable(props) { return ( <div> <Table columns={columns} data={tbldata} defaultheader={defaultheader[0]} optionalheader={optionalheader[0]} showAction={props.showaction} - defaultSortColumn={defaultSortColumn} tablename={tablename} defaultpagesize={defaultpagesize} columnOrders={props.columnOrders} /> + defaultSortColumn={defaultSortColumn} tablename={tablename} defaultpagesize={defaultpagesize} columnOrders={props.columnOrders} toggleBySorting={(sortData) => props.toggleBySorting(sortData)} + onColumnToggle={props.onColumnToggle} + lsKeySortColumn={props.lsKeySortColumn} + descendingColumn={props.descendingColumn} + ignoreSorting={props.ignoreSorting} /> </div> ) } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Cycle/list.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Cycle/list.js index ac81b969c4f2c2a45696aaaba4424c2b3a29294d..4bef0c706c1d83ac6095b70b11bdc4febb6c6a07 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Cycle/list.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Cycle/list.js @@ -8,9 +8,14 @@ import UnitConversion from '../../utils/unit.converter'; import AppLoader from '../../layout/components/AppLoader'; import PageHeader from '../../layout/components/PageHeader'; import UIConstants from '../../utils/ui.constants'; +import UtilService from '../../services/util.service'; +/* eslint-disable no-unused-expressions */ -class CycleList extends Component{ - constructor(props){ +class CycleList extends Component { + lsTableName = "cycle_list"; + lsKeySortColumn='cycleSortData'; + descendingColumn=['start date']; // Values should be lower case + constructor(props) { super(props) this.state = { cyclelist: [], @@ -21,83 +26,92 @@ class CycleList extends Component{ } this.projectCategory = ['regular', 'user_shared_support']; this.periodCategory = ['long_term']; - this.defaultcolumns = [ { id:"Cycle Code", - start: { - name: "Start Date", - filter: "date", - format: UIConstants.CALENDAR_DEFAULTDATE_FORMAT - }, - stop: { - name: "End Date", - filter: "date", - format: UIConstants.CALENDAR_DEFAULTDATE_FORMAT - }, - duration:{ - name: "Duration (Days)", - filter: "range", - format: UIConstants.CALENDAR_TIME_FORMAT - }, - totalProjects:{ - name:'No.of Projects', - filter:"range" - }, - observingTime:{ - name: "Lofar Observing Time (Hrs)", - filter:"range" - }, - processingTime:{ - name:"Lofar Processing Time (Hrs)", - filter:"range" - }, - ltaResources: { - name:"Lofar LTA Resources(TB)", - filter:"range" - }, - support:{ - name:"Lofar Support (Hrs)", - filter:"range" - }, - longterm : { - name:"Long Term Projects", - filter:"range" - }} ]; - this.optionalcolumns = [{ regularProjects:{ - name: "No.of Regular Projects", - filter:"range" - }, - observingTimeDDT:{ - name: "Lofar Observing Time Commissioning (Hrs)", - filter:"range" - }, - observingTimePrioA:{ - name:"Lofar Observing Time Prio A (Hrs)", - filter:"range" - }, - observingTimePrioB:{ - name:"Lofar Observing Time Prio B (Hrs)", - filter:"range" - }, - actionpath: "actionpath" }]; + this.defaultcolumns = [{ + id: "Cycle Code", + start: { + name: "Start Date", + filter: "date", + format: UIConstants.CALENDAR_DEFAULTDATE_FORMAT + }, + stop: { + name: "End Date", + filter: "date", + format: UIConstants.CALENDAR_DEFAULTDATE_FORMAT + }, + duration: { + name: "Duration (Days)", + filter: "range", + format: UIConstants.CALENDAR_TIME_FORMAT + }, + totalProjects: { + name: 'No.of Projects', + filter: "range" + }, + observingTime: { + name: "Lofar Observing Time (Hrs)", + filter: "range" + }, + processingTime: { + name: "Lofar Processing Time (Hrs)", + filter: "range" + }, + ltaResources: { + name: "Lofar LTA Resources(TB)", + filter: "range" + }, + support: { + name: "Lofar Support (Hrs)", + filter: "range" + }, + longterm: { + name: "Long Term Projects", + filter: "range" + } + }]; + this.optionalcolumns = [{ + regularProjects: { + name: "No.of Regular Projects", + filter: "range" + }, + observingTimeDDT: { + name: "Lofar Observing Time Commissioning (Hrs)", + filter: "range" + }, + observingTimePrioA: { + name: "Lofar Observing Time Prio A (Hrs)", + filter: "range" + }, + observingTimePrioB: { + name: "Lofar Observing Time Prio B (Hrs)", + filter: "range" + }, + actionpath: "actionpath" + }]; - this.columnclassname = [{ "Cycle Code":"filter-input-75", - "Duration (Days)" : "filter-input-50", - "No.of Projects" : "filter-input-50", - "Lofar Observing Time (Hrs)" : "filter-input-75", - "Lofar Processing Time (Hrs)" : "filter-input-75", - "Lofar LTA Resources(TB)" : "filter-input-75", - "Lofar Support (Hrs)" : "filter-input-50", - "Long Term Projects" : "filter-input-50", - "No.of Regular Projects" : "filter-input-50", - "Lofar Observing Time Commissioning (Hrs)" : "filter-input-75", - "Lofar Observing Time Prio A (Hrs)" : "filter-input-75", - "Lofar Observing Time Prio B (Hrs)" : "filter-input-75" }]; - - this.defaultSortColumn = [{id: "Cycle Code", desc: false}]; + this.columnclassname = [{ + "Cycle Code": "filter-input-75", + "Duration (Days)": "filter-input-50", + "No.of Projects": "filter-input-50", + "Lofar Observing Time (Hrs)": "filter-input-75", + "Lofar Processing Time (Hrs)": "filter-input-75", + "Lofar LTA Resources(TB)": "filter-input-75", + "Lofar Support (Hrs)": "filter-input-50", + "Long Term Projects": "filter-input-50", + "No.of Regular Projects": "filter-input-50", + "Lofar Observing Time Commissioning (Hrs)": "filter-input-75", + "Lofar Observing Time Prio A (Hrs)": "filter-input-75", + "Lofar Observing Time Prio B (Hrs)": "filter-input-75" + }]; + + this.setToggleBySorting(); + //this.defaultSortColumn = [{ id: "Start Date", desc: true }]; + this.toggleBySorting = this.toggleBySorting.bind(this); + this.setToggleBySorting = this.setToggleBySorting.bind(this); } getUnitConvertedQuotaValue(cycle, cycleQuota, resourceName) { - const quota = _.find(cycleQuota, {'cycle_id': cycle.name, 'resource_type_id': resourceName}); + const quota = _.find(cycleQuota, { 'cycle_id': cycle.name, 'resource_type_id': resourceName }); const unitQuantity = this.state.resources.find(i => i.name === resourceName).quantity_value; - return UnitConversion.getUIResourceUnit(unitQuantity, quota?quota.value:0); + return UnitConversion.getUIResourceUnit(unitQuantity, quota ? quota.value : 0); } getCycles(cycles = [], cycleQuota) { const promises = []; @@ -110,7 +124,7 @@ class CycleList extends Component{ const longterm = projects.filter(project => this.periodCategory.includes(project.period_category_value)); cycle.duration = UnitConversion.getUIResourceUnit('days', cycle.duration); cycle.totalProjects = cycle.projects ? cycle.projects.length : 0; - cycle.id = cycle.name ; + cycle.id = cycle.name; cycle.regularProjects = regularProjects.length; cycle.longterm = longterm.length; cycle.observingTime = this.getUnitConvertedQuotaValue(cycle, cycleQuota, 'LOFAR Observing Time'); @@ -120,18 +134,17 @@ class CycleList extends Component{ cycle.observingTimeDDT = this.getUnitConvertedQuotaValue(cycle, cycleQuota, 'LOFAR Observing Time Commissioning'); cycle.observingTimePrioA = this.getUnitConvertedQuotaValue(cycle, cycleQuota, 'LOFAR Observing Time prio A'); cycle.observingTimePrioB = this.getUnitConvertedQuotaValue(cycle, cycleQuota, 'LOFAR Observing Time prio B'); - cycle['actionpath'] = `/cycle/view/${cycle.id}`; return cycle; }); this.setState({ - cyclelist : results, + cyclelist: results, isLoading: false }); }); } - componentDidMount(){ + componentDidMount() { const promises = [CycleService.getAllCycleQuotas(), CycleService.getResources()] Promise.all(promises).then(responses => { const cycleQuota = responses[0]; @@ -139,13 +152,34 @@ class CycleList extends Component{ CycleService.getAllCycles().then(cyclelist => { this.getCycles(cyclelist, cycleQuota) }); - }); + }); + this.setToggleBySorting(); + } + + setToggleBySorting() { + let sortData = UtilService.localStore({ type: 'get', key: this.lsKeySortColumn }); + if(sortData){ + if(Object.prototype.toString.call(sortData) === '[object Array]'){ + this.defaultSortColumn = sortData; + } + else{ + this.defaultSortColumn = [{...sortData}]; + } + }else{ + this.defaultSortColumn = [{ id: "Start Date", desc: true }]; + } + this.defaultSortColumn = this.defaultSortColumn || []; + UtilService.localStore({ type: 'set', key: 'cycleSortData', value: this.defaultSortColumn }); + } + + toggleBySorting(sortData) { + UtilService.localStore({ type: 'set', key: this.lsKeySortColumn, value: [{...sortData}] }); } - - render(){ + + render() { return ( <> - { /*<div className="p-grid"> + { /*<div className="p-grid"> <div className="p-col-10 p-lg-10 p-md-10"> <h2>Cycle - List </h2> </div> @@ -162,7 +196,7 @@ class CycleList extends Component{ showaction - {true/false} -> to show the action column paths - specify the path for navigation - Table will set "id" value for each row in action button */} - <PageHeader location={this.props.location} title={'Cycle - List'} actions={[{icon:'fa-plus-square',title:'Click to Add Cycle', props:{ pathname: '/cycle/create'}}]}/> + <PageHeader location={this.props.location} title={'Cycle - List'} actions={[{ icon: 'fa-plus-square', title: 'Click to Add Cycle', props: { pathname: '/cycle/create' } }]} /> {/* * Call View table to show table data, the parameters are, data - Pass API data @@ -170,27 +204,26 @@ class CycleList extends Component{ showaction - {true/false} -> to show the action column paths - specify the path for navigation - Table will set "id" value for each row in action button */} - - {this.state.isLoading? <AppLoader /> : (this.state.cyclelist && this.state.cyclelist.length) ? - - <ViewTable - data={this.state.cyclelist} - defaultcolumns={this.defaultcolumns} + + {this.state.isLoading ? <AppLoader /> : (this.state.cyclelist && this.state.cyclelist.length) ? + + <ViewTable + data={this.state.cyclelist} + defaultcolumns={this.defaultcolumns} optionalcolumns={this.optionalcolumns} - columnclassname = {this.columnclassname} - defaultSortColumn= {this.defaultSortColumn} - showaction="true" + columnclassname={this.columnclassname} + defaultSortColumn={this.defaultSortColumn} + showaction={true} paths={this.state.paths} - tablename="cycle_list" - /> : <></> - } - - - + tablename={this.lsTableName} + toggleBySorting={(sortData) => this.toggleBySorting(sortData)} + lsKeySortColumn={this.lsKeySortColumn} + descendingColumn={this.descendingColumn} + /> : <></> + } </> ) } } -export default CycleList - +export default CycleList \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Cycle/view.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Cycle/view.js index 47c90d8bcbca18c07900b81a15d3ed7512a5099a..2d3dec85738a927061d5b05396e020cf6112b423 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Cycle/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Cycle/view.js @@ -110,10 +110,10 @@ export class CycleView extends Component { <span className="col-lg-4 col-md-4 col-sm-12">{this.state.cycle.description}</span> </div> <div className="p-grid"> - <label className="col-lg-2 col-md-2 col-sm-12">Created At</label> - <span className="col-lg-4 col-md-4 col-sm-12">{moment.utc(this.state.cycle.created_at).format(UIConstants.CALENDAR_DATETIME_FORMAT)}</span> - <label className="col-lg-2 col-md-2 col-sm-12">Updated At</label> - <span className="col-lg-4 col-md-4 col-sm-12">{moment.utc(this.state.cycle.updated_at).format(UIConstants.CALENDAR_DATETIME_FORMAT)}</span> + <label className="col-lg-2 col-md-2 col-sm-12">Start Time</label> + <span className="col-lg-4 col-md-4 col-sm-12">{moment.utc(this.state.cycle.start).format(UIConstants.CALENDAR_DATETIME_FORMAT)}</span> + <label className="col-lg-2 col-md-2 col-sm-12">End Time</label> + <span className="col-lg-4 col-md-4 col-sm-12">{moment.utc(this.state.cycle.stop).format(UIConstants.CALENDAR_DATETIME_FORMAT)}</span> </div> {/* <div className="p-grid"> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/list.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/list.js index 3a36213f6769764122071412e4b2ad7b19c69467..207d7e40d38d251e48050f0abe0fc1ae95dec6f0 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/list.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/list.js @@ -1,114 +1,122 @@ -import React, {Component} from 'react'; +import React, { Component } from 'react'; import ProjectService from '../../services/project.service'; import ViewTable from '../../components/ViewTable'; import AppLoader from '../../layout/components/AppLoader'; import PageHeader from '../../layout/components/PageHeader'; import CycleService from '../../services/cycle.service'; +import UtilService from '../../services/util.service'; +/* eslint-disable no-unused-expressions */ -export class ProjectList extends Component{ - constructor(props){ +export class ProjectList extends Component { + lsTableName = 'project_list'; + lsKeySortColumn = "projectSortData"; + defaultSortColumn= [{ id: "Name / Project Code", desc: false }]; + constructor(props) { super(props) this.state = { projectlist: [], - defaultcolumns: [ { - name:"Name / Project Code", - status:{ - name:"Status", - filter:"select" + defaultcolumns: [{ + name: "Name / Project Code", + status: { + name: "Status", + filter: "select" }, - project_category_value:{ - name:"Category of Project", - filter:"select" + project_category_value: { + name: "Category of Project", + filter: "select" }, - description:"Description", - archive_location_label:{ - name:"LTA Storage Location", - filter:"select" + description: "Description", + archive_location_label: { + name: "LTA Storage Location", + filter: "select" }, - archive_subdirectory:"LTA Storage Path", - }], - optionalcolumns: [{ - priority_rank:{ - name:"Project Priority", - filter:"range" + archive_subdirectory: "LTA Storage Path", + }], + optionalcolumns: [{ + priority_rank: { + name: "Project Priority", + filter: "range" }, - trigger_priority:{ - name:"Trigger Priority", - filter:"range" + trigger_priority: { + name: "Trigger Priority", + filter: "range" }, - period_category_value:{ - name:"Category of Period", - filter:"select" + period_category_value: { + name: "Category of Period", + filter: "select" }, - cycles_ids:{ - name:"Cycles", - filter:"select" + cycles_ids: { + name: "Cycles", + filter: "select" }, - can_trigger:{ - name:"Trigger Allowed", - filter:"switch" + can_trigger: { + name: "Trigger Allowed", + filter: "switch" }, - LOFAR_Observing_Time:{ - name:"Observing time (Hrs)", - filter:"range" + LOFAR_Observing_Time: { + name: "Observing time (Hrs)", + filter: "range" }, - LOFAR_Observing_Time_prio_A:{ - name:"Observing time prio A (Hrs)", - filter:"range" + LOFAR_Observing_Time_prio_A: { + name: "Observing time prio A (Hrs)", + filter: "range" }, - LOFAR_Observing_Time_prio_B:{ - name:"Observing time prio B (Hrs)", - filter:"range" + LOFAR_Observing_Time_prio_B: { + name: "Observing time prio B (Hrs)", + filter: "range" }, - CEP_Processing_Time:{ - name:"Processing time (Hrs)", - filter:"range" + CEP_Processing_Time: { + name: "Processing time (Hrs)", + filter: "range" }, - LTA_Storage:{ - name:"LTA storage (TB)", - filter:"range" + LTA_Storage: { + name: "LTA storage (TB)", + filter: "range" }, - Number_of_triggers:{ - name:"Number of Triggers", - filter:"range" + Number_of_triggers: { + name: "Number of Triggers", + filter: "range" }, - auto_pin:{ - name:"Prevent automatic deletion after ingest", - filter:"switch" + auto_pin: { + name: "Prevent automatic deletion after ingest", + filter: "switch" }, - actionpath:"actionpath" - + actionpath: "actionpath" + }], columnclassname: [{ - "Observing time (Hrs)":"filter-input-50", - "Observing time prio A (Hrs)":"filter-input-50", - "Observing time prio B (Hrs)":"filter-input-50", - "Processing time (Hrs)":"filter-input-50", - "LTA storage (TB)":"filter-input-50", - "Status":"filter-input-50", - "Trigger Allowed":"filter-input-50", - "Number of Triggers":"filter-input-50", - "Project Priority":"filter-input-50", - "Trigger Priority":"filter-input-50", - "Category of Period":"filter-input-50", - "Cycles":"filter-input-100", - "LTA Storage Location":"filter-input-100", - "LTA Storage Path":"filter-input-100" + "Observing time (Hrs)": "filter-input-50", + "Observing time prio A (Hrs)": "filter-input-50", + "Observing time prio B (Hrs)": "filter-input-50", + "Processing time (Hrs)": "filter-input-50", + "LTA storage (TB)": "filter-input-50", + "Status": "filter-input-50", + "Trigger Allowed": "filter-input-50", + "Number of Triggers": "filter-input-50", + "Project Priority": "filter-input-50", + "Trigger Priority": "filter-input-50", + "Category of Period": "filter-input-50", + "Cycles": "filter-input-100", + "LTA Storage Location": "filter-input-100", + "LTA Storage Path": "filter-input-100" }], - defaultSortColumn: [{id: "Name / Project Code", desc: false}], + defaultSortColumn: [{ id: "Name / Project Code", desc: false }], isprocessed: false, isLoading: true } this.getPopulatedProjectList = this.getPopulatedProjectList.bind(this); + this.toggleBySorting = this.toggleBySorting.bind(this); + this.setToggleBySorting = this.setToggleBySorting.bind(this); + this.setToggleBySorting(); } getPopulatedProjectList(cycleId) { - Promise.all([ProjectService.getFileSystem(), ProjectService.getCluster()]).then(async(response) => { + Promise.all([ProjectService.getFileSystem(), ProjectService.getCluster()]).then(async (response) => { const options = {}; response[0].map(fileSystem => { - const cluster = response[1].filter(clusterObj => { return (clusterObj.id === fileSystem.cluster_id && clusterObj.archive_site);}); + const cluster = response[1].filter(clusterObj => { return (clusterObj.id === fileSystem.cluster_id && clusterObj.archive_site); }); if (cluster.length) { - fileSystem.label =`${cluster[0].name} - ${fileSystem.name}` + fileSystem.label = `${cluster[0].name} - ${fileSystem.name}` options[fileSystem.url] = fileSystem; } return fileSystem; @@ -116,7 +124,7 @@ export class ProjectList extends Component{ let projects = []; if (cycleId) { projects = await CycleService.getProjectsByCycle(cycleId); - } else { + } else { projects = await ProjectService.getProjectList(); } projects = await ProjectService.getUpdatedProjectQuota(projects); @@ -133,16 +141,37 @@ export class ProjectList extends Component{ }); } - componentDidMount(){ + componentDidMount() { // Show Project for the Cycle, This request will be coming from Cycle View. Otherwise it is consider as normal Project List. let cycle = this.props.cycle; this.getPopulatedProjectList(cycle); + this.setToggleBySorting(); + } + + setToggleBySorting() { + let sortData = UtilService.localStore({ type: 'get', key: this.lsKeySortColumn }); + if(sortData){ + if(Object.prototype.toString.call(sortData) === '[object Array]'){ + this.defaultSortColumn = sortData; + } + else{ + this.defaultSortColumn = [{...sortData}]; + } + }else{ + this.defaultSortColumn = [{ id: "Name / Project Code", desc: false }]; + } + this.defaultSortColumn = this.defaultSortColumn || []; + UtilService.localStore({ type: 'set', key: this.lsKeySortColumn, value: [...this.defaultSortColumn] }) } - - render(){ - return( + + toggleBySorting(sortData) { + UtilService.localStore({ type: 'set', key: this.lsKeySortColumn, value: sortData }); + } + + render() { + return ( <> - {/*<div className="p-grid"> + {/*<div className="p-grid"> <div className="p-col-10 p-lg-10 p-md-10"> <h2>Project - List </h2> </div> @@ -152,27 +181,29 @@ export class ProjectList extends Component{ </Link> </div> </div> */} - { (this.props.cycle) ? - <> - </> - : - <PageHeader location={this.props.location} title={'Project - List'} - actions={[{icon: 'fa-plus-square',title:'Click to Add Project', props:{pathname: '/project/create' }}]} - /> - } - {this.state.isLoading? <AppLoader /> : (this.state.isprocessed && this.state.projectlist.length>0) ? - <ViewTable - data={this.state.projectlist} - defaultcolumns={this.state.defaultcolumns} + { (this.props.cycle) ? + <> + </> + : + <PageHeader location={this.props.location} title={'Project - List'} + actions={[{ icon: 'fa-plus-square', title: 'Click to Add Project', props: { pathname: '/project/create' } }]} + /> + } + {this.state.isLoading ? <AppLoader /> : (this.state.isprocessed && this.state.projectlist.length > 0) ? + <ViewTable + data={this.state.projectlist} + defaultcolumns={this.state.defaultcolumns} optionalcolumns={this.state.optionalcolumns} columnclassname={this.state.columnclassname} - defaultSortColumn={this.state.defaultSortColumn} - showaction="true" + defaultSortColumn={this.defaultSortColumn} + showaction={true} paths={this.state.paths} keyaccessor="name" unittest={this.state.unittest} - tablename="project_list" - /> + tablename={this.lsTableName} + toggleBySorting={(sortData) => this.toggleBySorting(sortData)} + lsKeySortColumn={this.lsKeySortColumn} + /> : <div>No project found </div> } </> @@ -180,10 +211,11 @@ export class ProjectList extends Component{ } // Set table data for Unit test - unittestDataProvider(){ - if(this.props.testdata){ + unittestDataProvider() { + if (this.props.testdata) { this.setState({ - projectlist: [{can_trigger: true, + projectlist: [{ + can_trigger: true, created_at: "2020-07-27T01:29:57.348499", cycles: ["http://localhost:3000/api/cycle/Cycle%204/"], cycles_ids: ["Cycle 4"], @@ -194,16 +226,16 @@ export class ProjectList extends Component{ observing_time: "155852.10", priority_rank: 10, private_data: true, - project_quota: ["http://localhost:3000/api/project_quota/6/", "http://localhost:3000/api/project_quota/7/"], - project_quota_ids: [6, 7], + project_quota: ["http://localhost:3000/api/project_quota/6/", "http://localhost:3000/api/project_quota/7/"], + project_quota_ids: [6, 7], tags: ["Lofar TMSS Project"], trigger_priority: 20, triggers_allowed: "56", updated_at: "2020-07-27T01:29:57.348522", url: "http://localhost:3000/api/project/Lofar-TMSS-Commissioning/" - }], - isprocessed: true, - unittest: true, + }], + isprocessed: true, + unittest: true, }) } } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.list.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.list.js index 22ddbc9addc2b443f597267cffd26ede7e25dfcc..f1ff8ea2a5d5e9c98e7c6991f93159cc4e087594 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.list.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.list.js @@ -16,8 +16,10 @@ import UnitService from '../../utils/unit.converter'; import UIConstants from '../../utils/ui.constants'; import ReservationService from '../../services/reservation.service'; import CycleService from '../../services/cycle.service'; +import UtilService from '../../services/util.service'; export class ReservationList extends Component{ + lsKeySortColumn = "ReservationListSortData"; constructor(props){ super(props); this.state = { @@ -120,6 +122,7 @@ export class ReservationList extends Component{ } async componentDidMount() { + this. setToggleBySorting(); const promises = [ ReservationService.getReservations(), CycleService.getAllCycles(), ]; @@ -163,7 +166,25 @@ export class ReservationList extends Component{ }); }); } - + + toggleBySorting=(sortData) => { + UtilService.localStore({ type: 'set', key: this.lsKeySortColumn, value: sortData }); + } + + setToggleBySorting() { + let sortData = UtilService.localStore({ type: 'get', key: this.lsKeySortColumn }); + if(sortData){ + if(Object.prototype.toString.call(sortData) === '[object Array]'){ + this.defaultSortColumn = sortData; + } + else{ + this.defaultSortColumn = [{...sortData}]; + } + } + this.defaultSortColumn = this.defaultSortColumn || []; + UtilService.localStore({ type: 'set', key: this.lsKeySortColumn, value: [...this.defaultSortColumn] }); + } + mergeResourceWithReservation ( reservation, params) { if( params ){ Object.keys(params).map((key, i) => ( @@ -474,13 +495,15 @@ export class ReservationList extends Component{ defaultcolumns={this.state.defaultcolumns} optionalcolumns={this.state.optionalcolumns} columnclassname={this.state.columnclassname} - defaultSortColumn={this.state.defaultSortColumn} + defaultSortColumn={this.defaultSortColumn} showaction="true" paths={this.state.paths} tablename="reservation_list" showCSV= {true} allowRowSelection={true} onRowSelection = {this.onRowSelection} + toggleBySorting={(sortData) => this.toggleBySorting(sortData)} + lsKeySortColumn={this.lsKeySortColumn} /> </> : <div>No Reservation found </div> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js index 2e173e9085a13141c43365b63a7dab3778440cef..ca9d501d3d3fda31040dc2a22d8341cf41ef9917 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js @@ -13,10 +13,14 @@ import { DataTable } from 'primereact/datatable'; import { Column } from 'primereact/column'; import { CustomDialog } from '../../layout/components/CustomDialog'; import { appGrowl } from '../../layout/components/AppGrowl'; +import UtilService from '../../services/util.service'; class SchedulingUnitList extends Component{ + lsKeySortColumn = "SchedulingUnitListSortData"; + defaultSortColumn= [{id: "Name", desc: false}]; constructor(props){ super(props); + this. setToggleBySorting(); this.defaultcolumns = { status: { name: "Status", @@ -406,6 +410,28 @@ class SchedulingUnitList extends Component{ componentDidMount(){ this.getSchedulingUnitList(); + this. setToggleBySorting(); + } + + setToggleBySorting() { + let sortData = UtilService.localStore({ type: 'get', key: this.lsKeySortColumn }); + if(sortData){ + if(Object.prototype.toString.call(sortData) === '[object Array]'){ + this.defaultSortColumn = sortData; + } + else{ + this.defaultSortColumn = [{...sortData}]; + } + } + else { + this.defaultSortColumn = [{id: "Name", desc: false}]; + } + this.defaultSortColumn = this.defaultSortColumn || []; + UtilService.localStore({ type: 'set', key: this.lsKeySortColumn, value: [...this.defaultSortColumn] }) + } + + toggleBySorting=(sortData) =>{ + UtilService.localStore({ type: 'set', key: this.lsKeySortColumn, value: sortData }); } /** @@ -605,7 +631,7 @@ class SchedulingUnitList extends Component{ optionalcolumns={this.state.optionalcolumns} columnclassname={this.state.columnclassname} columnOrders={this.state.columnOrders} - defaultSortColumn={this.state.defaultSortColumn} + defaultSortColumn={this.defaultSortColumn} showaction="true" keyaccessor="id" paths={this.state.paths} @@ -613,6 +639,8 @@ class SchedulingUnitList extends Component{ tablename="scheduleunit_list" allowRowSelection={this.props.allowRowSelection} onRowSelection = {this.onRowSelection} + toggleBySorting={(sortData) => this.toggleBySorting(sortData)} + lsKeySortColumn={this.lsKeySortColumn} /> :<div>No Scheduling Unit found</div> } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/ViewSchedulingUnit.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/ViewSchedulingUnit.js index ba52802355ed2324dc7ef6a1ea2c7f6c8a15c37f..5bd338d371491e9d414c05a521374235f113afc5 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/ViewSchedulingUnit.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/ViewSchedulingUnit.js @@ -24,10 +24,15 @@ import Schedulingtaskrelation from './Scheduling.task.relation'; import UnitConverter from '../../utils/unit.converter'; import TaskService from '../../services/task.service'; import UIConstants from '../../utils/ui.constants'; +import UtilService from '../../services/util.service'; -class ViewSchedulingUnit extends Component{ - constructor(props){ - super(props) +class ViewSchedulingUnit extends Component { + lsKeySortColumn = 'SortDataViewSchedulingUnit'; + defaultSortColumn = []; + ignoreSorting = ['status logs']; + constructor(props) { + super(props); + this.setToggleBySorting(); this.state = { scheduleunit: null, schedule_unit_task: [], @@ -41,103 +46,103 @@ class ViewSchedulingUnit extends Component{ missingStationFieldsErrors: [], columnOrders: [ "Status Logs", - "Status", - "Type", - "ID", - "Control ID", - "Name", - "Description", - "Start Time", - "End Time", - "Duration (HH:mm:ss)", - "Relative Start Time (HH:mm:ss)", - "Relative End Time (HH:mm:ss)", - "#Dataproducts", - "size", - "dataSizeOnDisk", - "subtaskContent", - "tags", - "blueprint_draft", - "url", - "Cancelled", - "Created at", - "Updated at" - ], - defaultcolumns: [ { + "Status", + "Type", + "ID", + "Control ID", + "Name", + "Description", + "Start Time", + "End Time", + "Duration (HH:mm:ss)", + "Relative Start Time (HH:mm:ss)", + "Relative End Time (HH:mm:ss)", + "#Dataproducts", + "size", + "dataSizeOnDisk", + "subtaskContent", + "tags", + "blueprint_draft", + "url", + "Cancelled", + "Created at", + "Updated at" + ], + defaultcolumns: [{ status_logs: "Status Logs", - status:{ - name:"Status", - filter: "select" + status: { + name: "Status", + filter: "select" }, - tasktype:{ - name:"Type", - filter:"select" + tasktype: { + name: "Type", + filter: "select" }, id: "ID", subTaskID: 'Control ID', - name:"Name", - description:"Description", - start_time:{ - name:"Start Time", + name: "Name", + description: "Description", + start_time: { + name: "Start Time", filter: "date", - format:UIConstants.CALENDAR_DATETIME_FORMAT + format: UIConstants.CALENDAR_DATETIME_FORMAT }, - stop_time:{ - name:"End Time", + stop_time: { + name: "End Time", filter: "date", - format:UIConstants.CALENDAR_DATETIME_FORMAT + format: UIConstants.CALENDAR_DATETIME_FORMAT }, - duration:{ - name:"Duration (HH:mm:ss)", - format:UIConstants.CALENDAR_TIME_FORMAT + duration: { + name: "Duration (HH:mm:ss)", + format: UIConstants.CALENDAR_TIME_FORMAT }, - relative_start_time:"Relative Start Time (HH:mm:ss)", - relative_stop_time:"Relative End Time (HH:mm:ss)", + relative_start_time: "Relative Start Time (HH:mm:ss)", + relative_stop_time: "Relative End Time (HH:mm:ss)", noOfOutputProducts: "#Dataproducts", - do_cancel:{ + do_cancel: { name: "Cancelled", filter: "switch" }, }], - optionalcolumns: [{ + optionalcolumns: [{ size: "Data size", dataSizeOnDisk: "Data size on Disk", subtaskContent: "Subtask Content", - tags:"Tags", - blueprint_draft:"BluePrint / Task Draft link", - url:"API URL", - created_at:{ + tags: "Tags", + blueprint_draft: "BluePrint / Task Draft link", + url: "API URL", + created_at: { name: "Created at", - filter:"date", - format:UIConstants.CALENDAR_DATETIME_FORMAT + filter: "date", + format: UIConstants.CALENDAR_DATETIME_FORMAT }, - updated_at:{ + updated_at: { name: "Updated at", filter: "date", - format:UIConstants.CALENDAR_DATETIME_FORMAT + format: UIConstants.CALENDAR_DATETIME_FORMAT }, - actionpath:"actionpath" + actionpath: "actionpath" }], columnclassname: [{ "Status Logs": "filter-input-0", - "Type":"filter-input-75", - "ID":"filter-input-50", - "Control ID":"filter-input-75", - "Cancelled":"filter-input-50", - "Duration (HH:mm:ss)":"filter-input-75", - "Template ID":"filter-input-50", + "Type": "filter-input-75", + "ID": "filter-input-50", + "Control ID": "filter-input-75", + "Cancelled": "filter-input-50", + "Duration (HH:mm:ss)": "filter-input-75", + "Template ID": "filter-input-50", // "BluePrint / Task Draft link": "filter-input-100", "Relative Start Time (HH:mm:ss)": "filter-input-75", "Relative End Time (HH:mm:ss)": "filter-input-75", - "Status":"filter-input-100", - "#Dataproducts":"filter-input-75", - "Data size":"filter-input-50", - "Data size on Disk":"filter-input-50", - "Subtask Content":"filter-input-75", - "BluePrint / Task Draft link":"filter-input-50", + "Status": "filter-input-100", + "#Dataproducts": "filter-input-75", + "Data size": "filter-input-50", + "Data size on Disk": "filter-input-50", + "Subtask Content": "filter-input-75", + "BluePrint / Task Draft link": "filter-input-50", }], stationGroup: [], - dialog: {header: 'Confirm', detail: 'Do you want to create a Scheduling Unit Blueprint?'}, + dialog: { header: 'Confirm', detail: 'Do you want to create a Scheduling Unit Blueprint?' }, dialogVisible: false, actions: [] } @@ -162,29 +167,48 @@ class ViewSchedulingUnit extends Component{ componentDidUpdate(prevProps, prevState) { if (this.state.scheduleunit && this.props.match.params && (this.state.scheduleunitId !== this.props.match.params.id || - this.state.scheduleunitType !== this.props.match.params.type)) { + this.state.scheduleunitType !== this.props.match.params.type)) { this.getSchedulingUnitDetails(this.props.match.params.type, this.props.match.params.id); - } + } } - + showTaskRelationDialog() { - this.setState({ showTaskRelationDialog: !this.state.showTaskRelationDialog}); + this.setState({ showTaskRelationDialog: !this.state.showTaskRelationDialog }); } - async componentDidMount(){ + async componentDidMount() { + this.setToggleBySorting(); let schedule_id = this.props.match.params.id; let schedule_type = this.props.match.params.type; if (schedule_type && schedule_id) { this.stations = await ScheduleService.getStationGroup(); - this.setState({stationOptions: this.stations}); + this.setState({ stationOptions: this.stations }); this.subtaskTemplates = await TaskService.getSubtaskTemplates(); this.getSchedulingUnitDetails(schedule_type, schedule_id); - } + } } - subtaskComponent = (task)=> { + toggleBySorting = (sortData) => { + UtilService.localStore({ type: 'set', key: this.lsKeySortColumn, value: sortData }); + } + + setToggleBySorting() { + let sortData = UtilService.localStore({ type: 'get', key: this.lsKeySortColumn }); + if (sortData) { + if (Object.prototype.toString.call(sortData) === '[object Array]') { + this.defaultSortColumn = sortData; + } + else { + this.defaultSortColumn = [{ ...sortData }]; + } + } + this.defaultSortColumn = this.defaultSortColumn || []; + UtilService.localStore({ type: 'set', key: this.lsKeySortColumn, value: [...this.defaultSortColumn] }); + } + + subtaskComponent = (task) => { return ( - <button className="p-link" onClick={(e) => {this.setState({showStatusLogs: true, task: task})}}> + <button className="p-link" onClick={(e) => { this.setState({ showStatusLogs: true, task: task }) }}> <i className="fa fa-history"></i> </button> ); @@ -192,27 +216,27 @@ class ViewSchedulingUnit extends Component{ getSchedulingUnitDetails(schedule_type, schedule_id) { ScheduleService.getSchedulingUnitExtended(schedule_type, schedule_id) - .then(async(schedulingUnit) =>{ + .then(async (schedulingUnit) => { if (schedulingUnit) { ScheduleService.getSchedulingConstraintTemplate(schedulingUnit.scheduling_constraints_template_id) .then((template) => { - this.setState({constraintTemplate: template}) - }); + this.setState({ constraintTemplate: template }) + }); if (schedulingUnit.draft_id) { await ScheduleService.getSchedulingUnitDraftById(schedulingUnit.draft_id).then((response) => { schedulingUnit['observation_strategy_template_id'] = response.observation_strategy_template_id; }); } - let tasks = schedulingUnit.task_drafts?(await this.getFormattedTaskDrafts(schedulingUnit)):this.getFormattedTaskBlueprints(schedulingUnit); - let ingestGroup = tasks.map(task => ({name: task.name, canIngest: task.canIngest, type_value: task.type_value, id: task.id })); + let tasks = schedulingUnit.task_drafts ? (await this.getFormattedTaskDrafts(schedulingUnit)) : this.getFormattedTaskBlueprints(schedulingUnit); + let ingestGroup = tasks.map(task => ({ name: task.name, canIngest: task.canIngest, type_value: task.type_value, id: task.id })); ingestGroup = _.groupBy(_.filter(ingestGroup, 'type_value'), 'type_value'); await Promise.all(tasks.map(async task => { - task.status_logs = task.tasktype === "Blueprint"?this.subtaskComponent(task):""; + task.status_logs = task.tasktype === "Blueprint" ? this.subtaskComponent(task) : ""; //Displaying SubTask ID of the 'control' Task - const subTaskIds = task.subTasks?task.subTasks.filter(sTask => sTask.subTaskTemplate.name.indexOf('control') >= 0):[]; + const subTaskIds = task.subTasks ? task.subTasks.filter(sTask => sTask.subTaskTemplate.name.indexOf('control') >= 0) : []; const promise = []; subTaskIds.map(subTask => promise.push(ScheduleService.getSubtaskOutputDataproduct(subTask.id))); - const dataProducts = promise.length > 0? await Promise.all(promise):[]; + const dataProducts = promise.length > 0 ? await Promise.all(promise) : []; task.dataProducts = []; task.size = 0; task.dataSizeOnDisk = 0; @@ -221,111 +245,117 @@ class ViewSchedulingUnit extends Component{ // task.start_time = moment(task.start_time).format(UIConstants.CALENDAR_DATETIME_FORMAT); // task.created_at = moment(task.created_at).format(UIConstants.CALENDAR_DATETIME_FORMAT); // task.updated_at = moment(task.updated_at).format(UIConstants.CALENDAR_DATETIME_FORMAT); - task.canSelect = task.tasktype.toLowerCase() === 'blueprint' ? true:(task.tasktype.toLowerCase() === 'draft' && task.blueprint_draft.length === 0)?true:false; + task.canSelect = task.tasktype.toLowerCase() === 'blueprint' ? true : (task.tasktype.toLowerCase() === 'draft' && task.blueprint_draft.length === 0) ? true : false; if (dataProducts.length && dataProducts[0].length) { task.dataProducts = dataProducts[0]; task.noOfOutputProducts = dataProducts[0].length; task.size = _.sumBy(dataProducts[0], 'size'); - task.dataSizeOnDisk = _.sumBy(dataProducts[0], function(product) { return product.deletedSince?0:product.size}); + task.dataSizeOnDisk = _.sumBy(dataProducts[0], function (product) { return product.deletedSince ? 0 : product.size }); task.size = UnitConverter.getUIResourceUnit('bytes', (task.size)); task.dataSizeOnDisk = UnitConverter.getUIResourceUnit('bytes', (task.dataSizeOnDisk)); } - task.subTaskID = subTaskIds.length ? subTaskIds[0].id : ''; + task.subTaskID = subTaskIds.length ? subTaskIds[0].id : ''; return task; })); - - const targetObservation = _.find(tasks, (task)=> {return task.template.type_value==='observation' && task.tasktype.toLowerCase()===schedule_type && task.specifications_doc.station_groups}); + + const targetObservation = _.find(tasks, (task) => { return task.template.type_value === 'observation' && task.tasktype.toLowerCase() === schedule_type && task.specifications_doc.station_groups }); this.setState({ scheduleunitId: schedule_id, - scheduleunit : schedulingUnit, + scheduleunit: schedulingUnit, scheduleunitType: schedule_type, - schedule_unit_task : tasks, + schedule_unit_task: tasks, isLoading: false, - stationGroup: targetObservation?targetObservation.specifications_doc.station_groups:[], + stationGroup: targetObservation ? targetObservation.specifications_doc.station_groups : [], redirect: null, dialogVisible: false, - ingestGroup}); + ingestGroup + }); this.selectedRows = []; // Add Action menu this.getActionMenu(schedule_type); - } else { + } else { this.setState({ isLoading: false, redirect: "/not-found" }); } }); - } + } /** * Get action menus for page header */ getActionMenu(schedule_type) { - this.actions =[]; + this.actions = []; let canDelete = (this.state.scheduleunit && - (!this.state.scheduleunit.scheduling_unit_blueprints_ids || this.state.scheduleunit.scheduling_unit_blueprints_ids.length === 0)); - this.actions.push({icon: 'fa fa-trash',title:!canDelete? 'Cannot delete Draft when Blueprint exists':'Scheduling Unit', - type: 'button', disabled: !canDelete, actOn: 'click', props:{ callback: this.showDeleteSUConfirmation}}); - - this.actions.push({icon: 'fa-window-close',title:'Click to Close Scheduling Unit View', link: this.props.history.goBack} ); + (!this.state.scheduleunit.scheduling_unit_blueprints_ids || this.state.scheduleunit.scheduling_unit_blueprints_ids.length === 0)); + this.actions.push({ + icon: 'fa fa-trash', title: !canDelete ? 'Cannot delete Draft when Blueprint exists' : 'Scheduling Unit', + type: 'button', disabled: !canDelete, actOn: 'click', props: { callback: this.showDeleteSUConfirmation } + }); + + this.actions.push({ icon: 'fa-window-close', title: 'Click to Close Scheduling Unit View', link: this.props.history.goBack }); if (this.props.match.params.type === 'draft') { - this.actions.unshift({icon:'fa-file-import', title: 'Data Products To Ingest', type:'button', - actOn:'click', props : { callback: this.showTaskRelationDialog} + this.actions.unshift({ + icon: 'fa-file-import', title: 'Data Products To Ingest', type: 'button', + actOn: 'click', props: { callback: this.showTaskRelationDialog } }); - this.actions.unshift({icon: 'fa-edit', title: 'Click to edit', props : { pathname:`/schedulingunit/edit/${ this.props.match.params.id}`} + this.actions.unshift({ + icon: 'fa-edit', title: 'Click to edit', props: { pathname: `/schedulingunit/edit/${this.props.match.params.id}` } }); - this.actions.unshift({icon:'fa-stamp', title: 'Create Blueprint', type:'button', - actOn:'click', props : { callback: this.checkAndCreateBlueprint}, + this.actions.unshift({ + icon: 'fa-stamp', title: 'Create Blueprint', type: 'button', + actOn: 'click', props: { callback: this.checkAndCreateBlueprint }, }); } else { - this.actions.unshift({icon: 'fa-sitemap',title :'View Workflow',props :{pathname:`/schedulingunit/${this.props.match.params.id}/workflow`}}); - this.actions.unshift({icon: 'fa-lock', title: 'Cannot edit blueprint'}); + this.actions.unshift({ icon: 'fa-sitemap', title: 'View Workflow', props: { pathname: `/schedulingunit/${this.props.match.params.id}/workflow` } }); + this.actions.unshift({ icon: 'fa-lock', title: 'Cannot edit blueprint' }); } - this.setState({actions: this.actions}); + this.setState({ actions: this.actions }); } - + /** * Formatting the task_drafts and task_blueprints in draft view to pass to the ViewTable component * @param {Object} schedulingUnit - scheduling_unit_draft object from extended API call loaded with tasks(draft & blueprint) along with their template and subtasks */ async getFormattedTaskDrafts(schedulingUnit) { - let scheduletasklist=[]; + let scheduletasklist = []; // Common keys for Task and Blueprint - let commonkeys = ['id','created_at','description','name','tags','updated_at','url','do_cancel','relative_start_time','relative_stop_time','start_time','stop_time','duration','status']; - for(const task of schedulingUnit.task_drafts){ + let commonkeys = ['id', 'created_at', 'description', 'name', 'tags', 'updated_at', 'url', 'do_cancel', 'relative_start_time', 'relative_stop_time', 'start_time', 'stop_time', 'duration', 'status']; + for (const task of schedulingUnit.task_drafts) { let scheduletask = {}; scheduletask['tasktype'] = 'Draft'; - scheduletask['actionpath'] = '/task/view/draft/'+task['id']; + scheduletask['actionpath'] = '/task/view/draft/' + task['id']; scheduletask['blueprint_draft'] = _.map(task['task_blueprints'], 'url'); scheduletask['status'] = task['status']; //fetch task draft details - for(const key of commonkeys){ + for (const key of commonkeys) { scheduletask[key] = task[key]; } scheduletask['specifications_doc'] = task['specifications_doc']; - scheduletask.duration = moment.utc((scheduletask.duration || 0)*1000).format(UIConstants.CALENDAR_TIME_FORMAT); - scheduletask.relative_start_time = moment.utc(scheduletask.relative_start_time*1000).format(UIConstants.CALENDAR_TIME_FORMAT); - scheduletask.relative_stop_time = moment.utc(scheduletask.relative_stop_time*1000).format(UIConstants.CALENDAR_TIME_FORMAT); + scheduletask.duration = moment.utc((scheduletask.duration || 0) * 1000).format(UIConstants.CALENDAR_TIME_FORMAT); + scheduletask.relative_start_time = moment.utc(scheduletask.relative_start_time * 1000).format(UIConstants.CALENDAR_TIME_FORMAT); + scheduletask.relative_stop_time = moment.utc(scheduletask.relative_stop_time * 1000).format(UIConstants.CALENDAR_TIME_FORMAT); scheduletask.template = task.specifications_template; scheduletask.type_value = task.specifications_template.type_value; scheduletask.produced_by = task.produced_by; scheduletask.produced_by_ids = task.produced_by_ids; - - for(const blueprint of task['task_blueprints']){ + + for (const blueprint of task['task_blueprints']) { let taskblueprint = {}; taskblueprint['tasktype'] = 'Blueprint'; - taskblueprint['actionpath'] = '/task/view/blueprint/'+blueprint['id']; + taskblueprint['actionpath'] = '/task/view/blueprint/' + blueprint['id']; taskblueprint['blueprint_draft'] = blueprint['draft']; taskblueprint['status'] = blueprint['status']; - - for(const key of commonkeys){ + + for (const key of commonkeys) { taskblueprint[key] = blueprint[key]; } taskblueprint['created_at'] = moment(blueprint['created_at'], moment.ISO_8601).format(UIConstants.CALENDAR_DATETIME_FORMAT); taskblueprint['updated_at'] = moment(blueprint['updated_at'], moment.ISO_8601).format(UIConstants.CALENDAR_DATETIME_FORMAT); - taskblueprint.duration = moment.utc((taskblueprint.duration || 0)*1000).format(UIConstants.CALENDAR_TIME_FORMAT); - taskblueprint.relative_start_time = moment.utc(taskblueprint.relative_start_time*1000).format(UIConstants.CALENDAR_TIME_FORMAT); - taskblueprint.relative_stop_time = moment.utc(taskblueprint.relative_stop_time*1000).format(UIConstants.CALENDAR_TIME_FORMAT); + taskblueprint.duration = moment.utc((taskblueprint.duration || 0) * 1000).format(UIConstants.CALENDAR_TIME_FORMAT); + taskblueprint.relative_start_time = moment.utc(taskblueprint.relative_start_time * 1000).format(UIConstants.CALENDAR_TIME_FORMAT); + taskblueprint.relative_stop_time = moment.utc(taskblueprint.relative_stop_time * 1000).format(UIConstants.CALENDAR_TIME_FORMAT); taskblueprint.template = scheduletask.template; taskblueprint.subTasks = blueprint.subtasks; for (const subtask of taskblueprint.subTasks) { @@ -355,13 +385,13 @@ class ViewSchedulingUnit extends Component{ */ getFormattedTaskBlueprints(schedulingUnit) { let taskBlueprintsList = []; - for(const taskBlueprint of schedulingUnit.task_blueprints) { + for (const taskBlueprint of schedulingUnit.task_blueprints) { taskBlueprint['tasktype'] = 'Blueprint'; - taskBlueprint['actionpath'] = '/task/view/blueprint/'+taskBlueprint['id']; + taskBlueprint['actionpath'] = '/task/view/blueprint/' + taskBlueprint['id']; taskBlueprint['blueprint_draft'] = taskBlueprint['draft']; taskBlueprint['relative_start_time'] = 0; taskBlueprint['relative_stop_time'] = 0; - taskBlueprint.duration = moment.utc((taskBlueprint.duration || 0)*1000).format(UIConstants.CALENDAR_TIME_FORMAT); + taskBlueprint.duration = moment.utc((taskBlueprint.duration || 0) * 1000).format(UIConstants.CALENDAR_TIME_FORMAT); taskBlueprint.template = taskBlueprint.specifications_template; for (const subtask of taskBlueprint.subtasks) { subtask.subTaskTemplate = _.find(this.subtaskTemplates, ['id', subtask.specifications_template_id]); @@ -372,15 +402,15 @@ class ViewSchedulingUnit extends Component{ return taskBlueprintsList; } - getScheduleUnitTasks(type, scheduleunit){ - if(type === 'draft') + getScheduleUnitTasks(type, scheduleunit) { + if (type === 'draft') return ScheduleService.getTasksBySchedulingUnit(scheduleunit.id, true, true, true); else return ScheduleService.getTaskBPWithSubtaskTemplateOfSU(scheduleunit); } - - getScheduleUnit(type, id){ - if(type === 'draft') + + getScheduleUnit(type, id) { + if (type === 'draft') return ScheduleService.getSchedulingUnitDraftById(id) else return ScheduleService.getSchedulingUnitBlueprintById(id) @@ -396,14 +426,14 @@ class ViewSchedulingUnit extends Component{ dialog.onSubmit = this.createBlueprintTree; dialog.content = null; dialog.width = null; - if (this.state.scheduleunit.scheduling_unit_blueprints.length>0) { + if (this.state.scheduleunit.scheduling_unit_blueprints.length > 0) { dialog.detail = "Blueprint(s) already exist for this Scheduling Unit. Do you want to create another one?"; - } else { - dialog.detail ="Do you want to create a Scheduling Unit Blueprint?"; + } else { + dialog.detail = "Do you want to create a Scheduling Unit Blueprint?"; } - dialog.actions = [{id: 'yes', title: 'Yes', callback: this.createBlueprintTree}, - {id: 'no', title: 'No', callback: this.closeDialog}]; - this.setState({dialogVisible: true, dialog: dialog}); + dialog.actions = [{ id: 'yes', title: 'Yes', callback: this.createBlueprintTree }, + { id: 'no', title: 'No', callback: this.closeDialog }]; + this.setState({ dialogVisible: true, dialog: dialog }); } } @@ -411,15 +441,15 @@ class ViewSchedulingUnit extends Component{ * Funtion called to create blueprint on confirmation. */ createBlueprintTree() { - this.setState({dialogVisible: false, showSpinner: true}); + this.setState({ dialogVisible: false, showSpinner: true }); ScheduleService.createSchedulingUnitBlueprintTree(this.state.scheduleunit.id) .then(blueprint => { if (blueprint) { - appGrowl.show({severity: 'success', summary: 'Success', detail: 'Blueprint created successfully!'}); - this.setState({showSpinner: false, redirect: `/schedulingunit/view/blueprint/${blueprint.id}`, isLoading: true}); - } else { - appGrowl.show({severity: 'error', summary: 'Failed', detail: 'Unable to create blueprint!'}); - this.setState({showSpinner: false}); + appGrowl.show({ severity: 'success', summary: 'Success', detail: 'Blueprint created successfully!' }); + this.setState({ showSpinner: false, redirect: `/schedulingunit/view/blueprint/${blueprint.id}`, isLoading: true }); + } else { + appGrowl.show({ severity: 'error', summary: 'Failed', detail: 'Unable to create blueprint!' }); + this.setState({ showSpinner: false }); } }); } @@ -428,46 +458,46 @@ class ViewSchedulingUnit extends Component{ * Callback function to close the dialog prompted. */ closeDialog() { - this.setState({dialogVisible: false}); + this.setState({ dialogVisible: false }); } - + onRowSelection(selectedRows) { this.selectedRows = selectedRows; } - + /** * Confirmation dialog for delete task(s) */ confirmDeleteTasks() { - if(this.selectedRows.length === 0) { - appGrowl.show({severity: 'info', summary: 'Select Row', detail: 'Select Task to delete.'}); - } else { + if (this.selectedRows.length === 0) { + appGrowl.show({ severity: 'info', summary: 'Select Row', detail: 'Select Task to delete.' }); + } else { let dialog = this.state.dialog; dialog.type = "confirmation"; - dialog.header= "Confirm to Delete Task(s)"; + dialog.header = "Confirm to Delete Task(s)"; dialog.detail = "Do you want to delete the selected Task(s)?"; dialog.content = this.getTaskDialogContent; - dialog.actions = [{id: 'yes', title: 'Yes', callback: this.deleteTasks}, - {id: 'no', title: 'No', callback: this.closeDialog}]; + dialog.actions = [{ id: 'yes', title: 'Yes', callback: this.deleteTasks }, + { id: 'no', title: 'No', callback: this.closeDialog }]; dialog.onSubmit = this.deleteTasks; dialog.width = '55vw'; dialog.showIcon = false; - this.setState({dialog: dialog, dialogVisible: true}); + this.setState({ dialog: dialog, dialogVisible: true }); } } - + showDeleteSUConfirmation() { let dialog = this.state.dialog; dialog.type = "confirmation"; - dialog.header= "Confirm to Delete Scheduling Unit"; + dialog.header = "Confirm to Delete Scheduling Unit"; dialog.detail = "Do you want to delete this Scheduling Unit?"; dialog.content = this.getSUDialogContent; - dialog.actions = [{id: 'yes', title: 'Yes', callback: this.deleteSchedulingUnit}, - {id: 'no', title: 'No', callback: this.closeDialog}]; + dialog.actions = [{ id: 'yes', title: 'Yes', callback: this.deleteSchedulingUnit }, + { id: 'no', title: 'No', callback: this.closeDialog }]; dialog.onSubmit = this.deleteSchedulingUnit; dialog.width = '55vw'; dialog.showIcon = false; - this.setState({dialog: dialog, dialogVisible: true}); + this.setState({ dialog: dialog, dialogVisible: true }); } /** @@ -475,19 +505,21 @@ class ViewSchedulingUnit extends Component{ */ getTaskDialogContent() { let selectedTasks = []; - for(const obj of this.selectedRows) { - selectedTasks.push({id:obj.id, suId: this.state.scheduleunit.id, suName: this.state.scheduleunit.name, - taskId: obj.id, controlId: obj.subTaskID, taskName: obj.name, status: obj.status}); - } - return <> - <DataTable value={selectedTasks} resizableColumns columnResizeMode="expand" className="card" style={{paddingLeft: '0em'}}> - <Column field="suId" header="Scheduling Unit Id"></Column> - <Column field="suName" header="Scheduling Unit Name"></Column> - <Column field="taskId" header="Task Id"></Column> - <Column field="controlId" header="Control Id"></Column> - <Column field="taskName" header="Task Name"></Column> - <Column field="status" header="Status"></Column> - </DataTable> + for (const obj of this.selectedRows) { + selectedTasks.push({ + id: obj.id, suId: this.state.scheduleunit.id, suName: this.state.scheduleunit.name, + taskId: obj.id, controlId: obj.subTaskID, taskName: obj.name, status: obj.status + }); + } + return <> + <DataTable value={selectedTasks} resizableColumns columnResizeMode="expand" className="card" style={{ paddingLeft: '0em' }}> + <Column field="suId" header="Scheduling Unit Id"></Column> + <Column field="suName" header="Scheduling Unit Name"></Column> + <Column field="taskId" header="Task Id"></Column> + <Column field="controlId" header="Control Id"></Column> + <Column field="taskName" header="Task Name"></Column> + <Column field="status" header="Status"></Column> + </DataTable> </> } @@ -495,9 +527,9 @@ class ViewSchedulingUnit extends Component{ * Prepare Scheduling Unit details to show on confirmation dialog */ getSUDialogContent() { - let selectedTasks = [{suId: this.state.scheduleunit.id, suName: this.state.scheduleunit.name, suType: (this.state.scheduleunit.draft)?'Blueprint': 'Draft'}]; - return <> - <DataTable value={selectedTasks} resizableColumns columnResizeMode="expand" className="card" style={{paddingLeft: '0em'}}> + let selectedTasks = [{ suId: this.state.scheduleunit.id, suName: this.state.scheduleunit.name, suType: (this.state.scheduleunit.draft) ? 'Blueprint' : 'Draft' }]; + return <> + <DataTable value={selectedTasks} resizableColumns columnResizeMode="expand" className="card" style={{ paddingLeft: '0em' }}> <Column field="suId" header="Scheduling Unit Id"></Column> <Column field="suName" header="Scheduling Unit Name"></Column> <Column field="suType" header="Type"></Column> @@ -510,117 +542,117 @@ class ViewSchedulingUnit extends Component{ */ async deleteTasks() { let hasError = false; - for(const task of this.selectedRows) { - if(!await TaskService.deleteTask(task.tasktype, task.id)) { + for (const task of this.selectedRows) { + if (!await TaskService.deleteTask(task.tasktype, task.id)) { hasError = true; } } - if(hasError){ - appGrowl.show({severity: 'error', summary: 'error', detail: 'Error while deleting Task(s)'}); - this.setState({dialogVisible: false}); - } else { + if (hasError) { + appGrowl.show({ severity: 'error', summary: 'error', detail: 'Error while deleting Task(s)' }); + this.setState({ dialogVisible: false }); + } else { this.selectedRows = []; - this.setState({dialogVisible: false}); + this.setState({ dialogVisible: false }); this.componentDidMount(); - appGrowl.show({severity: 'success', summary: 'Success', detail: 'Task(s) deleted successfully'}); + appGrowl.show({ severity: 'success', summary: 'Success', detail: 'Task(s) deleted successfully' }); } } - /** - * Delete Scheduling Unit - */ + /** + * Delete Scheduling Unit + */ async deleteSchedulingUnit() { let hasError = false; - if(!await ScheduleService.deleteSchedulingUnit(this.state.scheduleunitType, this.state.scheduleunit.id)) { + if (!await ScheduleService.deleteSchedulingUnit(this.state.scheduleunitType, this.state.scheduleunit.id)) { hasError = true; } - if(hasError){ - appGrowl.show({severity: 'error', summary: 'error', detail: 'Error while deleting scheduling Unit'}); - this.setState({dialogVisible: false}); - } else { + if (hasError) { + appGrowl.show({ severity: 'error', summary: 'error', detail: 'Error while deleting scheduling Unit' }); + this.setState({ dialogVisible: false }); + } else { this.selectedRows = []; - appGrowl.show({severity: 'success', summary: 'Success', detail: 'Scheduling Unit is deleted successfully'}); - this.setState({dialogVisible: false, redirect: '/schedulingunit'}); + appGrowl.show({ severity: 'success', summary: 'Success', detail: 'Scheduling Unit is deleted successfully' }); + this.setState({ dialogVisible: false, redirect: '/schedulingunit' }); } } - - render(){ + + render() { if (this.state.redirect) { - return <Redirect to={ {pathname: this.state.redirect} }></Redirect> + return <Redirect to={{ pathname: this.state.redirect }}></Redirect> } - return( - <> - <PageHeader location={this.props.location} title={'Scheduling Unit - Details'} - actions={this.state.actions}/> - { this.state.isLoading ? <AppLoader/> :this.state.scheduleunit && - <> - <div className="main-content"> - <div className="p-grid"> - <label className="col-lg-2 col-md-2 col-sm-12">Name</label> - <span className="p-col-lg-4 col-md-4 col-sm-12">{this.state.scheduleunit.name}</span> - <label className="col-lg-2 col-md-2 col-sm-12">Description</label> - <span className="col-lg-4 col-md-4 col-sm-12">{this.state.scheduleunit.description}</span> - </div> - <div className="p-grid"> - <label className="col-lg-2 col-md-2 col-sm-12">Created At</label> - <span className="col-lg-4 col-md-4 col-sm-12">{this.state.scheduleunit.created_at && moment(this.state.scheduleunit.created_at,moment.ISO_8601).format(UIConstants.CALENDAR_DATETIME_FORMAT)}</span> - <label className="col-lg-2 col-md-2 col-sm-12">Updated At</label> - <span className="col-lg-4 col-md-4 col-sm-12">{this.state.scheduleunit.created_at && moment(this.state.scheduleunit.updated_at,moment.ISO_8601).format(UIConstants.CALENDAR_DATETIME_FORMAT)}</span> - </div> - <div className="p-grid"> - <label className="col-lg-2 col-md-2 col-sm-12">Start Time</label> - <span className="col-lg-4 col-md-4 col-sm-12">{this.state.scheduleunit.start_time && moment(this.state.scheduleunit.start_time).format(UIConstants.CALENDAR_DATETIME_FORMAT)}</span> - <label className="col-lg-2 col-md-2 col-sm-12">End Time</label> - <span className="col-lg-4 col-md-4 col-sm-12">{this.state.scheduleunit.stop_time && moment(this.state.scheduleunit.stop_time).format(UIConstants.CALENDAR_DATETIME_FORMAT)}</span> - </div> - <div className="p-grid"> - <label className="col-lg-2 col-md-2 col-sm-12" >Duration (HH:mm:ss)</label> - <span className="col-lg-4 col-md-4 col-sm-12">{moment.utc((this.state.scheduleunit.duration?this.state.scheduleunit.duration:0)*1000).format(UIConstants.CALENDAR_TIME_FORMAT)}</span> - <label className="col-lg-2 col-md-2 col-sm-12">Template ID</label> - <span className="col-lg-4 col-md-4 col-sm-12">{this.state.scheduleunit.observation_strategy_template_id}</span> - </div> - <div className="p-grid"> - {this.state.scheduleunit.scheduling_set_object.project_id && - <> - <label className="col-lg-2 col-md-2 col-sm-12">Project</label> - <span className="col-lg-4 col-md-4 col-sm-12"> - <Link to={`/project/view/${this.state.scheduleunit.scheduling_set_object.project_id}`}>{this.state.scheduleunit.scheduling_set_object.project_id}</Link> - </span> - </> + return ( + <> + <PageHeader location={this.props.location} title={'Scheduling Unit - Details'} + actions={this.state.actions} /> + { this.state.isLoading ? <AppLoader /> : this.state.scheduleunit && + <> + <div className="main-content"> + <div className="p-grid"> + <label className="col-lg-2 col-md-2 col-sm-12">Name</label> + <span className="p-col-lg-4 col-md-4 col-sm-12">{this.state.scheduleunit.name}</span> + <label className="col-lg-2 col-md-2 col-sm-12">Description</label> + <span className="col-lg-4 col-md-4 col-sm-12">{this.state.scheduleunit.description}</span> + </div> + <div className="p-grid"> + <label className="col-lg-2 col-md-2 col-sm-12">Created At</label> + <span className="col-lg-4 col-md-4 col-sm-12">{this.state.scheduleunit.created_at && moment(this.state.scheduleunit.created_at, moment.ISO_8601).format(UIConstants.CALENDAR_DATETIME_FORMAT)}</span> + <label className="col-lg-2 col-md-2 col-sm-12">Updated At</label> + <span className="col-lg-4 col-md-4 col-sm-12">{this.state.scheduleunit.created_at && moment(this.state.scheduleunit.updated_at, moment.ISO_8601).format(UIConstants.CALENDAR_DATETIME_FORMAT)}</span> + </div> + <div className="p-grid"> + <label className="col-lg-2 col-md-2 col-sm-12">Start Time</label> + <span className="col-lg-4 col-md-4 col-sm-12">{this.state.scheduleunit.start_time && moment(this.state.scheduleunit.start_time).format(UIConstants.CALENDAR_DATETIME_FORMAT)}</span> + <label className="col-lg-2 col-md-2 col-sm-12">End Time</label> + <span className="col-lg-4 col-md-4 col-sm-12">{this.state.scheduleunit.stop_time && moment(this.state.scheduleunit.stop_time).format(UIConstants.CALENDAR_DATETIME_FORMAT)}</span> + </div> + <div className="p-grid"> + <label className="col-lg-2 col-md-2 col-sm-12" >Duration (HH:mm:ss)</label> + <span className="col-lg-4 col-md-4 col-sm-12">{moment.utc((this.state.scheduleunit.duration ? this.state.scheduleunit.duration : 0) * 1000).format(UIConstants.CALENDAR_TIME_FORMAT)}</span> + <label className="col-lg-2 col-md-2 col-sm-12">Template ID</label> + <span className="col-lg-4 col-md-4 col-sm-12">{this.state.scheduleunit.observation_strategy_template_id}</span> + </div> + <div className="p-grid"> + {this.state.scheduleunit.scheduling_set_object.project_id && + <> + <label className="col-lg-2 col-md-2 col-sm-12">Project</label> + <span className="col-lg-4 col-md-4 col-sm-12"> + <Link to={`/project/view/${this.state.scheduleunit.scheduling_set_object.project_id}`}>{this.state.scheduleunit.scheduling_set_object.project_id}</Link> + </span> + </> } - <label className="col-lg-2 col-md-2 col-sm-12">Scheduling set</label> - <span className="col-lg-4 col-md-4 col-sm-12">{this.state.scheduleunit.scheduling_set_object.name}</span> - </div> - <div className="p-grid"> - <label className="col-lg-2 col-md-2 col-sm-12">{this.props.match.params.type === 'blueprint' ? 'Draft' : 'Blueprints'}</label> - <span className="col-lg-4 col-md-4 col-sm-12"> - <ul className="task-list"> - {(this.state.scheduleunit.blueprintList || []).map(blueprint => ( - <li> - <Link to={{ pathname: `/schedulingunit/view/blueprint/${blueprint.id}`}}>{blueprint.name}</Link> - </li>))} - {this.state.scheduleunit.draft_object && - <li> - <Link to={{ pathname: `/schedulingunit/view/draft/${this.state.scheduleunit.draft_object.id}` }}> - {this.state.scheduleunit.draft_object.name} - </Link> - </li>} - </ul> - </span> - {this.props.match.params.type === 'blueprint' && - <label className="col-lg-2 col-md-2 col-sm-12 ">Status</label> } - {this.props.match.params.type === 'blueprint' && - <span className="col-lg-2 col-md-2 col-sm-12">{this.state.scheduleunit.status}</span>} - </div> - <div className="p-grid"> - <label className="col-lg-2 col-md-2 col-sm-12">Tags</label> - <Chips className="p-col-4 chips-readonly" disabled value={this.state.scheduleunit.tags}></Chips> + <label className="col-lg-2 col-md-2 col-sm-12">Scheduling set</label> + <span className="col-lg-4 col-md-4 col-sm-12">{this.state.scheduleunit.scheduling_set_object.name}</span> + </div> + <div className="p-grid"> + <label className="col-lg-2 col-md-2 col-sm-12">{this.props.match.params.type === 'blueprint' ? 'Draft' : 'Blueprints'}</label> + <span className="col-lg-4 col-md-4 col-sm-12"> + <ul className="task-list"> + {(this.state.scheduleunit.blueprintList || []).map(blueprint => ( + <li> + <Link to={{ pathname: `/schedulingunit/view/blueprint/${blueprint.id}` }}>{blueprint.name}</Link> + </li>))} + {this.state.scheduleunit.draft_object && + <li> + <Link to={{ pathname: `/schedulingunit/view/draft/${this.state.scheduleunit.draft_object.id}` }}> + {this.state.scheduleunit.draft_object.name} + </Link> + </li>} + </ul> + </span> + {this.props.match.params.type === 'blueprint' && + <label className="col-lg-2 col-md-2 col-sm-12 ">Status</label>} + {this.props.match.params.type === 'blueprint' && + <span className="col-lg-2 col-md-2 col-sm-12">{this.state.scheduleunit.status}</span>} + </div> + <div className="p-grid"> + <label className="col-lg-2 col-md-2 col-sm-12">Tags</label> + <Chips className="p-col-4 chips-readonly" disabled value={this.state.scheduleunit.tags}></Chips> + </div> + </div> - - </div> - </> - } - + </> + } + <div> <h3>Tasks Details</h3> </div> @@ -634,85 +666,88 @@ class ViewSchedulingUnit extends Component{ paths - specify the path for navigation - Table will set "id" value for each row in action button */} - + <div className="delete-option"> <div > <span className="p-float-label"> {this.state.schedule_unit_task && this.state.schedule_unit_task.length > 0 && - <a href="#" onClick={this.confirmDeleteTasks} title="Delete selected Task(s)"> + <a href="#" onClick={this.confirmDeleteTasks} title="Delete selected Task(s)"> <i class="fa fa-trash" aria-hidden="true" ></i> </a> } </span> - </div> + </div> </div> - {this.state.isLoading ? <AppLoader/> : (this.state.schedule_unit_task.length>0 )? - <ViewTable - data={this.state.schedule_unit_task} + {this.state.isLoading ? <AppLoader /> : (this.state.schedule_unit_task.length > 0) ? + <ViewTable + data={this.state.schedule_unit_task} defaultcolumns={this.state.defaultcolumns} optionalcolumns={this.state.optionalcolumns} columnclassname={this.state.columnclassname} columnOrders={this.state.columnOrders} - defaultSortColumn={this.state.defaultSortColumn} showaction="true" keyaccessor="id" paths={this.state.paths} unittest={this.state.unittest} tablename="scheduleunit_task_list" allowRowSelection={true} - onRowSelection = {this.onRowSelection} + onRowSelection={this.onRowSelection} + ignoreSorting={this.ignoreSorting} + lsKeySortColumn={this.lsKeySortColumn} + toggleBySorting={(sortData) => this.toggleBySorting(sortData)} + defaultSortColumn={this.defaultSortColumn} /> - :<div>No Tasks found</div> + : <div>No Tasks found</div> } - - {!this.state.isLoading && + + {!this.state.isLoading && <> - {(this.state.stationGroup && this.state.stationGroup.length > 0 )? - <Stations - stationGroup={this.state.stationGroup} - targetObservation={this.state.targetObservation} - view - /> - :<> - <div style={{marginTop: "10px"}}> - <h3>Station Groups</h3> - </div> - <div>No Station Groups Specified</div> - </> - } + {(this.state.stationGroup && this.state.stationGroup.length > 0) ? + <Stations + stationGroup={this.state.stationGroup} + targetObservation={this.state.targetObservation} + view + /> + : <> + <div style={{ marginTop: "10px" }}> + <h3>Station Groups</h3> + </div> + <div>No Station Groups Specified</div> + </> + } - {this.state.scheduleunit && this.state.scheduleunit.scheduling_constraints_doc && - <SchedulingConstraint disable constraintTemplate={this.state.constraintTemplate} + {this.state.scheduleunit && this.state.scheduleunit.scheduling_constraints_doc && + <SchedulingConstraint disable constraintTemplate={this.state.constraintTemplate} initValue={this.state.scheduleunit.scheduling_constraints_doc} />} </> } {this.state.showStatusLogs && - <Dialog header={`Status change logs - ${this.state.task?this.state.task.name:""}`} - visible={this.state.showStatusLogs} maximizable maximized={false} position="left" style={{ width: '50vw' }} - onHide={() => {this.setState({showStatusLogs: false})}}> - <TaskStatusLogs taskId={this.state.task.id}></TaskStatusLogs> + <Dialog header={`Status change logs - ${this.state.task ? this.state.task.name : ""}`} + visible={this.state.showStatusLogs} maximizable maximized={false} position="left" style={{ width: '50vw' }} + onHide={() => { this.setState({ showStatusLogs: false }) }}> + <TaskStatusLogs taskId={this.state.task.id}></TaskStatusLogs> </Dialog> - } + } {/* Dialog component to show messages and get confirmation */} - + <CustomDialog type="confirmation" visible={this.state.dialogVisible} - header={this.state.dialog.header} message={this.state.dialog.detail} actions={this.state.dialog.actions} - content={this.state.dialog.content} width={this.state.dialog.width} showIcon={this.state.dialog.showIcon} - onClose={this.closeDialog} onCancel={this.closeDialog} onSubmit={this.state.dialog.onSubmit}/> - + header={this.state.dialog.header} message={this.state.dialog.detail} actions={this.state.dialog.actions} + content={this.state.dialog.content} width={this.state.dialog.width} showIcon={this.state.dialog.showIcon} + onClose={this.closeDialog} onCancel={this.closeDialog} onSubmit={this.state.dialog.onSubmit} /> + {/* Show spinner during backend API call */} <CustomPageSpinner visible={this.state.showSpinner} /> {/* To show Data Products To Ingest */} {this.state.showTaskRelationDialog && ( - <Schedulingtaskrelation - showTaskRelationDialog={this.state.showTaskRelationDialog} - ingestGroup={this.state.ingestGroup} - toggle={this.showTaskRelationDialog} - - /> + <Schedulingtaskrelation + showTaskRelationDialog={this.state.showTaskRelationDialog} + ingestGroup={this.state.ingestGroup} + toggle={this.showTaskRelationDialog} + + /> )} - </> + </> ) } } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/list.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/list.js index 5bdef19b88263dc4fab8d695fc6ae02f9d2f7f49..4e217041ca0662dc7a9522dbcd78e08808ac481e 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/list.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/list.js @@ -1,5 +1,5 @@ -import React, {Component} from 'react'; -import {Redirect} from 'react-router-dom' +import React, { Component } from 'react'; +import { Redirect } from 'react-router-dom' import moment from 'moment'; import { Dialog } from 'primereact/dialog'; import { DataTable } from 'primereact/datatable'; @@ -15,8 +15,12 @@ import { appGrowl } from '../../layout/components/AppGrowl'; import { CustomDialog } from '../../layout/components/CustomDialog'; import ScheduleService from '../../services/schedule.service'; import UnitConverter from '../../utils/unit.converter'; +import UtilService from '../../services/util.service'; export class TaskList extends Component { + lsKeySortColumn = "TaskListSortData"; + // The following values should be lower case + ignoreSorting = ['status logs']; constructor(props) { super(props); this.state = { @@ -27,104 +31,104 @@ export class TaskList extends Component { }], columnOrders: [ "Status Logs", - "Status", - "Type", - "Scheduling Unit ID", - "Scheduling Unit Name", - "ID", - "Control ID", - "Name", - "Description", - "Start Time", - "End Time", - "Duration (HH:mm:ss)", - "Relative Start Time (HH:mm:ss)", - "Relative End Time (HH:mm:ss)", - "#Dataproducts", - "size", - "dataSizeOnDisk", - "subtaskContent", - "tags", - "blueprint_draft", - "url", - "Cancelled", - "Created at", - "Updated at" - ], + "Status", + "Type", + "Scheduling Unit ID", + "Scheduling Unit Name", + "ID", + "Control ID", + "Name", + "Description", + "Start Time", + "End Time", + "Duration (HH:mm:ss)", + "Relative Start Time (HH:mm:ss)", + "Relative End Time (HH:mm:ss)", + "#Dataproducts", + "size", + "dataSizeOnDisk", + "subtaskContent", + "tags", + "blueprint_draft", + "url", + "Cancelled", + "Created at", + "Updated at" + ], dialog: {}, - defaultcolumns: [ { + defaultcolumns: [{ status_logs: "Status Logs", - status:{ - name:"Status", + status: { + name: "Status", filter: "select" }, - tasktype:{ - name:"Type", - filter:"select" + tasktype: { + name: "Type", + filter: "select" }, schedulingUnitId: "Scheduling Unit ID", schedulingUnitName: "Scheduling Unit Name", id: "ID", subTaskID: 'Control ID', - name:"Name", - description:"Description", - start_time:{ - name:"Start Time", + name: "Name", + description: "Description", + start_time: { + name: "Start Time", filter: "date", - format:UIConstants.CALENDAR_DATETIME_FORMAT + format: UIConstants.CALENDAR_DATETIME_FORMAT }, - stop_time:{ - name:"End Time", + stop_time: { + name: "End Time", filter: "date", - format:UIConstants.CALENDAR_DATETIME_FORMAT + format: UIConstants.CALENDAR_DATETIME_FORMAT }, - duration:"Duration (HH:mm:ss)", - relative_start_time:"Relative Start Time (HH:mm:ss)", - relative_stop_time:"Relative End Time (HH:mm:ss)", + duration: "Duration (HH:mm:ss)", + relative_start_time: "Relative Start Time (HH:mm:ss)", + relative_stop_time: "Relative End Time (HH:mm:ss)", noOfOutputProducts: "#Dataproducts", - do_cancel:{ + do_cancel: { name: "Cancelled", filter: "switch" }, }], - optionalcolumns: [{ + optionalcolumns: [{ size: "Data size", dataSizeOnDisk: "Data size on Disk", subtaskContent: "Subtask Content", - tags:"Tags", - blueprint_draft:"BluePrint / Task Draft link", - url:"API URL", - created_at:{ + tags: "Tags", + blueprint_draft: "BluePrint / Task Draft link", + url: "API URL", + created_at: { name: "Created at", filter: "date", - format:UIConstants.CALENDAR_DATETIME_FORMAT + format: UIConstants.CALENDAR_DATETIME_FORMAT }, - updated_at:{ + updated_at: { name: "Updated at", filter: "date", - format:UIConstants.CALENDAR_DATETIME_FORMAT + format: UIConstants.CALENDAR_DATETIME_FORMAT }, - actionpath:"actionpath" + actionpath: "actionpath" }], columnclassname: [{ "Status Logs": "filter-input-0", - "Type":"filter-input-75", + "Type": "filter-input-75", "Scheduling Unit ID": "filter-input-50", "Scheduling Unit Name": "filter-input-100", - "ID":"filter-input-50", - "Control ID":"filter-input-75", - "Cancelled":"filter-input-50", - "Duration (HH:mm:ss)":"filter-input-75", - "Template ID":"filter-input-50", + "ID": "filter-input-50", + "Control ID": "filter-input-75", + "Cancelled": "filter-input-50", + "Duration (HH:mm:ss)": "filter-input-75", + "Template ID": "filter-input-50", // "BluePrint / Task Draft link": "filter-input-100", "Relative Start Time (HH:mm:ss)": "filter-input-75", "Relative End Time (HH:mm:ss)": "filter-input-75", - "Status":"filter-input-100", - "#Dataproducts":"filter-input-75", - "Data size":"filter-input-50", - "Data size on Disk":"filter-input-50", - "Subtask Content":"filter-input-75", - "BluePrint / Task Draft link":"filter-input-50", + "Status": "filter-input-100", + "#Dataproducts": "filter-input-75", + "Data size": "filter-input-50", + "Data size on Disk": "filter-input-50", + "Subtask Content": "filter-input-75", + "BluePrint / Task Draft link": "filter-input-50", }] }; this.selectedRows = []; @@ -136,9 +140,9 @@ export class TaskList extends Component { this.getTaskDialogContent = this.getTaskDialogContent.bind(this); } - subtaskComponent = (task)=> { + subtaskComponent = (task) => { return ( - <button className="p-link" onClick={(e) => {this.setState({showStatusLogs: true, task: task})}}> + <button className="p-link" onClick={(e) => { this.setState({ showStatusLogs: true, task: task }) }}> <i className="fa fa-history"></i> </button> ); @@ -149,23 +153,23 @@ export class TaskList extends Component { * Formatting the task_blueprints in blueprint view to pass to the ViewTable component * @param {Object} schedulingUnit - scheduling_unit_blueprint object from extended API call loaded with tasks(blueprint) along with their template and subtasks */ - getFormattedTaskBlueprints(schedulingUnit) { + getFormattedTaskBlueprints(schedulingUnit) { let taskBlueprintsList = []; - for(const taskBlueprint of schedulingUnit.task_blueprints) { + for (const taskBlueprint of schedulingUnit.task_blueprints) { taskBlueprint['status_logs'] = this.subtaskComponent(taskBlueprint); taskBlueprint['tasktype'] = 'Blueprint'; - taskBlueprint['actionpath'] = '/task/view/blueprint/'+taskBlueprint['id']; + taskBlueprint['actionpath'] = '/task/view/blueprint/' + taskBlueprint['id']; taskBlueprint['blueprint_draft'] = taskBlueprint['draft']; taskBlueprint['relative_start_time'] = 0; taskBlueprint['relative_stop_time'] = 0; - taskBlueprint.duration = moment.utc((taskBlueprint.duration || 0)*1000).format(UIConstants.CALENDAR_TIME_FORMAT); - taskBlueprint.template = taskBlueprint.specifications_template; + taskBlueprint.duration = moment.utc((taskBlueprint.duration || 0) * 1000).format(UIConstants.CALENDAR_TIME_FORMAT); + taskBlueprint.template = taskBlueprint.specifications_template; taskBlueprint.schedulingUnitName = schedulingUnit.name; for (const subtask of taskBlueprint.subtasks) { subtask.subTaskTemplate = _.find(this.subtaskTemplates, ['id', subtask.specifications_template_id]); } taskBlueprint.schedulingUnitId = taskBlueprint.scheduling_unit_blueprint_id; - taskBlueprint.subTasks = taskBlueprint.subtasks; + taskBlueprint.subTasks = taskBlueprint.subtasks; taskBlueprintsList.push(taskBlueprint); } return taskBlueprintsList; @@ -175,25 +179,25 @@ export class TaskList extends Component { * Formatting the task_drafts and task_blueprints in draft view to pass to the ViewTable component * @param {Object} schedulingUnit - scheduling_unit_draft object from extended API call loaded with tasks(draft & blueprint) along with their template and subtasks */ - getFormattedTaskDrafts(schedulingUnit) { - let scheduletasklist=[]; + getFormattedTaskDrafts(schedulingUnit) { + let scheduletasklist = []; // Common keys for Task and Blueprint - let commonkeys = ['id','created_at','description','name','tags','updated_at','url','do_cancel','relative_start_time','relative_stop_time','start_time','stop_time','duration','status']; - for(const task of schedulingUnit.task_drafts){ + let commonkeys = ['id', 'created_at', 'description', 'name', 'tags', 'updated_at', 'url', 'do_cancel', 'relative_start_time', 'relative_stop_time', 'start_time', 'stop_time', 'duration', 'status']; + for (const task of schedulingUnit.task_drafts) { let scheduletask = {}; scheduletask['tasktype'] = 'Draft'; - scheduletask['actionpath'] = '/task/view/draft/'+task['id']; + scheduletask['actionpath'] = '/task/view/draft/' + task['id']; scheduletask['blueprint_draft'] = _.map(task['task_blueprints'], 'url'); scheduletask['status'] = task['status']; //fetch task draft details - for(const key of commonkeys){ + for (const key of commonkeys) { scheduletask[key] = task[key]; } scheduletask['specifications_doc'] = task['specifications_doc']; - scheduletask.duration = moment.utc((scheduletask.duration || 0)*1000).format(UIConstants.CALENDAR_TIME_FORMAT); - scheduletask.relative_start_time = moment.utc(scheduletask.relative_start_time*1000).format(UIConstants.CALENDAR_TIME_FORMAT); - scheduletask.relative_stop_time = moment.utc(scheduletask.relative_stop_time*1000).format(UIConstants.CALENDAR_TIME_FORMAT); + scheduletask.duration = moment.utc((scheduletask.duration || 0) * 1000).format(UIConstants.CALENDAR_TIME_FORMAT); + scheduletask.relative_start_time = moment.utc(scheduletask.relative_start_time * 1000).format(UIConstants.CALENDAR_TIME_FORMAT); + scheduletask.relative_stop_time = moment.utc(scheduletask.relative_stop_time * 1000).format(UIConstants.CALENDAR_TIME_FORMAT); scheduletask.template = task.specifications_template; scheduletask.type_value = task.specifications_template.type_value; scheduletask.produced_by = task.produced_by; @@ -208,92 +212,111 @@ export class TaskList extends Component { async formatDataProduct(tasks) { await Promise.all(tasks.map(async task => { - task.status_logs = task.tasktype === "Blueprint"?this.subtaskComponent(task):""; + task.status_logs = task.tasktype === "Blueprint" ? this.subtaskComponent(task) : ""; //Displaying SubTask ID of the 'control' Task - const subTaskIds = task.subTasks?task.subTasks.filter(sTask => sTask.subTaskTemplate.name.indexOf('control') >= 0):[]; + const subTaskIds = task.subTasks ? task.subTasks.filter(sTask => sTask.subTaskTemplate.name.indexOf('control') >= 0) : []; const promise = []; subTaskIds.map(subTask => promise.push(ScheduleService.getSubtaskOutputDataproduct(subTask.id))); - const dataProducts = promise.length > 0? await Promise.all(promise):[]; + const dataProducts = promise.length > 0 ? await Promise.all(promise) : []; task.dataProducts = []; task.size = 0; task.dataSizeOnDisk = 0; task.noOfOutputProducts = 0; - task.canSelect = task.tasktype.toLowerCase() === 'blueprint' ? true:(task.tasktype.toLowerCase() === 'draft' && task.blueprint_draft.length === 0)?true:false; + task.canSelect = task.tasktype.toLowerCase() === 'blueprint' ? true : (task.tasktype.toLowerCase() === 'draft' && task.blueprint_draft.length === 0) ? true : false; if (dataProducts.length && dataProducts[0].length) { task.dataProducts = dataProducts[0]; task.noOfOutputProducts = dataProducts[0].length; task.size = _.sumBy(dataProducts[0], 'size'); - task.dataSizeOnDisk = _.sumBy(dataProducts[0], function(product) { return product.deletedSince?0:product.size}); + task.dataSizeOnDisk = _.sumBy(dataProducts[0], function (product) { return product.deletedSince ? 0 : product.size }); task.size = UnitConverter.getUIResourceUnit('bytes', (task.size)); task.dataSizeOnDisk = UnitConverter.getUIResourceUnit('bytes', (task.dataSizeOnDisk)); } - task.subTaskID = subTaskIds.length ? subTaskIds[0].id : ''; + task.subTaskID = subTaskIds.length ? subTaskIds[0].id : ''; return task; })); return tasks; } - async componentDidMount() { + this.setToggleBySorting(); this.subtaskTemplates = await TaskService.getSubtaskTemplates() const promises = [ - ScheduleService.getSchedulingUnitsExtended('draft'), + ScheduleService.getSchedulingUnitsExtended('draft'), ScheduleService.getSchedulingUnitsExtended('blueprint') ]; Promise.all(promises).then(async (responses) => { let allTasks = []; for (const schedulingUnit of responses[0]) { - let tasks = schedulingUnit.task_drafts?(await this.getFormattedTaskDrafts(schedulingUnit)):this.getFormattedTaskBlueprints(schedulingUnit); - let ingestGroup = tasks.map(task => ({name: task.name, canIngest: task.canIngest, type_value: task.type_value, id: task.id })); + let tasks = schedulingUnit.task_drafts ? (await this.getFormattedTaskDrafts(schedulingUnit)) : this.getFormattedTaskBlueprints(schedulingUnit); + let ingestGroup = tasks.map(task => ({ name: task.name, canIngest: task.canIngest, type_value: task.type_value, id: task.id })); ingestGroup = _.groupBy(_.filter(ingestGroup, 'type_value'), 'type_value'); tasks = await this.formatDataProduct(tasks); allTasks = [...allTasks, ...tasks]; } for (const schedulingUnit of responses[1]) { - let tasks = schedulingUnit.task_drafts?(await this.getFormattedTaskDrafts(schedulingUnit)):this.getFormattedTaskBlueprints(schedulingUnit); - let ingestGroup = tasks.map(task => ({name: task.name, canIngest: task.canIngest, type_value: task.type_value, id: task.id })); + let tasks = schedulingUnit.task_drafts ? (await this.getFormattedTaskDrafts(schedulingUnit)) : this.getFormattedTaskBlueprints(schedulingUnit); + let ingestGroup = tasks.map(task => ({ name: task.name, canIngest: task.canIngest, type_value: task.type_value, id: task.id })); ingestGroup = _.groupBy(_.filter(ingestGroup, 'type_value'), 'type_value'); tasks = await this.formatDataProduct(tasks); allTasks = [...allTasks, ...tasks]; } - this.setState({ tasks: allTasks, isLoading: false }); + this.setState({ tasks: allTasks, isLoading: false }); }); } + toggleBySorting = (sortData) => { + UtilService.localStore({ type: 'set', key: this.lsKeySortColumn, value: sortData }); + } + setToggleBySorting() { + let sortData = UtilService.localStore({ type: 'get', key: this.lsKeySortColumn }); + if (sortData) { + if (Object.prototype.toString.call(sortData) === '[object Array]') { + this.defaultSortColumn = sortData; + } + else { + this.defaultSortColumn = [{ ...sortData }]; + } + } + this.defaultSortColumn = this.defaultSortColumn || []; + UtilService.localStore({ type: 'set', key: this.lsKeySortColumn, value: [...this.defaultSortColumn] }); + } + /** * Prepare Task(s) details to show on confirmation dialog */ - getTaskDialogContent() { + getTaskDialogContent() { let selectedTasks = []; - for(const obj of this.selectedRows) { - selectedTasks.push({id:obj.id, suId: obj.schedulingUnitId, suName: obj.schedulingUnitName, - taskId: obj.id, controlId: obj.subTaskID, taskName: obj.name, status: obj.status}); - } - return <> - <DataTable value={selectedTasks} resizableColumns columnResizeMode="expand" className="card" style={{paddingLeft: '0em'}}> - <Column field="suId" header="Scheduling Unit Id"></Column> - <Column field="taskId" header="Task Id"></Column> - <Column field="taskName" header="Task Name"></Column> - <Column field="status" header="Status"></Column> - </DataTable> + for (const obj of this.selectedRows) { + selectedTasks.push({ + id: obj.id, suId: obj.schedulingUnitId, suName: obj.schedulingUnitName, + taskId: obj.id, controlId: obj.subTaskID, taskName: obj.name, status: obj.status + }); + } + return <> + <DataTable value={selectedTasks} resizableColumns columnResizeMode="expand" className="card" style={{ paddingLeft: '0em' }}> + <Column field="suId" header="Scheduling Unit Id"></Column> + <Column field="taskId" header="Task Id"></Column> + <Column field="taskName" header="Task Name"></Column> + <Column field="status" header="Status"></Column> + </DataTable> </> } confirmDeleteTasks() { - if(this.selectedRows.length === 0) { - appGrowl.show({severity: 'info', summary: 'Select Row', detail: 'Select Task to delete.'}); - } else { + if (this.selectedRows.length === 0) { + appGrowl.show({ severity: 'info', summary: 'Select Row', detail: 'Select Task to delete.' }); + } else { let dialog = {}; dialog.type = "confirmation"; - dialog.header= "Confirm to Delete Task(s)"; + dialog.header = "Confirm to Delete Task(s)"; dialog.detail = "Do you want to delete the selected Task(s)?"; dialog.content = this.getTaskDialogContent; - dialog.actions = [{id: 'yes', title: 'Yes', callback: this.deleteTasks}, - {id: 'no', title: 'No', callback: this.closeDialog}]; + dialog.actions = [{ id: 'yes', title: 'Yes', callback: this.deleteTasks }, + { id: 'no', title: 'No', callback: this.closeDialog }]; dialog.onSubmit = this.deleteTasks; dialog.width = '55vw'; dialog.showIcon = false; - this.setState({dialog: dialog, dialogVisible: true}); + this.setState({ dialog: dialog, dialogVisible: true }); } } @@ -302,19 +325,19 @@ export class TaskList extends Component { */ async deleteTasks() { let hasError = false; - for(const task of this.selectedRows) { - if(!await TaskService.deleteTask(task.tasktype, task.id)) { + for (const task of this.selectedRows) { + if (!await TaskService.deleteTask(task.tasktype, task.id)) { hasError = true; } } - if(hasError){ - appGrowl.show({severity: 'error', summary: 'error', detail: 'Error while deleting Task(s)'}); - this.setState({dialogVisible: false}); - } else { + if (hasError) { + appGrowl.show({ severity: 'error', summary: 'error', detail: 'Error while deleting Task(s)' }); + this.setState({ dialogVisible: false }); + } else { this.selectedRows = []; - this.setState({dialogVisible: false}); + this.setState({ dialogVisible: false }); this.componentDidMount(); - appGrowl.show({severity: 'success', summary: 'Success', detail: 'Task(s) deleted successfully'}); + appGrowl.show({ severity: 'success', summary: 'Success', detail: 'Task(s) deleted successfully' }); } } @@ -322,7 +345,7 @@ export class TaskList extends Component { * Callback function to close the dialog prompted. */ closeDialog() { - this.setState({dialogVisible: false}); + this.setState({ dialogVisible: false }); } onRowSelection(selectedRows) { @@ -332,53 +355,55 @@ export class TaskList extends Component { render() { if (this.state.redirect) { - return <Redirect to={ {pathname: this.state.redirect} }></Redirect> + return <Redirect to={{ pathname: this.state.redirect }}></Redirect> } return ( <React.Fragment> <PageHeader location={this.props.location} title={'Task - List'} /> - {this.state.isLoading? <AppLoader /> : - <> - <div className="delete-option"> - <div > - <span className="p-float-label"> - <a href="#" onClick={this.confirmDeleteTasks} title="Delete selected Task(s)"> - <i class="fa fa-trash" aria-hidden="true" ></i> - </a> - </span> - </div> - </div> - <ViewTable - data={this.state.tasks} - defaultcolumns={this.state.defaultcolumns} - optionalcolumns={this.state.optionalcolumns} - columnclassname={this.state.columnclassname} - columnOrders={this.state.columnOrders} - defaultSortColumn={this.state.defaultSortColumn} - showaction="true" - keyaccessor="id" - paths={this.state.paths} - unittest={this.state.unittest} - tablename="scheduleunit_task_list" - allowRowSelection={true} - onRowSelection = {this.onRowSelection} - /> - </> + {this.state.isLoading ? <AppLoader /> : + <> + <div className="delete-option"> + <div > + <span className="p-float-label"> + <a href="#" onClick={this.confirmDeleteTasks} title="Delete selected Task(s)"> + <i class="fa fa-trash" aria-hidden="true" ></i> + </a> + </span> + </div> + </div> + <ViewTable + data={this.state.tasks} + defaultcolumns={this.state.defaultcolumns} + optionalcolumns={this.state.optionalcolumns} + columnclassname={this.state.columnclassname} + columnOrders={this.state.columnOrders} + defaultSortColumn={this.defaultSortColumn} + showaction="true" + keyaccessor="id" + paths={this.state.paths} + unittest={this.state.unittest} + tablename="scheduleunit_task_list" + allowRowSelection={true} + onRowSelection={this.onRowSelection} + lsKeySortColumn={this.lsKeySortColumn} + toggleBySorting={(sortData) => this.toggleBySorting(sortData)} + ignoreSorting={this.ignoreSorting} + /> + </> } {this.state.showStatusLogs && - <Dialog header={`Status change logs - ${this.state.task?this.state.task.name:""}`} - visible={this.state.showStatusLogs} maximizable maximized={false} position="left" style={{ width: '50vw' }} - onHide={() => {this.setState({showStatusLogs: false})}}> - <TaskStatusLogs taskId={this.state.task.id}></TaskStatusLogs> + <Dialog header={`Status change logs - ${this.state.task ? this.state.task.name : ""}`} + visible={this.state.showStatusLogs} maximizable maximized={false} position="left" style={{ width: '50vw' }} + onHide={() => { this.setState({ showStatusLogs: false }) }}> + <TaskStatusLogs taskId={this.state.task.id}></TaskStatusLogs> </Dialog> } <CustomDialog type="confirmation" visible={this.state.dialogVisible} header={this.state.dialog.header} message={this.state.dialog.detail} actions={this.state.dialog.actions} content={this.state.dialog.content} width={this.state.dialog.width} showIcon={this.state.dialog.showIcon} - onClose={this.closeDialog} onCancel={this.closeDialog} onSubmit={this.state.dialog.onSubmit}/> + onClose={this.closeDialog} onCancel={this.closeDialog} onSubmit={this.state.dialog.onSubmit} /> </React.Fragment> ); } -} - \ No newline at end of file +} 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 4d03a9eebe5993937c24710d778b32826936de4c..e9d147fb52eb7aecd52c3a611554cb9eb6db18c0 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js @@ -1,4 +1,4 @@ -import React, {Component} from 'react'; +import React, { Component } from 'react'; import { Redirect } from 'react-router-dom/cjs/react-router-dom.min'; import moment from 'moment'; import _ from 'lodash'; @@ -32,33 +32,43 @@ import { Button } from 'primereact/button'; // Color constant for SU status -const SU_STATUS_COLORS = { "ERROR": "FF0000", "CANCELLED": "#00FF00", "DEFINED": "#00BCD4", - "SCHEDULABLE":"#0000FF", "SCHEDULED": "#abc", "OBSERVING": "#bcd", - "OBSERVED": "#cde", "PROCESSING": "#cddc39", "PROCESSED": "#fed", - "INGESTING": "#edc", "FINISHED": "#47d53d"}; +const SU_STATUS_COLORS = { + "ERROR": "FF0000", "CANCELLED": "#00FF00", "DEFINED": "#00BCD4", + "SCHEDULABLE": "#0000FF", "SCHEDULED": "#abc", "OBSERVING": "#bcd", + "OBSERVED": "#cde", "PROCESSING": "#cddc39", "PROCESSED": "#fed", + "INGESTING": "#edc", "FINISHED": "#47d53d" +}; // Color constant for Task status -const TASK_STATUS_COLORS = { "ERROR": "FF0000", "CANCELLED": "#00FF00", "DEFINED": "#00BCD4", - "SCHEDULABLE":"#0000FF", "SCHEDULED": "#abc", "STARTED": "#bcd", - "OBSERVED": "#cde", "FINISHED": "#47d53d"}; +const TASK_STATUS_COLORS = { + "ERROR": "FF0000", "CANCELLED": "#00FF00", "DEFINED": "#00BCD4", + "SCHEDULABLE": "#0000FF", "SCHEDULED": "#abc", "STARTED": "#bcd", + "OBSERVED": "#cde", "FINISHED": "#47d53d" +}; -const RESERVATION_COLORS = {"true-true":{bgColor:"lightgrey", color:"#585859"}, "true-false":{bgColor:'#585859', color:"white"}, - "false-true":{bgColor:"#9b9999", color:"white"}, "false-false":{bgColor:"black", color:"white"}}; +const RESERVATION_COLORS = { + "true-true": { bgColor: "lightgrey", color: "#585859" }, "true-false": { bgColor: '#585859', color: "white" }, + "false-true": { bgColor: "#9b9999", color: "white" }, "false-false": { bgColor: "black", color: "white" } +}; /** * Scheduling Unit timeline view component to view SU List and timeline */ export class TimelineView extends Component { + lsKeySortColumn = 'SortDataTimelineView'; + defaultSortColumn = []; + constructor(props) { super(props); + this.setToggleBySorting(); this.state = { isLoading: true, suBlueprints: [], // Scheduling Unit Blueprints suDrafts: [], // Scheduling Unit Drafts suBlueprintList: [], // SU Blueprints filtered to view - group:[], // Timeline group from scheduling unit draft name - items:[], // Timeline items from scheduling unit blueprints grouped by scheduling unit draft + group: [], // Timeline group from scheduling unit draft name + items: [], // Timeline items from scheduling unit blueprints grouped by scheduling unit draft isSUDetsVisible: false, isTaskDetsVisible: false, canExtendSUList: true, @@ -66,7 +76,7 @@ export class TimelineView extends Component { isSUListVisible: true, selectedItem: null, mouseOverItem: null, - suTaskList:[], + suTaskList: [], isSummaryLoading: false, stationGroup: [], selectedStationGroup: [], //Station Group(core,international,remote) @@ -81,10 +91,10 @@ export class TimelineView extends Component { this.reservations = []; this.reservationReasons = []; this.optionsMenu = React.createRef(); - this.menuOptions = [ {label:'Add Reservation', icon: "fa fa-", command: () => {this.selectOptionMenu('Add Reservation')}}, - {label:'Reservation List', icon: "fa fa-", command: () => {this.selectOptionMenu('Reservation List')}}, - ]; - + this.menuOptions = [{ label: 'Add Reservation', icon: "fa fa-", command: () => { this.selectOptionMenu('Add Reservation') } }, + { label: 'Reservation List', icon: "fa fa-", command: () => { this.selectOptionMenu('Reservation List') } }, + ]; + this.showOptionMenu = this.showOptionMenu.bind(this); this.selectOptionMenu = this.selectOptionMenu.bind(this); this.onItemClick = this.onItemClick.bind(this); @@ -102,22 +112,23 @@ export class TimelineView extends Component { this.addNewData = this.addNewData.bind(this); this.updateExistingData = this.updateExistingData.bind(this); this.updateSchedulingUnit = this.updateSchedulingUnit.bind(this); - this.setSelectedStationGroup = this.setSelectedStationGroup.bind(this); + this.setSelectedStationGroup = this.setSelectedStationGroup.bind(this); this.getStationsByGroupName = this.getStationsByGroupName.bind(this); } async componentDidMount() { + this.setToggleBySorting(); this.setState({ loader: true }); // Fetch all details from server and prepare data to pass to timeline and table components - const promises = [ ProjectService.getProjectList(), - ScheduleService.getSchedulingUnitsExtended('blueprint'), - ScheduleService.getSchedulingUnitDraft(), - ScheduleService.getSchedulingSets(), - UtilService.getUTC(), - ScheduleService.getStations('All'), - TaskService.getSubtaskTemplates(), - ScheduleService.getMainGroupStations()]; - Promise.all(promises).then(async(responses) => { + const promises = [ProjectService.getProjectList(), + ScheduleService.getSchedulingUnitsExtended('blueprint'), + ScheduleService.getSchedulingUnitDraft(), + ScheduleService.getSchedulingSets(), + UtilService.getUTC(), + ScheduleService.getStations('All'), + TaskService.getSubtaskTemplates(), + ScheduleService.getMainGroupStations()]; + Promise.all(promises).then(async (responses) => { this.subtaskTemplates = responses[6]; const projects = responses[0]; const suBlueprints = _.sortBy(responses[1], 'name'); @@ -129,11 +140,11 @@ export class TimelineView extends Component { const defaultEndTime = currentUTC.clone().add(24, 'hours'); // Default end time, this should be updated if default view is changed. let suList = []; for (const suDraft of suDrafts) { - const suSet = suSets.find((suSet) => { return suDraft.scheduling_set_id===suSet.id}); - const project = projects.find((project) => { return suSet.project_id===project.name}); + const suSet = suSets.find((suSet) => { return suDraft.scheduling_set_id === suSet.id }); + const project = projects.find((project) => { return suSet.project_id === project.name }); if (suDraft.scheduling_unit_blueprints.length > 0) { for (const suBlueprintId of suDraft.scheduling_unit_blueprints_ids) { - const suBlueprint = _.find(suBlueprints, {'id': suBlueprintId}); + const suBlueprint = _.find(suBlueprints, { 'id': suBlueprintId }); suBlueprint['actionpath'] = `/schedulingunit/view/blueprint/${suBlueprintId}`; suBlueprint.suDraft = suDraft; suBlueprint.project = project.name; @@ -142,15 +153,17 @@ export class TimelineView extends Component { suBlueprint.duration = UnitConverter.getSecsToHHmmss(suBlueprint.duration); suBlueprint.tasks = suBlueprint.task_blueprints; // Select only blueprints with start_time and stop_time in the default time limit - if (suBlueprint.start_time && + if (suBlueprint.start_time && ((moment.utc(suBlueprint.start_time).isBetween(defaultStartTime, defaultEndTime) || - moment.utc(suBlueprint.stop_time).isBetween(defaultStartTime, defaultEndTime)) - || (moment.utc(suBlueprint.start_time).isSameOrBefore(defaultStartTime) && - moment.utc(suBlueprint.stop_time).isSameOrAfter(defaultEndTime)))) { + moment.utc(suBlueprint.stop_time).isBetween(defaultStartTime, defaultEndTime)) + || (moment.utc(suBlueprint.start_time).isSameOrBefore(defaultStartTime) && + moment.utc(suBlueprint.stop_time).isSameOrAfter(defaultEndTime)))) { items.push(this.getTimelineItem(suBlueprint)); - if (!_.find(group, {'id': suDraft.id})) { - group.push({'id': this.state.groupByProject?suBlueprint.project:suDraft.id, - title: this.state.groupByProject?suBlueprint.project:suDraft.name}); + if (!_.find(group, { 'id': suDraft.id })) { + group.push({ + 'id': this.state.groupByProject ? suBlueprint.project : suDraft.id, + title: this.state.groupByProject ? suBlueprint.project : suDraft.name + }); } suList.push(suBlueprint); } @@ -159,7 +172,7 @@ export class TimelineView extends Component { const template = _.find(this.subtaskTemplates, ['id', subtask.specifications_template_id]); return (template && template.name.indexOf('control')) > 0; }); - task.subTaskID = subTaskIds.length ? subTaskIds[0].id : ''; + task.subTaskID = subTaskIds.length ? subTaskIds[0].id : ''; if (task.specifications_template.type_value.toLowerCase() === "observation") { task.antenna_set = task.specifications_doc.antenna_set; task.band = task.specifications_doc.filter; @@ -169,18 +182,18 @@ export class TimelineView extends Component { } } for (const station of responses[5]['stations']) { - this.allStationsGroup.push({id: station, title: station}); + this.allStationsGroup.push({ id: station, title: station }); } // Fetch Reservations and keep ready to use in station view UtilService.getReservations().then(reservations => { this.reservations = reservations; }); UtilService.getReservationTemplates().then(templates => { - this.reservationTemplate = templates.length>0?templates[0]:null; + this.reservationTemplate = templates.length > 0 ? templates[0] : null; if (this.reservationTemplate) { let reasons = this.reservationTemplate.schema.properties.activity.properties.type.enum; for (const reason of reasons) { - this.reservationReasons.push({name: reason}); + this.reservationReasons.push({ name: reason }); } } }); @@ -188,19 +201,39 @@ export class TimelineView extends Component { ScheduleService.getSchedulingConstraintTemplates() .then(suConstraintTemplates => { this.suConstraintTemplates = suConstraintTemplates; + }); + this.setState({ + suBlueprints: suBlueprints, suDrafts: suDrafts, group: group, suSets: suSets, + loader: false, + projects: projects, suBlueprintList: suList, + items: items, currentUTC: currentUTC, isLoading: false, + currentStartTime: defaultStartTime, currentEndTime: defaultEndTime }); - this.setState({suBlueprints: suBlueprints, suDrafts: suDrafts, group: group, suSets: suSets, - loader: false, - projects: projects, suBlueprintList: suList, - items: items, currentUTC: currentUTC, isLoading: false, - currentStartTime: defaultStartTime, currentEndTime: defaultEndTime}); this.mainStationGroups = responses[7]; this.mainStationGroupOptions = Object.keys(responses[7]).map(value => ({ value })); }); } + toggleBySorting = (sortData) => { + UtilService.localStore({ type: 'set', key: this.lsKeySortColumn, value: sortData }); + } + + setToggleBySorting() { + let sortData = UtilService.localStore({ type: 'get', key: this.lsKeySortColumn }); + if (sortData) { + if (Object.prototype.toString.call(sortData) === '[object Array]') { + this.defaultSortColumn = sortData; + } + else { + this.defaultSortColumn = [{ ...sortData }]; + } + } + this.defaultSortColumn = this.defaultSortColumn || []; + UtilService.localStore({ type: 'set', key: this.lsKeySortColumn, value: [...this.defaultSortColumn] }); + } + setSelectedStationGroup(value) { - this.setState({ selectedStationGroup: value}); + this.setState({ selectedStationGroup: value }); } /** @@ -211,26 +244,28 @@ export class TimelineView extends Component { let antennaSet = ""; for (let task of suBlueprint.tasks) { if (task.specifications_template.type_value.toLowerCase() === "observation" - && task.specifications_doc.antenna_set) { + && task.specifications_doc.antenna_set) { antennaSet = task.specifications_doc.antenna_set; } } - let item = { id: suBlueprint.id, - group: this.state.groupByProject?suBlueprint.project:suBlueprint.suDraft.id, + let item = { + id: suBlueprint.id, + group: this.state.groupByProject ? suBlueprint.project : suBlueprint.suDraft.id, //title: `${suBlueprint.project} - ${suBlueprint.suDraft.name} - ${(suBlueprint.durationInSec/3600).toFixed(2)}Hrs`, title: "", project: suBlueprint.project, type: 'SCHEDULE', name: suBlueprint.suDraft.name, - band: antennaSet?antennaSet.split("_")[0]:"", + band: antennaSet ? antennaSet.split("_")[0] : "", antennaSet: antennaSet, scheduleMethod: suBlueprint.suDraft.scheduling_constraints_doc.scheduler, - duration: suBlueprint.durationInSec?`${(suBlueprint.durationInSec/3600).toFixed(2)}Hrs`:"", + duration: suBlueprint.durationInSec ? `${(suBlueprint.durationInSec / 3600).toFixed(2)}Hrs` : "", start_time: moment.utc(suBlueprint.start_time), end_time: moment.utc(suBlueprint.stop_time), - bgColor: suBlueprint.status? SU_STATUS_COLORS[suBlueprint.status.toUpperCase()]:"#2196f3", + bgColor: suBlueprint.status ? SU_STATUS_COLORS[suBlueprint.status.toUpperCase()] : "#2196f3", // selectedBgColor: suBlueprint.status? SU_STATUS_COLORS[suBlueprint.status.toUpperCase()]:"#2196f3"}; selectedBgColor: "none", - status: suBlueprint.status.toLowerCase()}; + status: suBlueprint.status.toLowerCase() + }; return item; } @@ -249,38 +284,42 @@ export class TimelineView extends Component { const start_time = moment.utc(task.start_time); const end_time = moment.utc(task.stop_time); if ((start_time.isBetween(startTime, endTime) || - end_time.isBetween(startTime, endTime)) - || (start_time.isSameOrBefore(startTime) && end_time.isSameOrAfter(endTime))) { + end_time.isBetween(startTime, endTime)) + || (start_time.isSameOrBefore(startTime) && end_time.isSameOrAfter(endTime))) { const subTaskIds = task.subtasks.filter(subtask => { - const template = _.find(subtaskTemplates, ['id', subtask.specifications_template_id]); - return (template && template.name.indexOf('control')) > 0; + const template = _.find(subtaskTemplates, ['id', subtask.specifications_template_id]); + return (template && template.name.indexOf('control')) > 0; }); - const controlId = subTaskIds.length>0 ? subTaskIds[0].id : ''; - let item = { id: `${suBlueprint.id}_${task.id}`, - suId: suBlueprint.id, - taskId: task.id, - controlId: controlId, - group: `${this.state.groupByProject?suBlueprint.project:suBlueprint.suDraft.id}_${this.state.groupByProject?'observations':task.draft_id}`, - // group: `${suBlueprint.suDraft.id}_Tasks`, // For single row task grouping - title: '', - project: suBlueprint.project, type: 'TASK', - name: task.name, - typeValue:task.specifications_template.type_value, - band: antennaSet?antennaSet.split("_")[0]:"", - antennaSet: antennaSet?antennaSet:"", - scheduleMethod: suBlueprint.suDraft.scheduling_constraints_doc.scheduler, - duration: `${(end_time.diff(start_time, 'seconds')/3600).toFixed(2)}Hrs`, - start_time: start_time, - end_time: end_time, - bgColor: task.status? TASK_STATUS_COLORS[task.status.toUpperCase()]:"#2196f3", - selectedBgColor: "none", - status: task.status.toLowerCase()}; + const controlId = subTaskIds.length > 0 ? subTaskIds[0].id : ''; + let item = { + id: `${suBlueprint.id}_${task.id}`, + suId: suBlueprint.id, + taskId: task.id, + controlId: controlId, + group: `${this.state.groupByProject ? suBlueprint.project : suBlueprint.suDraft.id}_${this.state.groupByProject ? 'observations' : task.draft_id}`, + // group: `${suBlueprint.suDraft.id}_Tasks`, // For single row task grouping + title: '', + project: suBlueprint.project, type: 'TASK', + name: task.name, + typeValue: task.specifications_template.type_value, + band: antennaSet ? antennaSet.split("_")[0] : "", + antennaSet: antennaSet ? antennaSet : "", + scheduleMethod: suBlueprint.suDraft.scheduling_constraints_doc.scheduler, + duration: `${(end_time.diff(start_time, 'seconds') / 3600).toFixed(2)}Hrs`, + start_time: start_time, + end_time: end_time, + bgColor: task.status ? TASK_STATUS_COLORS[task.status.toUpperCase()] : "#2196f3", + selectedBgColor: "none", + status: task.status.toLowerCase() + }; items.push(item); if (!_.find(itemGroup, ['id', `${suBlueprint.suDraft.id}_${task.draft_id}`])) { - itemGroup.push({'id': `${this.state.groupByProject?suBlueprint.project:suBlueprint.suDraft.id}_${this.state.groupByProject?'observations':task.draft_id}`, - parent: this.state.groupByProject?suBlueprint.project:suBlueprint.suDraft.id, - start: start_time, - title: `${!this.state.showSUs?(this.state.groupByProject?suBlueprint.project:suBlueprint.suDraft.name):""} -- ${this.state.groupByProject?'observations':task.name}`}); + itemGroup.push({ + 'id': `${this.state.groupByProject ? suBlueprint.project : suBlueprint.suDraft.id}_${this.state.groupByProject ? 'observations' : task.draft_id}`, + parent: this.state.groupByProject ? suBlueprint.project : suBlueprint.suDraft.id, + start: start_time, + title: `${!this.state.showSUs ? (this.state.groupByProject ? suBlueprint.project : suBlueprint.suDraft.name) : ""} -- ${this.state.groupByProject ? 'observations' : task.name}` + }); } /* >>>>>> If all tasks should be shown in single row remove the above 2 lines and uncomment these lines if (!_.find(itemGroup, ['id', `${suBlueprint.suDraft.id}_Tasks`])) { @@ -302,11 +341,11 @@ export class TimelineView extends Component { * @param {Object} item */ onItemClick(item) { - if (item.type === "SCHEDULE") { + if (item.type === "SCHEDULE") { this.showSUSummary(item); - } else if (item.type === "RESERVATION") { + } else if (item.type === "RESERVATION") { this.showReservationSummary(item); - } else { + } else { this.showTaskSummary(item); } } @@ -316,22 +355,26 @@ export class TimelineView extends Component { * @param {Object} item - Timeline SU item object. */ showSUSummary(item) { - if (this.state.isSUDetsVisible && item.id===this.state.selectedItem.id) { + if (this.state.isSUDetsVisible && item.id === this.state.selectedItem.id) { this.closeSUDets(); - } else { - const fetchDetails = !this.state.selectedItem || item.id!==this.state.selectedItem.id - this.setState({selectedItem: item, isSUDetsVisible: true, isTaskDetsVisible: false, + } else { + const fetchDetails = !this.state.selectedItem || item.id !== this.state.selectedItem.id + this.setState({ + selectedItem: item, isSUDetsVisible: true, isTaskDetsVisible: false, isSummaryLoading: fetchDetails, - suTaskList: !fetchDetails?this.state.suTaskList:[], - canExtendSUList: false, canShrinkSUList:false}); + suTaskList: !fetchDetails ? this.state.suTaskList : [], + canExtendSUList: false, canShrinkSUList: false + }); if (fetchDetails) { - const suBlueprint = _.find(this.state.suBlueprints, {id: (this.state.stationView?parseInt(item.id.split('-')[0]):item.id)}); - const suConstraintTemplate = _.find(this.suConstraintTemplates, {id: suBlueprint.suDraft.scheduling_constraints_template_id}); + const suBlueprint = _.find(this.state.suBlueprints, { id: (this.state.stationView ? parseInt(item.id.split('-')[0]) : item.id) }); + const suConstraintTemplate = _.find(this.suConstraintTemplates, { id: suBlueprint.suDraft.scheduling_constraints_template_id }); /* If tasks are not loaded on component mounting fetch from API */ if (suBlueprint.tasks) { - this.setState({suTaskList: _.sortBy(suBlueprint.tasks, "id"), suConstraintTemplate: suConstraintTemplate, - stationGroup: this.getSUStations(suBlueprint), isSummaryLoading: false}); - } else { + this.setState({ + suTaskList: _.sortBy(suBlueprint.tasks, "id"), suConstraintTemplate: suConstraintTemplate, + stationGroup: this.getSUStations(suBlueprint), isSummaryLoading: false + }); + } else { ScheduleService.getTaskBPWithSubtaskTemplateOfSU(suBlueprint) .then(taskList => { for (let task of taskList) { @@ -341,14 +384,16 @@ export class TimelineView extends Component { const template = _.find(this.subtaskTemplates, ['id', subtask.specifications_template_id]); return (template && template.name.indexOf('control')) > 0; }); - task.subTaskID = subTaskIds.length ? subTaskIds[0].id : ''; + task.subTaskID = subTaskIds.length ? subTaskIds[0].id : ''; if (task.specifications_template.type_value.toLowerCase() === "observation") { task.antenna_set = task.specifications_doc.antenna_set; task.band = task.specifications_doc.filter; } } - this.setState({suTaskList: _.sortBy(taskList, "id"), isSummaryLoading: false, - stationGroup: this.getSUStations(suBlueprint)}); + this.setState({ + suTaskList: _.sortBy(taskList, "id"), isSummaryLoading: false, + stationGroup: this.getSUStations(suBlueprint) + }); }); } // Get the scheduling constraint template of the selected SU block @@ -365,7 +410,7 @@ export class TimelineView extends Component { * @param {Object} item */ showReservationSummary(item) { - this.setState({selectedItem: item, isReservDetsVisible: true, isSUDetsVisible: false}); + this.setState({ selectedItem: item, isReservDetsVisible: true, isSUDetsVisible: false }); } /** @@ -373,14 +418,14 @@ export class TimelineView extends Component { * @param {Object} item - Timeline task item object */ showTaskSummary(item) { - this.setState({isTaskDetsVisible: !this.state.isTaskDetsVisible, isSUDetsVisible: false}); + this.setState({ isTaskDetsVisible: !this.state.isTaskDetsVisible, isSUDetsVisible: false }); } /** * Closes the SU details section */ closeSUDets() { - this.setState({isSUDetsVisible: false, isReservDetsVisible: false, isTaskDetsVisible: false, canExtendSUList: true, canShrinkSUList: false}); + this.setState({ isSUDetsVisible: false, isReservDetsVisible: false, isTaskDetsVisible: false, canExtendSUList: true, canShrinkSUList: false }); } /** @@ -398,10 +443,10 @@ export class TimelineView extends Component { */ onItemMouseOver(evt, item) { if (item.type === "SCHEDULE" || item.type === "TASK") { - const itemSU = _.find(this.state.suBlueprints, {id: (item.suId?item.suId:item.id)}); + const itemSU = _.find(this.state.suBlueprints, { id: (item.suId ? item.suId : item.id) }); const itemStations = this.getSUStations(itemSU); const itemStationGroups = this.groupSUStations(itemStations); - item.stations = {groups: "", counts: ""}; + item.stations = { groups: "", counts: "" }; item.suName = itemSU.name; for (const stationgroup of _.keys(itemStationGroups)) { let groups = item.stations.groups; @@ -411,13 +456,13 @@ export class TimelineView extends Component { counts = counts.concat("/"); } // Get station group 1st character and append 'S' to get CS,RS,IS - groups = groups.concat(stationgroup.substring(0,1).concat('S')); + groups = groups.concat(stationgroup.substring(0, 1).concat('S')); counts = counts.concat(itemStationGroups[stationgroup].length); item.stations.groups = groups; item.stations.counts = counts; } - } else { - const reservation = _.find(this.reservations, {'id': parseInt(item.id.split("-")[1])}); + } else { + const reservation = _.find(this.reservations, { 'id': parseInt(item.id.split("-")[1]) }); const reservStations = reservation.specifications_doc.resources.stations; // const reservStationGroups = this.groupSUStations(reservStations); item.name = reservation.name; @@ -427,7 +472,7 @@ export class TimelineView extends Component { item.planned = reservation.specifications_doc.activity.planned; } this.popOver.toggle(evt); - this.setState({mouseOverItem: item}); + this.setState({ mouseOverItem: item }); } /** @@ -437,7 +482,7 @@ export class TimelineView extends Component { groupSUStations(stationList) { let suStationGroups = {}; for (const group in this.mainStationGroups) { - suStationGroups[group] = _.intersection(this.mainStationGroups[group],stationList); + suStationGroups[group] = _.intersection(this.mainStationGroups[group], stationList); } return suStationGroups; } @@ -448,27 +493,29 @@ export class TimelineView extends Component { * @param {moment} endTime */ async dateRangeCallback(startTime, endTime) { - let suBlueprintList = [], group=[], items = []; + let suBlueprintList = [], group = [], items = []; if (startTime && endTime) { for (const suBlueprint of this.state.suBlueprints) { - if (moment.utc(suBlueprint.start_time).isBetween(startTime, endTime) + if (moment.utc(suBlueprint.start_time).isBetween(startTime, endTime) || moment.utc(suBlueprint.stop_time).isBetween(startTime, endTime) - || (moment.utc(suBlueprint.start_time).isSameOrBefore(startTime) && - moment.utc(suBlueprint.stop_time).isSameOrAfter(endTime))) { + || (moment.utc(suBlueprint.start_time).isSameOrBefore(startTime) && + moment.utc(suBlueprint.stop_time).isSameOrAfter(endTime))) { // Get timeline item for station view noramlly and in timeline view only if SU to be shown - let timelineItem = (this.state.showSUs || this.state.stationView)?this.getTimelineItem(suBlueprint):null; + let timelineItem = (this.state.showSUs || this.state.stationView) ? this.getTimelineItem(suBlueprint) : null; if (this.state.stationView) { this.getStationItemGroups(suBlueprint, timelineItem, this.allStationsGroup, items); - } else { + } else { // Add timeline SU item if (timelineItem) { items.push(timelineItem); - if (!_.find(group, {'id': suBlueprint.suDraft.id})) { + if (!_.find(group, { 'id': suBlueprint.suDraft.id })) { /* parent and start properties are added to order and display task rows below the corresponding SU row */ - group.push({'id': this.state.groupByProject?suBlueprint.project:suBlueprint.suDraft.id, - parent: this.state.groupByProject?suBlueprint.project:suBlueprint.suDraft.id, - start: moment.utc("1900-01-01", "YYYY-MM-DD"), - title: this.state.groupByProject?suBlueprint.project:suBlueprint.suDraft.name}); + group.push({ + 'id': this.state.groupByProject ? suBlueprint.project : suBlueprint.suDraft.id, + parent: this.state.groupByProject ? suBlueprint.project : suBlueprint.suDraft.id, + start: moment.utc("1900-01-01", "YYYY-MM-DD"), + title: this.state.groupByProject ? suBlueprint.project : suBlueprint.suDraft.name + }); } } // Add task item only in timeline view and when show task is enabled @@ -479,24 +526,26 @@ export class TimelineView extends Component { } } suBlueprintList.push(suBlueprint); - } + } } if (this.state.stationView) { items = this.addStationReservations(items, startTime, endTime); } - } else { + } else { suBlueprintList = _.clone(this.state.suBlueprints); group = this.state.group; items = this.state.items; } - - this.setState({suBlueprintList: _.filter(suBlueprintList, (suBlueprint) => {return suBlueprint.start_time!=null}), - currentStartTime: startTime, currentEndTime: endTime}); + + this.setState({ + suBlueprintList: _.filter(suBlueprintList, (suBlueprint) => { return suBlueprint.start_time != null }), + currentStartTime: startTime, currentEndTime: endTime + }); // On range change close the Details pane // this.closeSUDets(); // console.log(_.orderBy(group, ["parent", "id"], ['asc', 'desc'])); - group = this.state.stationView ? this.getStationsByGroupName() : _.orderBy(_.uniqBy(group, 'id'),["parent", "start"], ['asc', 'asc']); - return {group: group, items: items}; + group = this.state.stationView ? this.getStationsByGroupName() : _.orderBy(_.uniqBy(group, 'id'), ["parent", "start"], ['asc', 'asc']); + return { group: group, items: items }; } /** @@ -509,7 +558,7 @@ export class TimelineView extends Component { getStationItemGroups(suBlueprint, timelineItem, group, items) { /* Get stations based on SU status */ let stations = this.getSUStations(suBlueprint); - + /* Group the items by station */ for (const station of stations) { let stationItem = _.cloneDeep(timelineItem); @@ -527,7 +576,7 @@ export class TimelineView extends Component { getSUStations(suBlueprint) { let stations = []; /* Get all observation tasks */ - const observationTasks = _.filter(suBlueprint.tasks, (task) => { return task.specifications_template.type_value.toLowerCase() === "observation"}); + const observationTasks = _.filter(suBlueprint.tasks, (task) => { return task.specifications_template.type_value.toLowerCase() === "observation" }); for (const observationTask of observationTasks) { /** If the status of SU is before scheduled, get all stations from the station_groups from the task specification_docs */ if (this.STATUS_BEFORE_SCHEDULED.indexOf(suBlueprint.status.toLowerCase()) >= 0 @@ -535,8 +584,8 @@ export class TimelineView extends Component { for (const grpStations of _.map(observationTask.specifications_doc.station_groups, "stations")) { stations = _.concat(stations, grpStations); } - } else if (this.STATUS_BEFORE_SCHEDULED.indexOf(suBlueprint.status.toLowerCase()) < 0 - && observationTask.subtasks) { + } else if (this.STATUS_BEFORE_SCHEDULED.indexOf(suBlueprint.status.toLowerCase()) < 0 + && observationTask.subtasks) { /** If the status of SU is scheduled or after get the stations from the subtask specification tasks */ for (const subtask of observationTask.subtasks) { if (subtask.specifications_doc.stations) { @@ -558,18 +607,18 @@ export class TimelineView extends Component { let reservations = this.reservations; for (const reservation of reservations) { const reservationStartTime = moment.utc(reservation.start_time); - const reservationEndTime = reservation.duration?reservationStartTime.clone().add(reservation.duration, 'seconds'):endTime; + const reservationEndTime = reservation.duration ? reservationStartTime.clone().add(reservation.duration, 'seconds') : endTime; const reservationSpec = reservation.specifications_doc; - if ( (reservationStartTime.isSame(startTime) - || reservationStartTime.isSame(endTime) - || reservationStartTime.isBetween(startTime, endTime) - || reservationEndTime.isSame(startTime) - || reservationEndTime.isSame(endTime) - || reservationEndTime.isBetween(startTime, endTime) - || (reservationStartTime.isSameOrBefore(startTime) + if ((reservationStartTime.isSame(startTime) + || reservationStartTime.isSame(endTime) + || reservationStartTime.isBetween(startTime, endTime) + || reservationEndTime.isSame(startTime) + || reservationEndTime.isSame(endTime) + || reservationEndTime.isBetween(startTime, endTime) + || (reservationStartTime.isSameOrBefore(startTime) && reservationEndTime.isSameOrAfter(endTime))) - && (!this.state.reservationFilter || // No reservation filter added - reservationSpec.activity.type === this.state.reservationFilter) ) { // Reservation reason == Filtered reaseon + && (!this.state.reservationFilter || // No reservation filter added + reservationSpec.activity.type === this.state.reservationFilter)) { // Reservation reason == Filtered reaseon if (reservationSpec.resources.stations) { items = items.concat(this.getReservationItems(reservation, endTime)); } @@ -587,18 +636,19 @@ export class TimelineView extends Component { const reservationSpec = reservation.specifications_doc; let items = []; const start_time = moment.utc(reservation.start_time); - const end_time = reservation.duration?start_time.clone().add(reservation.duration, 'seconds'):endTime; + const end_time = reservation.duration ? start_time.clone().add(reservation.duration, 'seconds') : endTime; for (const station of reservationSpec.resources.stations) { const blockColor = RESERVATION_COLORS[this.getReservationType(reservationSpec.schedulability)]; - let item = { id: `Res-${reservation.id}-${station}`, - start_time: start_time, end_time: end_time, - name: reservationSpec.activity.type, project: reservation.project_id, - group: station, type: 'RESERVATION', - title: `${reservationSpec.activity.type}${reservation.project_id?("-"+ reservation.project_id):""}`, - desc: reservation.description, - duration: reservation.duration?UnitConverter.getSecsToHHmmss(reservation.duration):"Unknown", - bgColor: blockColor.bgColor, selectedBgColor: blockColor.bgColor, color: blockColor.color - }; + let item = { + id: `Res-${reservation.id}-${station}`, + start_time: start_time, end_time: end_time, + name: reservationSpec.activity.type, project: reservation.project_id, + group: station, type: 'RESERVATION', + title: `${reservationSpec.activity.type}${reservation.project_id ? ("-" + reservation.project_id) : ""}`, + desc: reservation.description, + duration: reservation.duration ? UnitConverter.getSecsToHHmmss(reservation.duration) : "Unknown", + bgColor: blockColor.bgColor, selectedBgColor: blockColor.bgColor, color: blockColor.color + }; items.push(item); } return items; @@ -612,11 +662,11 @@ export class TimelineView extends Component { getReservationType(schedulability) { if (schedulability.manual && schedulability.dynamic) { return 'true-true'; - } else if (!schedulability.manual && !schedulability.dynamic) { + } else if (!schedulability.manual && !schedulability.dynamic) { return 'false-false'; - } else if (schedulability.manual && !schedulability.dynamic) { + } else if (schedulability.manual && !schedulability.dynamic) { return 'true-false'; - } else { + } else { return 'false-true'; } } @@ -626,7 +676,7 @@ export class TimelineView extends Component { * @param {String} filter */ setReservationFilter(filter) { - this.setState({reservationFilter: filter}); + this.setState({ reservationFilter: filter }); } /** @@ -634,8 +684,10 @@ export class TimelineView extends Component { * @param {String} value */ showTimelineItems(value) { - this.setState({showSUs: value==='su' || value==="suTask", - showTasks: value==='task' || value==="suTask"}); + this.setState({ + showSUs: value === 'su' || value === "suTask", + showTasks: value === 'task' || value === "suTask" + }); } /** @@ -647,14 +699,14 @@ export class TimelineView extends Component { let canShrinkSUList = this.state.canShrinkSUList; if (step === 1) { // Can Extend when fully shrunk and still extendable - canExtendSUList = (!canShrinkSUList && canExtendSUList)?true:false; + canExtendSUList = (!canShrinkSUList && canExtendSUList) ? true : false; canShrinkSUList = true; - } else { + } else { // Can Shrink when fully extended and still shrinkable - canShrinkSUList = (canShrinkSUList && !canExtendSUList)?true:false; + canShrinkSUList = (canShrinkSUList && !canExtendSUList) ? true : false; canExtendSUList = true; } - this.setState({canExtendSUList: canExtendSUList, canShrinkSUList: canShrinkSUList}); + this.setState({ canExtendSUList: canExtendSUList, canShrinkSUList: canShrinkSUList }); } /** @@ -662,22 +714,24 @@ export class TimelineView extends Component { * @param {Array} filteredData */ suListFilterCallback(filteredData) { - let group=[], items = []; + let group = [], items = []; const suBlueprints = this.state.suBlueprints; for (const data of filteredData) { - const suBlueprint = _.find(suBlueprints, {actionpath: data.actionpath}); - let timelineItem = (this.state.showSUs || this.state.stationView)?this.getTimelineItem(suBlueprint):null; + const suBlueprint = _.find(suBlueprints, { actionpath: data.actionpath }); + let timelineItem = (this.state.showSUs || this.state.stationView) ? this.getTimelineItem(suBlueprint) : null; if (this.state.stationView) { this.getStationItemGroups(suBlueprint, timelineItem, this.allStationsGroup, items); - } else { + } else { if (timelineItem) { items.push(timelineItem); - if (!_.find(group, {'id': suBlueprint.suDraft.id})) { + if (!_.find(group, { 'id': suBlueprint.suDraft.id })) { /* parent and start properties are added to order and list task rows below the SU row */ - group.push({'id': this.state.groupByProject?suBlueprint.project:suBlueprint.suDraft.id, - parent: this.state.groupByProject?suBlueprint.project:suBlueprint.suDraft.id, - start: moment.utc("1900-01-01", "YYYY-MM-DD"), - title: this.state.groupByProject?suBlueprint.project:suBlueprint.suDraft.name}); + group.push({ + 'id': this.state.groupByProject ? suBlueprint.project : suBlueprint.suDraft.id, + parent: this.state.groupByProject ? suBlueprint.project : suBlueprint.suDraft.id, + start: moment.utc("1900-01-01", "YYYY-MM-DD"), + title: this.state.groupByProject ? suBlueprint.project : suBlueprint.suDraft.name + }); } } if (this.state.showTasks && !this.state.stationView) { @@ -691,37 +745,37 @@ export class TimelineView extends Component { items = this.addStationReservations(items, this.state.currentStartTime, this.state.currentEndTime); } if (this.timeline) { - this.timeline.updateTimeline({group: this.state.stationView ? this.getStationsByGroupName() : _.orderBy(_.uniqBy(group, 'id'),["parent", "start"], ['asc', 'asc']), items: items}); + this.timeline.updateTimeline({ group: this.state.stationView ? this.getStationsByGroupName() : _.orderBy(_.uniqBy(group, 'id'), ["parent", "start"], ['asc', 'asc']), items: items }); } - + } - getStationsByGroupName() { + getStationsByGroupName() { let stations = []; this.state.selectedStationGroup.forEach((group) => { stations = [...stations, ...this.mainStationGroups[group]]; }); - stations = stations.map(station => ({id: station, title: station})); + stations = stations.map(station => ({ id: station, title: station })); return stations; } setStationView(e) { this.closeSUDets(); const selectedGroups = _.keys(this.mainStationGroups); - this.setState({stationView: e.value, selectedStationGroup: selectedGroups}); + this.setState({ stationView: e.value, selectedStationGroup: selectedGroups }); } showOptionMenu(event) { this.optionsMenu.toggle(event); } selectOptionMenu(menuName) { - switch(menuName) { + switch (menuName) { case 'Reservation List': { - this.setState({redirect: `/reservation/list`}); + this.setState({ redirect: `/reservation/list` }); break; } case 'Add Reservation': { - this.setState({redirect: `/reservation/create`}); + this.setState({ redirect: `/reservation/create` }); break; } default: { @@ -753,7 +807,7 @@ export class TimelineView extends Component { const jsonData = JSON.parse(data); if (jsonData.action === 'create') { this.addNewData(jsonData.object_details.id, jsonData.object_type, jsonData.object_details); - } else if (jsonData.action === 'update') { + } else if (jsonData.action === 'update') { this.updateExistingData(jsonData.object_details.id, jsonData.object_type, jsonData.object_details); } } @@ -766,7 +820,7 @@ export class TimelineView extends Component { * @param {Object} object - model object with certain properties */ addNewData(id, type, object) { - switch(type) { + switch (type) { /* When a new scheduling_unit_draft is created, it should be added to the existing list of suDraft. */ case 'scheduling_unit_draft': { this.updateSUDraft(id); @@ -802,7 +856,7 @@ export class TimelineView extends Component { */ updateExistingData(id, type, object) { const objectProps = ['status', 'start_time', 'stop_time', 'duration']; - switch(type) { + switch (type) { case 'scheduling_unit_draft': { this.updateSUDraft(id); // let suDrafts = this.state.suDrafts; @@ -828,7 +882,7 @@ export class TimelineView extends Component { // } break; } - default: { break;} + default: { break; } } } @@ -840,13 +894,13 @@ export class TimelineView extends Component { let suDrafts = this.state.suDrafts; let suSets = this.state.suSets; ScheduleService.getSchedulingUnitDraftById(id) - .then(suDraft => { - _.remove(suDrafts, function(suDraft) { return suDraft.id === id}); - suDrafts.push(suDraft); - _.remove(suSets, function(suSet) { return suSet.id === suDraft.scheduling_set_id}); - suSets.push(suDraft.scheduling_set_object); - this.setState({suSet: suSets, suDrafts: suDrafts}); - }); + .then(suDraft => { + _.remove(suDrafts, function (suDraft) { return suDraft.id === id }); + suDrafts.push(suDraft); + _.remove(suSets, function (suSet) { return suSet.id === suDraft.scheduling_set_id }); + suSets.push(suDraft.scheduling_set_object); + this.setState({ suSet: suSets, suDrafts: suDrafts }); + }); } /** @@ -856,29 +910,29 @@ export class TimelineView extends Component { */ updateSchedulingUnit(id) { ScheduleService.getSchedulingUnitExtended('blueprint', id, true) - .then(suBlueprint => { - const suDraft = _.find(this.state.suDrafts, ['id', suBlueprint.draft_id]); - const suSet = this.state.suSets.find((suSet) => { return suDraft.scheduling_set_id===suSet.id}); - const project = this.state.projects.find((project) => { return suSet.project_id===project.name}); - let suBlueprints = this.state.suBlueprints; - suBlueprint['actionpath'] = `/schedulingunit/view/blueprint/${id}`; - suBlueprint.suDraft = suDraft; - suBlueprint.project = project.name; - suBlueprint.suSet = suSet; - suBlueprint.durationInSec = suBlueprint.duration; - suBlueprint.duration = UnitConverter.getSecsToHHmmss(suBlueprint.duration); - suBlueprint.tasks = suBlueprint.task_blueprints; - _.remove(suBlueprints, function(suB) { return suB.id === id}); - suBlueprints.push(suBlueprint); - // Set updated suBlueprints in the state and call the dateRangeCallback to create the timeline group and items - this.setState({suBlueprints: suBlueprints}); - this.dateRangeCallback(this.state.currentStartTime, this.state.currentEndTime); - }); + .then(suBlueprint => { + const suDraft = _.find(this.state.suDrafts, ['id', suBlueprint.draft_id]); + const suSet = this.state.suSets.find((suSet) => { return suDraft.scheduling_set_id === suSet.id }); + const project = this.state.projects.find((project) => { return suSet.project_id === project.name }); + let suBlueprints = this.state.suBlueprints; + suBlueprint['actionpath'] = `/schedulingunit/view/blueprint/${id}`; + suBlueprint.suDraft = suDraft; + suBlueprint.project = project.name; + suBlueprint.suSet = suSet; + suBlueprint.durationInSec = suBlueprint.duration; + suBlueprint.duration = UnitConverter.getSecsToHHmmss(suBlueprint.duration); + suBlueprint.tasks = suBlueprint.task_blueprints; + _.remove(suBlueprints, function (suB) { return suB.id === id }); + suBlueprints.push(suBlueprint); + // Set updated suBlueprints in the state and call the dateRangeCallback to create the timeline group and items + this.setState({ suBlueprints: suBlueprints }); + this.dateRangeCallback(this.state.currentStartTime, this.state.currentEndTime); + }); } render() { if (this.state.redirect) { - return <Redirect to={ {pathname: this.state.redirect} }></Redirect> + return <Redirect to={{ pathname: this.state.redirect }}></Redirect> } // if (this.state.loader) { // return <AppLoader /> @@ -891,125 +945,131 @@ export class TimelineView extends Component { const canShrinkSUList = this.state.canShrinkSUList; let suBlueprint = null, reservation = null; if (isSUDetsVisible) { - suBlueprint = _.find(this.state.suBlueprints, {id: this.state.stationView?parseInt(this.state.selectedItem.id.split('-')[0]):this.state.selectedItem.id}); + suBlueprint = _.find(this.state.suBlueprints, { id: this.state.stationView ? parseInt(this.state.selectedItem.id.split('-')[0]) : this.state.selectedItem.id }); } if (isReservDetsVisible) { - reservation = _.find(this.reservations, {id: parseInt(this.state.selectedItem.id.split('-')[1])}); + reservation = _.find(this.reservations, { id: parseInt(this.state.selectedItem.id.split('-')[1]) }); reservation.project = this.state.selectedItem.project; } let mouseOverItem = this.state.mouseOverItem; return ( <React.Fragment> <TieredMenu className="app-header-menu" model={this.menuOptions} popup ref={el => this.optionsMenu = el} /> - <PageHeader location={this.props.location} title={'Scheduling Units - Timeline View'} + <PageHeader location={this.props.location} title={'Scheduling Units - Timeline View'} actions={[ - {icon:'fa-bars',title: '', type:'button', actOn:'mouseOver', props : { callback: this.showOptionMenu},}, - {icon: 'fa-calendar-alt',title:'Week View', props : { pathname: `/su/timelineview/week`}} + { icon: 'fa-bars', title: '', type: 'button', actOn: 'mouseOver', props: { callback: this.showOptionMenu }, }, + { icon: 'fa-calendar-alt', title: 'Week View', props: { pathname: `/su/timelineview/week` } } ]} /> { this.state.isLoading ? <AppLoader /> : - <div className="p-grid"> - {/* SU List Panel */} - <div className={isSUListVisible && (isSUDetsVisible || isReservDetsVisible || isTaskDetsVisible || - (canExtendSUList && !canShrinkSUList)?"col-lg-4 col-md-4 col-sm-12": - ((canExtendSUList && canShrinkSUList)?"col-lg-5 col-md-5 col-sm-12":"col-lg-6 col-md-6 col-sm-12"))} - style={isSUListVisible?{position: "inherit", borderRight: "3px solid #efefef", paddingTop: "10px"}:{display: 'none'}}> - <ViewTable - viewInNewWindow - data={this.state.suBlueprintList} - defaultcolumns={[{name: "Name", - start_time: - { - name:"Start Time", - format:UIConstants.CALENDAR_DATETIME_FORMAT - }, - stop_time:{ - name:"End Time", - format:UIConstants.CALENDAR_DATETIME_FORMAT} - }]} - optionalcolumns={[{project:"Project",description: "Description", duration:"Duration (HH:mm:ss)", actionpath: "actionpath"}]} - columnclassname={[{"Start Time":"filter-input-50", "End Time":"filter-input-50", - "Duration (HH:mm:ss)" : "filter-input-50",}]} - defaultSortColumn= {[{id: "Start Time", desc: false}]} - showaction="true" - tablename="timeline_scheduleunit_list" - showTopTotal={false} - filterCallback={this.suListFilterCallback} - /> - </div> - {/* Timeline Panel */} - <div className={isSUListVisible?((isSUDetsVisible || isReservDetsVisible)?"col-lg-5 col-md-5 col-sm-12": - (!canExtendSUList && canShrinkSUList)?"col-lg-6 col-md-6 col-sm-12": - ((canExtendSUList && canShrinkSUList)?"col-lg-7 col-md-7 col-sm-12":"col-lg-8 col-md-8 col-sm-12")): - ((isSUDetsVisible || isReservDetsVisible || isTaskDetsVisible)?"col-lg-9 col-md-9 col-sm-12":"col-lg-12 col-md-12 col-sm-12")} - // style={{borderLeft: "3px solid #efefef"}} - > - {/* Panel Resize buttons */} - {isSUListVisible && + <div className="p-grid"> + {/* SU List Panel */} + <div className={isSUListVisible && (isSUDetsVisible || isReservDetsVisible || isTaskDetsVisible || + (canExtendSUList && !canShrinkSUList) ? "col-lg-4 col-md-4 col-sm-12" : + ((canExtendSUList && canShrinkSUList) ? "col-lg-5 col-md-5 col-sm-12" : "col-lg-6 col-md-6 col-sm-12"))} + style={isSUListVisible ? { position: "inherit", borderRight: "3px solid #efefef", paddingTop: "10px" } : { display: 'none' }}> + <ViewTable + viewInNewWindow + data={this.state.suBlueprintList} + defaultcolumns={[{ + name: "Name", + start_time: + { + name: "Start Time", + format: UIConstants.CALENDAR_DATETIME_FORMAT + }, + stop_time: { + name: "End Time", + format: UIConstants.CALENDAR_DATETIME_FORMAT + } + }]} + optionalcolumns={[{ project: "Project", description: "Description", duration: "Duration (HH:mm:ss)", actionpath: "actionpath" }]} + columnclassname={[{ + "Start Time": "filter-input-50", "End Time": "filter-input-50", + "Duration (HH:mm:ss)": "filter-input-50", + }]} + defaultSortColumn={this.defaultSortColumn} + showaction="true" + tablename="timeline_scheduleunit_list" + showTopTotal={false} + filterCallback={this.suListFilterCallback} + lsKeySortColumn={this.lsKeySortColumn} + toggleBySorting={(sortData) => this.toggleBySorting(sortData)} + /> + </div> + {/* Timeline Panel */} + <div className={isSUListVisible ? ((isSUDetsVisible || isReservDetsVisible) ? "col-lg-5 col-md-5 col-sm-12" : + (!canExtendSUList && canShrinkSUList) ? "col-lg-6 col-md-6 col-sm-12" : + ((canExtendSUList && canShrinkSUList) ? "col-lg-7 col-md-7 col-sm-12" : "col-lg-8 col-md-8 col-sm-12")) : + ((isSUDetsVisible || isReservDetsVisible || isTaskDetsVisible) ? "col-lg-9 col-md-9 col-sm-12" : "col-lg-12 col-md-12 col-sm-12")} + // style={{borderLeft: "3px solid #efefef"}} + > + {/* Panel Resize buttons */} + {isSUListVisible && <div className="resize-div"> - <button className="p-link resize-btn" disabled={!this.state.canShrinkSUList} - title="Shrink List/Expand Timeline" - onClick={(e)=> { this.resizeSUList(-1)}}> + <button className="p-link resize-btn" disabled={!this.state.canShrinkSUList} + title="Shrink List/Expand Timeline" + onClick={(e) => { this.resizeSUList(-1) }}> <i className="pi pi-step-backward"></i> </button> - <button className="p-link resize-btn" disabled={!this.state.canExtendSUList} - title="Expand List/Shrink Timeline" - onClick={(e)=> { this.resizeSUList(1)}}> + <button className="p-link resize-btn" disabled={!this.state.canExtendSUList} + title="Expand List/Shrink Timeline" + onClick={(e) => { this.resizeSUList(1) }}> <i className="pi pi-step-forward"></i> </button> - </div> - } - <div className={isSUListVisible?"resize-div su-visible":"resize-div su-hidden"}> - {isSUListVisible && - <button className="p-link resize-btn" - title="Hide List" - onClick={(e)=> { this.setState({isSUListVisible: false})}}> + </div> + } + <div className={isSUListVisible ? "resize-div su-visible" : "resize-div su-hidden"}> + {isSUListVisible && + <button className="p-link resize-btn" + title="Hide List" + onClick={(e) => { this.setState({ isSUListVisible: false }) }}> <i className="pi pi-eye-slash"></i> </button> - } - {!isSUListVisible && + } + {!isSUListVisible && <button className="p-link resize-btn" - title="Show List" - onClick={(e)=> { this.setState({isSUListVisible: true})}}> + title="Show List" + onClick={(e) => { this.setState({ isSUListVisible: true }) }}> <i className="pi pi-eye"> Show List</i> </button> - } - </div> - <div className={`timeline-view-toolbar ${this.state.stationView && 'alignTimeLineHeader'}`}> - <div className="sub-header"> - <label >Station View</label> - <InputSwitch checked={this.state.stationView} onChange={(e) => {this.setStationView(e)}} /> - { this.state.stationView && - <> - <label style={{marginLeft: '20px'}}>Stations Group</label> - <MultiSelect data-testid="stations" id="stations" optionLabel="value" optionValue="value" - style={{top:'2px'}} + } + </div> + <div className={`timeline-view-toolbar ${this.state.stationView && 'alignTimeLineHeader'}`}> + <div className="sub-header"> + <label >Station View</label> + <InputSwitch checked={this.state.stationView} onChange={(e) => { this.setStationView(e) }} /> + {this.state.stationView && + <> + <label style={{ marginLeft: '20px' }}>Stations Group</label> + <MultiSelect data-testid="stations" id="stations" optionLabel="value" optionValue="value" + style={{ top: '2px' }} tooltip="Select Stations" - value={this.state.selectedStationGroup} - options={this.mainStationGroupOptions} + value={this.state.selectedStationGroup} + options={this.mainStationGroupOptions} placeholder="Select Group" onChange={(e) => this.setSelectedStationGroup(e.value)} /> - </> - } - </div> - - {this.state.stationView && + </> + } + </div> + + {this.state.stationView && <div className="sub-header"> - <label style={{marginLeft: '20px'}}>Reservation</label> - <Dropdown optionLabel="name" optionValue="name" - style={{top:'2px'}} - value={this.state.reservationFilter} - options={this.reservationReasons} - filter showClear={true} filterBy="name" - onChange={(e) => {this.setReservationFilter(e.value)}} - placeholder="Reason"/> - + <label style={{ marginLeft: '20px' }}>Reservation</label> + <Dropdown optionLabel="name" optionValue="name" + style={{ top: '2px' }} + value={this.state.reservationFilter} + options={this.reservationReasons} + filter showClear={true} filterBy="name" + onChange={(e) => { this.setReservationFilter(e.value) }} + placeholder="Reason" /> + </div> - } - {!this.state.stationView && + } + {!this.state.stationView && <> - <label style={{marginLeft: '15px'}}>Show :</label> + <label style={{ marginLeft: '15px' }}>Show :</label> <RadioButton value="su" name="Only SUs" inputId="suOnly" onChange={(e) => this.showTimelineItems(e.value)} checked={this.state.showSUs && !this.state.showTasks} /> <label htmlFor="suOnly">Only SU</label> <RadioButton value="task" name="Only Tasks" inputId="taskOnly" onChange={(e) => this.showTimelineItems(e.value)} checked={!this.state.showSUs && this.state.showTasks} /> @@ -1019,137 +1079,137 @@ export class TimelineView extends Component { <div className="sub-header"> {this.state.groupByProject && - <Button className="p-button-rounded toggle-btn" label="Group By SU" onClick={e => this.setState({groupByProject: false})} /> } + <Button className="p-button-rounded toggle-btn" label="Group By SU" onClick={e => this.setState({ groupByProject: false })} />} {!this.state.groupByProject && - <Button className="p-button-rounded toggle-btn" label="Group By Project" onClick={e => this.setState({groupByProject: true})} /> } + <Button className="p-button-rounded toggle-btn" label="Group By Project" onClick={e => this.setState({ groupByProject: true })} />} </div> </> - } - </div> - - <Timeline ref={(tl)=>{this.timeline=tl}} - group={this.state.group} - items={this.state.items} - currentUTC={this.state.currentUTC} - rowHeight={this.state.stationView?50:50} - sidebarWidth={!this.state.showSUs?250:200} - itemClickCallback={this.onItemClick} - itemMouseOverCallback={this.onItemMouseOver} - itemMouseOutCallback={this.onItemMouseOut} - dateRangeCallback={this.dateRangeCallback} - showSunTimings={!this.state.stationView} - // stackItems ={this.state.stationView} - stackItems - className="timeline-toolbar-margin-top-0"></Timeline> + } </div> - {/* Details Panel */} - {this.state.isSUDetsVisible && - <div className="col-lg-3 col-md-3 col-sm-12" - style={{borderLeft: "1px solid #efefef", marginTop: "0px", backgroundColor: "#f2f2f2"}}> - {this.state.isSummaryLoading?<AppLoader /> : - <SchedulingUnitSummary schedulingUnit={suBlueprint} suTaskList={this.state.suTaskList} - viewInNewWindow - constraintsTemplate={this.state.suConstraintTemplate} - stationGroup={this.state.stationGroup} - closeCallback={this.closeSUDets}></SchedulingUnitSummary> - } - </div> - } - {this.state.isTaskDetsVisible && - <div className="col-lg-3 col-md-3 col-sm-12" - style={{borderLeft: "1px solid #efefef", marginTop: "0px", backgroundColor: "#f2f2f2"}}> - {this.state.isSummaryLoading?<AppLoader /> : - <div>Yet to be developed <i className="fa fa-times" onClick={this.closeSUDets}></i></div> - } - </div> - } - {this.state.isReservDetsVisible && - <div className="col-lg-3 col-md-3 col-sm-12" - style={{borderLeft: "1px solid #efefef", marginTop: "0px", backgroundColor: "#f2f2f2"}}> - {this.state.isSummaryLoading?<AppLoader /> : - <ReservationSummary reservation={reservation} closeCallback={this.closeSUDets}></ReservationSummary> - } - </div> - } + + <Timeline ref={(tl) => { this.timeline = tl }} + group={this.state.group} + items={this.state.items} + currentUTC={this.state.currentUTC} + rowHeight={this.state.stationView ? 50 : 50} + sidebarWidth={!this.state.showSUs ? 250 : 200} + itemClickCallback={this.onItemClick} + itemMouseOverCallback={this.onItemMouseOver} + itemMouseOutCallback={this.onItemMouseOut} + dateRangeCallback={this.dateRangeCallback} + showSunTimings={!this.state.stationView} + // stackItems ={this.state.stationView} + stackItems + className="timeline-toolbar-margin-top-0"></Timeline> </div> - + {/* Details Panel */} + {this.state.isSUDetsVisible && + <div className="col-lg-3 col-md-3 col-sm-12" + style={{ borderLeft: "1px solid #efefef", marginTop: "0px", backgroundColor: "#f2f2f2" }}> + {this.state.isSummaryLoading ? <AppLoader /> : + <SchedulingUnitSummary schedulingUnit={suBlueprint} suTaskList={this.state.suTaskList} + viewInNewWindow + constraintsTemplate={this.state.suConstraintTemplate} + stationGroup={this.state.stationGroup} + closeCallback={this.closeSUDets}></SchedulingUnitSummary> + } + </div> + } + {this.state.isTaskDetsVisible && + <div className="col-lg-3 col-md-3 col-sm-12" + style={{ borderLeft: "1px solid #efefef", marginTop: "0px", backgroundColor: "#f2f2f2" }}> + {this.state.isSummaryLoading ? <AppLoader /> : + <div>Yet to be developed <i className="fa fa-times" onClick={this.closeSUDets}></i></div> + } + </div> + } + {this.state.isReservDetsVisible && + <div className="col-lg-3 col-md-3 col-sm-12" + style={{ borderLeft: "1px solid #efefef", marginTop: "0px", backgroundColor: "#f2f2f2" }}> + {this.state.isSummaryLoading ? <AppLoader /> : + <ReservationSummary reservation={reservation} closeCallback={this.closeSUDets}></ReservationSummary> + } + </div> + } + </div> + } {/* SU Item Tooltip popover with SU status color */} <OverlayPanel className="timeline-popover" ref={(el) => this.popOver = el} dismissable> - {(mouseOverItem && (["SCHEDULE", "TASK"].indexOf(mouseOverItem.type)>=0)) && - <div className={`p-grid su-${mouseOverItem.status}`} style={{width: '350px'}}> - <h3 className={`col-12 su-${mouseOverItem.status}-icon`}>{mouseOverItem.type==='SCHEDULE'?'Scheduling Unit ':'Task '}Overview</h3> - <hr></hr> - <label className={`col-5 su-${mouseOverItem.status}-icon`}>Project:</label> - <div className="col-7">{mouseOverItem.project}</div> - <label className={`col-5 su-${mouseOverItem.status}-icon`}>Scheduling Unit:</label> - <div className="col-7">{mouseOverItem.suName}</div> - {mouseOverItem.type==='SCHEDULE' && - <> - - <label className={`col-5 su-${mouseOverItem.status}-icon`}>Scheduler:</label> - <div className="col-7">{mouseOverItem.scheduleMethod}</div> - <label className={`col-5 su-${mouseOverItem.status}-icon`}>Friends:</label> - <div className="col-7">{mouseOverItem.friends?mouseOverItem.friends:"-"}</div> - </>} - {mouseOverItem.type==='TASK' && - <> - <label className={`col-5 su-${mouseOverItem.status}-icon`}>Task Name:</label> - <div className="col-7">{mouseOverItem.name}</div> - </>} - <label className={`col-5 su-${mouseOverItem.status}-icon`}>Start Time:</label> - <div className="col-7">{mouseOverItem.start_time.format(UIConstants.CALENDAR_DATETIME_FORMAT)}</div> - <label className={`col-5 su-${mouseOverItem.status}-icon`}>End Time:</label> - <div className="col-7">{mouseOverItem.end_time.format(UIConstants.CALENDAR_DATETIME_FORMAT)}</div> - {mouseOverItem.type==='SCHEDULE' && - <> - <label className={`col-5 su-${mouseOverItem.status}-icon`}>Antenna Set:</label> - <div className="col-7">{mouseOverItem.antennaSet}</div> - </>} - <label className={`col-5 su-${mouseOverItem.status}-icon`}>Stations:</label> - <div className="col-7">{mouseOverItem.stations.groups}:{mouseOverItem.stations.counts}</div> - <label className={`col-5 su-${mouseOverItem.status}-icon`}>Status:</label> - <div className="col-7">{mouseOverItem.status}</div> - <label className={`col-5 su-${mouseOverItem.status}-icon`}>Duration:</label> - <div className="col-7">{mouseOverItem.duration}</div> - </div> - } - {(mouseOverItem && mouseOverItem.type === "RESERVATION") && - <div className={`p-grid`} style={{width: '350px', backgroundColor: mouseOverItem.bgColor, color: mouseOverItem.color}}> - <h3 className={`col-12`}>Reservation Overview</h3> - <hr></hr> - <label className={`col-5`} style={{color: mouseOverItem.color}}>Name:</label> - <div className="col-7">{mouseOverItem.name}</div> - <label className={`col-5`} style={{color: mouseOverItem.color}}>Description:</label> - <div className="col-7">{mouseOverItem.desc}</div> - <label className={`col-5`} style={{color: mouseOverItem.color}}>Type:</label> - <div className="col-7">{mouseOverItem.activity_type}</div> - <label className={`col-5`} style={{color: mouseOverItem.color}}>Stations:</label> - {/* <div className="col-7"><ListBox options={mouseOverItem.stations} /></div> */} - <div className="col-7 station-list"> - {mouseOverItem.stations.map((station, index) => ( - <div key={`stn-${index}`}>{station}</div> - ))} + {(mouseOverItem && (["SCHEDULE", "TASK"].indexOf(mouseOverItem.type) >= 0)) && + <div className={`p-grid su-${mouseOverItem.status}`} style={{ width: '350px' }}> + <h3 className={`col-12 su-${mouseOverItem.status}-icon`}>{mouseOverItem.type === 'SCHEDULE' ? 'Scheduling Unit ' : 'Task '}Overview</h3> + <hr></hr> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Project:</label> + <div className="col-7">{mouseOverItem.project}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Scheduling Unit:</label> + <div className="col-7">{mouseOverItem.suName}</div> + {mouseOverItem.type === 'SCHEDULE' && + <> + + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Scheduler:</label> + <div className="col-7">{mouseOverItem.scheduleMethod}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Friends:</label> + <div className="col-7">{mouseOverItem.friends ? mouseOverItem.friends : "-"}</div> + </>} + {mouseOverItem.type === 'TASK' && + <> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Task Name:</label> + <div className="col-7">{mouseOverItem.name}</div> + </>} + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Start Time:</label> + <div className="col-7">{mouseOverItem.start_time.format(UIConstants.CALENDAR_DATETIME_FORMAT)}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>End Time:</label> + <div className="col-7">{mouseOverItem.end_time.format(UIConstants.CALENDAR_DATETIME_FORMAT)}</div> + {mouseOverItem.type === 'SCHEDULE' && + <> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Antenna Set:</label> + <div className="col-7">{mouseOverItem.antennaSet}</div> + </>} + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Stations:</label> + <div className="col-7">{mouseOverItem.stations.groups}:{mouseOverItem.stations.counts}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Status:</label> + <div className="col-7">{mouseOverItem.status}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Duration:</label> + <div className="col-7">{mouseOverItem.duration}</div> </div> - <label className={`col-5`} style={{color: mouseOverItem.color}}>Project:</label> - <div className="col-7">{mouseOverItem.project?mouseOverItem.project:"-"}</div> - <label className={`col-5`} style={{color: mouseOverItem.color}}>Start Time:</label> - <div className="col-7">{mouseOverItem.start_time.format(UIConstants.CALENDAR_DATETIME_FORMAT)}</div> - <label className={`col-5`} style={{color: mouseOverItem.color}}>End Time:</label> - <div className="col-7">{mouseOverItem.end_time.format(UIConstants.CALENDAR_DATETIME_FORMAT)}</div> - {/* <label className={`col-5`} style={{color: mouseOverItem.color}}>Stations:</label> + } + {(mouseOverItem && mouseOverItem.type === "RESERVATION") && + <div className={`p-grid`} style={{ width: '350px', backgroundColor: mouseOverItem.bgColor, color: mouseOverItem.color }}> + <h3 className={`col-12`}>Reservation Overview</h3> + <hr></hr> + <label className={`col-5`} style={{ color: mouseOverItem.color }}>Name:</label> + <div className="col-7">{mouseOverItem.name}</div> + <label className={`col-5`} style={{ color: mouseOverItem.color }}>Description:</label> + <div className="col-7">{mouseOverItem.desc}</div> + <label className={`col-5`} style={{ color: mouseOverItem.color }}>Type:</label> + <div className="col-7">{mouseOverItem.activity_type}</div> + <label className={`col-5`} style={{ color: mouseOverItem.color }}>Stations:</label> + {/* <div className="col-7"><ListBox options={mouseOverItem.stations} /></div> */} + <div className="col-7 station-list"> + {mouseOverItem.stations.map((station, index) => ( + <div key={`stn-${index}`}>{station}</div> + ))} + </div> + <label className={`col-5`} style={{ color: mouseOverItem.color }}>Project:</label> + <div className="col-7">{mouseOverItem.project ? mouseOverItem.project : "-"}</div> + <label className={`col-5`} style={{ color: mouseOverItem.color }}>Start Time:</label> + <div className="col-7">{mouseOverItem.start_time.format(UIConstants.CALENDAR_DATETIME_FORMAT)}</div> + <label className={`col-5`} style={{ color: mouseOverItem.color }}>End Time:</label> + <div className="col-7">{mouseOverItem.end_time.format(UIConstants.CALENDAR_DATETIME_FORMAT)}</div> + {/* <label className={`col-5`} style={{color: mouseOverItem.color}}>Stations:</label> <div className="col-7">{mouseOverItem.stations.groups}:{mouseOverItem.stations.counts}</div> */} - <label className={`col-5`} style={{color: mouseOverItem.color}}>Duration:</label> - <div className="col-7">{mouseOverItem.duration}</div> - <label className={`col-5`} style={{color: mouseOverItem.color}}>Planned:</label> - <div className="col-7">{mouseOverItem.planned?'Yes':'No'}</div> - </div> - } + <label className={`col-5`} style={{ color: mouseOverItem.color }}>Duration:</label> + <div className="col-7">{mouseOverItem.duration}</div> + <label className={`col-5`} style={{ color: mouseOverItem.color }}>Planned:</label> + <div className="col-7">{mouseOverItem.planned ? 'Yes' : 'No'}</div> + </div> + } </OverlayPanel> {!this.state.isLoading && - <Websocket url={process.env.REACT_APP_WEBSOCKET_URL} onOpen={this.onConnect} onMessage={this.handleData} onClose={this.onDisconnect} /> } - </React.Fragment> - + <Websocket url={process.env.REACT_APP_WEBSOCKET_URL} onOpen={this.onConnect} onMessage={this.handleData} onClose={this.onDisconnect} />} + </React.Fragment> + ); } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/week.view.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/week.view.js index b4450b421a3ddf0d1c468a14ab538c24a7f1dc85..a260824d3f52aa7ebca2ee9a94edfd38548fb718 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/week.view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/week.view.js @@ -1,4 +1,4 @@ -import React, {Component} from 'react'; +import React, { Component } from 'react'; import { Redirect } from 'react-router-dom/cjs/react-router-dom.min'; import moment from 'moment'; import _ from 'lodash'; @@ -28,34 +28,40 @@ import { Dropdown } from 'primereact/dropdown'; import ReservationSummary from '../Reservation/reservation.summary'; // Color constant for status -const STATUS_COLORS = { "ERROR": "FF0000", "CANCELLED": "#00FF00", "DEFINED": "#00BCD4", - "SCHEDULABLE":"#0000FF", "SCHEDULED": "#abc", "OBSERVING": "#bcd", - "OBSERVED": "#cde", "PROCESSING": "#cddc39", "PROCESSED": "#fed", - "INGESTING": "#edc", "FINISHED": "#47d53d"}; +const STATUS_COLORS = { + "ERROR": "FF0000", "CANCELLED": "#00FF00", "DEFINED": "#00BCD4", + "SCHEDULABLE": "#0000FF", "SCHEDULED": "#abc", "OBSERVING": "#bcd", + "OBSERVED": "#cde", "PROCESSING": "#cddc39", "PROCESSED": "#fed", + "INGESTING": "#edc", "FINISHED": "#47d53d" +}; -const RESERVATION_COLORS = {"true-true":{bgColor:"lightgrey", color:"#585859"}, "true-false":{bgColor:'#585859', color:"white"}, - "false-true":{bgColor:"#9b9999", color:"white"}, "false-false":{bgColor:"black", color:"white"}}; +const RESERVATION_COLORS = { + "true-true": { bgColor: "lightgrey", color: "#585859" }, "true-false": { bgColor: '#585859', color: "white" }, + "false-true": { bgColor: "#9b9999", color: "white" }, "false-false": { bgColor: "black", color: "white" } +}; /** * Scheduling Unit timeline view component to view SU List and timeline */ export class WeekTimelineView extends Component { - + lsKeySortColumn = 'SortDataWeekTimelineView-WeekView'; + defaultSortColumn = []; constructor(props) { super(props); + this.setToggleBySorting(); this.state = { isLoading: true, suBlueprints: [], // Scheduling Unit Blueprints suDrafts: [], // Scheduling Unit Drafts suBlueprintList: [], // SU Blueprints filtered to view - group:[], // Timeline group from scheduling unit draft name - items:[], // Timeline items from scheduling unit blueprints grouped by scheduling unit draft + group: [], // Timeline group from scheduling unit draft name + items: [], // Timeline items from scheduling unit blueprints grouped by scheduling unit draft isSUListVisible: true, isSUDetsVisible: false, canExtendSUList: true, canShrinkSUList: false, selectedItem: null, - suTaskList:[], + suTaskList: [], isSummaryLoading: false, stationGroup: [], reservationEnabled: true @@ -65,10 +71,10 @@ export class WeekTimelineView extends Component { this.reservations = []; this.reservationReasons = []; this.optionsMenu = React.createRef(); - this.menuOptions = [ {label:'Add Reservation', icon: "fa fa-", command: () => {this.selectOptionMenu('Add Reservation')}}, - {label:'Reservation List', icon: "fa fa-", command: () => {this.selectOptionMenu('Reservation List')}}, - ]; - + this.menuOptions = [{ label: 'Add Reservation', icon: "fa fa-", command: () => { this.selectOptionMenu('Add Reservation') } }, + { label: 'Reservation List', icon: "fa fa-", command: () => { this.selectOptionMenu('Reservation List') } }, + ]; + this.showOptionMenu = this.showOptionMenu.bind(this); this.selectOptionMenu = this.selectOptionMenu.bind(this); this.onItemClick = this.onItemClick.bind(this); @@ -88,25 +94,26 @@ export class WeekTimelineView extends Component { } async componentDidMount() { + this.setToggleBySorting(); UtilService.getReservationTemplates().then(templates => { - this.reservationTemplate = templates.length>0?templates[0]:null; + this.reservationTemplate = templates.length > 0 ? templates[0] : null; if (this.reservationTemplate) { let reasons = this.reservationTemplate.schema.properties.activity.properties.type.enum; for (const reason of reasons) { - this.reservationReasons.push({name: reason}); + this.reservationReasons.push({ name: reason }); } } }); - + // Fetch all details from server and prepare data to pass to timeline and table components - const promises = [ ProjectService.getProjectList(), - ScheduleService.getSchedulingUnitsExtended('blueprint'), - ScheduleService.getSchedulingUnitDraft(), - ScheduleService.getSchedulingSets(), - UtilService.getUTC(), - TaskService.getSubtaskTemplates(), - UtilService.getReservations()] ; - Promise.all(promises).then(async(responses) => { + const promises = [ProjectService.getProjectList(), + ScheduleService.getSchedulingUnitsExtended('blueprint'), + ScheduleService.getSchedulingUnitDraft(), + ScheduleService.getSchedulingSets(), + UtilService.getUTC(), + TaskService.getSubtaskTemplates(), + UtilService.getReservations()]; + Promise.all(promises).then(async (responses) => { this.subtaskTemplates = responses[5]; const projects = responses[0]; const suBlueprints = _.sortBy(responses[1], 'name'); @@ -119,15 +126,15 @@ export class WeekTimelineView extends Component { const defaultEndTime = moment.utc().day(8).hour(23).minutes(59).seconds(59); for (const count of _.range(11)) { const groupDate = defaultStartTime.clone().add(count, 'days'); - group.push({'id': groupDate.format("MMM DD ddd"), title: groupDate.format("MMM DD - ddd"), value: groupDate}); + group.push({ 'id': groupDate.format("MMM DD ddd"), title: groupDate.format("MMM DD - ddd"), value: groupDate }); } let suList = []; for (const suDraft of suDrafts) { - const suSet = suSets.find((suSet) => { return suDraft.scheduling_set_id===suSet.id}); - const project = projects.find((project) => { return suSet.project_id===project.name}); + const suSet = suSets.find((suSet) => { return suDraft.scheduling_set_id === suSet.id }); + const project = projects.find((project) => { return suSet.project_id === project.name }); if (suDraft.scheduling_unit_blueprints.length > 0) { for (const suBlueprintId of suDraft.scheduling_unit_blueprints_ids) { - const suBlueprint = _.find(suBlueprints, {'id': suBlueprintId}); + const suBlueprint = _.find(suBlueprints, { 'id': suBlueprintId }); suBlueprint['actionpath'] = `/schedulingunit/view/blueprint/${suBlueprintId}`; suBlueprint.suDraft = suDraft; suBlueprint.project = project.name; @@ -136,23 +143,23 @@ export class WeekTimelineView extends Component { suBlueprint.duration = UnitConverter.getSecsToHHmmss(suBlueprint.duration); suBlueprint.tasks = suBlueprint.task_blueprints; // Select only blueprints with start_time and stop_time in the default time limit - if (suBlueprint.start_time && + if (suBlueprint.start_time && ((moment.utc(suBlueprint.start_time).isBetween(defaultStartTime, defaultEndTime) || - moment.utc(suBlueprint.stop_time).isBetween(defaultStartTime, defaultEndTime)) - || (moment.utc(suBlueprint.start_time).isSameOrBefore(defaultStartTime, defaultEndTime) && - moment.utc(suBlueprint.stop_time).isSameOrAfter(defaultStartTime, defaultEndTime)))) { + moment.utc(suBlueprint.stop_time).isBetween(defaultStartTime, defaultEndTime)) + || (moment.utc(suBlueprint.start_time).isSameOrBefore(defaultStartTime, defaultEndTime) && + moment.utc(suBlueprint.stop_time).isSameOrAfter(defaultStartTime, defaultEndTime)))) { const startTime = moment.utc(suBlueprint.start_time); const endTime = moment.utc(suBlueprint.stop_time); if (startTime.format("MM-DD-YYYY") !== endTime.format("MM-DD-YYYY")) { - let suBlueprintStart = _.cloneDeep(suBlueprint); + let suBlueprintStart = _.cloneDeep(suBlueprint); let suBlueprintEnd = _.cloneDeep(suBlueprint); suBlueprintStart.stop_time = startTime.hour(23).minutes(59).seconds(59).format('YYYY-MM-DDTHH:mm:ss.00000'); suBlueprintEnd.start_time = endTime.hour(0).minutes(0).seconds(0).format('YYYY-MM-DDTHH:mm:ss.00000'); items.push(await this.getTimelineItem(suBlueprintStart, currentUTC)); items.push(await this.getTimelineItem(suBlueprintEnd, currentUTC)); - - } else { + + } else { items.push(await this.getTimelineItem(suBlueprint, currentUTC)); } suList.push(suBlueprint); @@ -163,7 +170,7 @@ export class WeekTimelineView extends Component { const template = _.find(this.subtaskTemplates, ['id', subtask.specifications_template_id]); return (template && template.name.indexOf('control')) > 0; }); - task.subTaskID = subTaskIds.length ? subTaskIds[0].id : ''; + task.subTaskID = subTaskIds.length ? subTaskIds[0].id : ''; if (task.specifications_template.type_value.toLowerCase() === "observation") { task.antenna_set = task.specifications_doc.antenna_set; task.band = task.specifications_doc.filter; @@ -182,18 +189,36 @@ export class WeekTimelineView extends Component { ScheduleService.getSchedulingConstraintTemplates() .then(suConstraintTemplates => { this.suConstraintTemplates = suConstraintTemplates; + }); + this.setState({ + suBlueprints: suBlueprints, suDrafts: suDrafts, group: _.sortBy(group, ['value']), suSets: suSets, + projects: projects, suBlueprintList: suList, + items: items, currentUTC: currentUTC, isLoading: false, + startTime: defaultStartTime, endTime: defaultEndTime }); - this.setState({suBlueprints: suBlueprints, suDrafts: suDrafts, group: _.sortBy(group, ['value']), suSets: suSets, - projects: projects, suBlueprintList: suList, - items: items, currentUTC: currentUTC, isLoading: false, - startTime: defaultStartTime, endTime: defaultEndTime - }); }); // Get maingroup and its stations. This grouping is used to show count of stations used against each group. ScheduleService.getMainGroupStations() - .then(stationGroups => {this.mainStationGroups = stationGroups}); + .then(stationGroups => { this.mainStationGroups = stationGroups }); } + toggleBySorting = (sortData) => { + UtilService.localStore({ type: 'set', key: this.lsKeySortColumn, value: sortData }); + } + + setToggleBySorting() { + let sortData = UtilService.localStore({ type: 'get', key: this.lsKeySortColumn }); + if (sortData) { + if (Object.prototype.toString.call(sortData) === '[object Array]') { + this.defaultSortColumn = sortData; + } + else { + this.defaultSortColumn = [{ ...sortData }]; + } + } + this.defaultSortColumn = this.defaultSortColumn || []; + UtilService.localStore({ type: 'set', key: this.lsKeySortColumn, value: [...this.defaultSortColumn] }); + } /** * Function to get/prepare Item object to be passed to Timeline component * @param {Object} suBlueprint @@ -201,27 +226,29 @@ export class WeekTimelineView extends Component { async getTimelineItem(suBlueprint, displayDate) { let antennaSet = ""; for (let task of suBlueprint.tasks) { - if (task.specifications_template.type_value.toLowerCase() === "observation" + if (task.specifications_template.type_value.toLowerCase() === "observation" && task.specifications_doc.antenna_set) { antennaSet = task.specifications_doc.antenna_set; } } - let item = { id: `${suBlueprint.id}-${suBlueprint.start_time}`, + let item = { + id: `${suBlueprint.id}-${suBlueprint.start_time}`, suId: suBlueprint.id, group: moment.utc(suBlueprint.start_time).format("MMM DD ddd"), title: "", project: suBlueprint.project, name: suBlueprint.name, - band: antennaSet?antennaSet.split("_")[0]:"", + band: antennaSet ? antennaSet.split("_")[0] : "", antennaSet: antennaSet, scheduleMethod: suBlueprint.suDraft.scheduling_constraints_doc.scheduler, - duration: suBlueprint.durationInSec?`${(suBlueprint.durationInSec/3600).toFixed(2)}Hrs`:"", + duration: suBlueprint.durationInSec ? `${(suBlueprint.durationInSec / 3600).toFixed(2)}Hrs` : "", start_time: moment.utc(`${displayDate.format('YYYY-MM-DD')} ${suBlueprint.start_time.split('T')[1]}`), end_time: moment.utc(`${displayDate.format('YYYY-MM-DD')} ${suBlueprint.stop_time.split('T')[1]}`), - bgColor: suBlueprint.status? STATUS_COLORS[suBlueprint.status.toUpperCase()]:"#2196f3", + bgColor: suBlueprint.status ? STATUS_COLORS[suBlueprint.status.toUpperCase()] : "#2196f3", selectedBgColor: 'none', type: 'SCHEDULE', - status: suBlueprint.status.toLowerCase()}; + status: suBlueprint.status.toLowerCase() + }; return item; } @@ -229,10 +256,10 @@ export class WeekTimelineView extends Component { * Callback function to pass to Timeline component for item click. * @param {Object} item */ - onItemClick(item) { - if (item.type === "SCHEDULE") { + onItemClick(item) { + if (item.type === "SCHEDULE") { this.showSUSummary(item); - } else if (item.type === "RESERVATION") { + } else if (item.type === "RESERVATION") { this.showReservationSummary(item); } } @@ -242,36 +269,42 @@ export class WeekTimelineView extends Component { * @param {Object} item - Timeline SU item object. */ showSUSummary(item) { - if (this.state.isSUDetsVisible && item.id===this.state.selectedItem.id) { + if (this.state.isSUDetsVisible && item.id === this.state.selectedItem.id) { this.closeSUDets(); - } else { - const fetchDetails = !this.state.selectedItem || item.id!==this.state.selectedItem.id - this.setState({selectedItem: item, isSUDetsVisible: true, + } else { + const fetchDetails = !this.state.selectedItem || item.id !== this.state.selectedItem.id + this.setState({ + selectedItem: item, isSUDetsVisible: true, isSummaryLoading: fetchDetails, - suTaskList: !fetchDetails?this.state.suTaskList:[], - canExtendSUList: false, canShrinkSUList:false}); + suTaskList: !fetchDetails ? this.state.suTaskList : [], + canExtendSUList: false, canShrinkSUList: false + }); if (fetchDetails) { - const suBlueprint = _.find(this.state.suBlueprints, {id: parseInt(item.id.split('-')[0])}); - const suConstraintTemplate = _.find(this.suConstraintTemplates, {id: suBlueprint.suDraft.scheduling_constraints_template_id}); + const suBlueprint = _.find(this.state.suBlueprints, { id: parseInt(item.id.split('-')[0]) }); + const suConstraintTemplate = _.find(this.suConstraintTemplates, { id: suBlueprint.suDraft.scheduling_constraints_template_id }); /* If tasks are not loaded on component mounting fetch from API */ if (suBlueprint.tasks) { - this.setState({suTaskList: _.sortBy(suBlueprint.tasks, "id"), suConstraintTemplate: suConstraintTemplate, - stationGroup: suBlueprint.stations, isSummaryLoading: false}) - } else { + this.setState({ + suTaskList: _.sortBy(suBlueprint.tasks, "id"), suConstraintTemplate: suConstraintTemplate, + stationGroup: suBlueprint.stations, isSummaryLoading: false + }) + } else { ScheduleService.getTaskBPWithSubtaskTemplateOfSU(suBlueprint) .then(taskList => { for (let task of taskList) { //Control Task ID const subTaskIds = (task.subTasks || []).filter(sTask => sTask.subTaskTemplate.name.indexOf('control') > 1); - task.subTaskID = subTaskIds.length ? subTaskIds[0].id : ''; + task.subTaskID = subTaskIds.length ? subTaskIds[0].id : ''; if (task.template.type_value.toLowerCase() === "observation" && task.specifications_doc.antenna_set) { task.antenna_set = task.specifications_doc.antenna_set; task.band = task.specifications_doc.filter; } } - this.setState({suTaskList: _.sortBy(taskList, "id"), isSummaryLoading: false, - stationGroup: this.getSUStations(suBlueprint)}) + this.setState({ + suTaskList: _.sortBy(taskList, "id"), isSummaryLoading: false, + stationGroup: this.getSUStations(suBlueprint) + }) }); } // Get the scheduling constraint template of the selected SU block @@ -287,15 +320,15 @@ export class WeekTimelineView extends Component { * To load and show Reservation summary * @param {Object} item */ - showReservationSummary(item) { - this.setState({selectedItem: item, isReservDetsVisible: true, isSUDetsVisible: false}); + showReservationSummary(item) { + this.setState({ selectedItem: item, isReservDetsVisible: true, isSUDetsVisible: false }); } /** * Closes the SU details section */ closeSUDets() { - this.setState({isSUDetsVisible: false, isReservDetsVisible: false, canExtendSUList: true, canShrinkSUList: false}); + this.setState({ isSUDetsVisible: false, isReservDetsVisible: false, canExtendSUList: true, canShrinkSUList: false }); } /** @@ -313,10 +346,10 @@ export class WeekTimelineView extends Component { */ onItemMouseOver(evt, item) { if (item.type === "SCHEDULE") { - const itemSU = _.find(this.state.suBlueprints, {id: parseInt(item.id.split("-")[0])}); + const itemSU = _.find(this.state.suBlueprints, { id: parseInt(item.id.split("-")[0]) }); const itemStations = itemSU.stations; const itemStationGroups = this.groupSUStations(itemStations); - item.stations = {groups: "", counts: ""}; + item.stations = { groups: "", counts: "" }; for (const stationgroup of _.keys(itemStationGroups)) { let groups = item.stations.groups; let counts = item.stations.counts; @@ -324,15 +357,15 @@ export class WeekTimelineView extends Component { groups = groups.concat("/"); counts = counts.concat("/"); } - groups = groups.concat(stationgroup.substring(0,1).concat('S')); + groups = groups.concat(stationgroup.substring(0, 1).concat('S')); counts = counts.concat(itemStationGroups[stationgroup].length); item.stations.groups = groups; item.stations.counts = counts; item.suStartTime = moment.utc(itemSU.start_time); item.suStopTime = moment.utc(itemSU.stop_time); } - } else { - const reservation = _.find(this.reservations, {'id': parseInt(item.id.split("-")[1])}); + } else { + const reservation = _.find(this.reservations, { 'id': parseInt(item.id.split("-")[1]) }); const reservStations = reservation.specifications_doc.resources.stations; // const reservStationGroups = this.groupSUStations(reservStations); item.name = reservation.name; @@ -341,10 +374,10 @@ export class WeekTimelineView extends Component { item.stations = reservStations; item.planned = reservation.specifications_doc.activity.planned; item.displayStartTime = moment.utc(reservation.start_time); - item.displayEndTime = reservation.duration?moment.utc(reservation.stop_time):null; + item.displayEndTime = reservation.duration ? moment.utc(reservation.stop_time) : null; } this.popOver.toggle(evt); - this.setState({mouseOverItem: item}); + this.setState({ mouseOverItem: item }); } /** @@ -366,7 +399,7 @@ export class WeekTimelineView extends Component { getSUStations(suBlueprint) { let stations = []; /* Get all observation tasks */ - const observationTasks = _.filter(suBlueprint.tasks, (task) => { return task.specifications_template.type_value.toLowerCase() === "observation"}); + const observationTasks = _.filter(suBlueprint.tasks, (task) => { return task.specifications_template.type_value.toLowerCase() === "observation" }); for (const observationTask of observationTasks) { /** If the status of SU is before scheduled, get all stations from the station_groups from the task specification_docs */ if (this.STATUS_BEFORE_SCHEDULED.indexOf(suBlueprint.status.toLowerCase()) >= 0 @@ -374,8 +407,8 @@ export class WeekTimelineView extends Component { for (const grpStations of _.map(observationTask.specifications_doc.station_groups, "stations")) { stations = _.concat(stations, grpStations); } - } else if (this.STATUS_BEFORE_SCHEDULED.indexOf(suBlueprint.status.toLowerCase()) < 0 - && observationTask.subtasks) { + } else if (this.STATUS_BEFORE_SCHEDULED.indexOf(suBlueprint.status.toLowerCase()) < 0 + && observationTask.subtasks) { /** If the status of SU is scheduled or after get the stations from the subtask specification tasks */ for (const subtask of observationTask.subtasks) { if (subtask.specifications_doc.stations) { @@ -393,21 +426,21 @@ export class WeekTimelineView extends Component { * @param {moment} endTime */ async dateRangeCallback(startTime, endTime, refreshData) { - let suBlueprintList = [], group=[], items = []; + let suBlueprintList = [], group = [], items = []; let currentUTC = this.state.currentUTC; if (refreshData) { for (const count of _.range(11)) { const groupDate = startTime.clone().add(count, 'days'); - group.push({'id': groupDate.format("MMM DD ddd"), title: groupDate.format("MMM DD - ddd"), value: groupDate}); + group.push({ 'id': groupDate.format("MMM DD ddd"), title: groupDate.format("MMM DD - ddd"), value: groupDate }); } let direction = startTime.week() - this.state.startTime.week(); currentUTC = this.state.currentUTC.clone().add(direction * 7, 'days'); if (startTime && endTime) { for (const suBlueprint of this.state.suBlueprints) { - if (moment.utc(suBlueprint.start_time).isBetween(startTime, endTime) - || moment.utc(suBlueprint.stop_time).isBetween(startTime, endTime) - || (moment.utc(suBlueprint.start_time).isSameOrBefore(startTime, endTime) && - moment.utc(suBlueprint.stop_time).isSameOrAfter(startTime, endTime))) { + if (moment.utc(suBlueprint.start_time).isBetween(startTime, endTime) + || moment.utc(suBlueprint.stop_time).isBetween(startTime, endTime) + || (moment.utc(suBlueprint.start_time).isSameOrBefore(startTime, endTime) && + moment.utc(suBlueprint.stop_time).isSameOrAfter(startTime, endTime))) { suBlueprintList.push(suBlueprint); const suStartTime = moment.utc(suBlueprint.start_time); const suEndTime = moment.utc(suBlueprint.stop_time); @@ -418,29 +451,31 @@ export class WeekTimelineView extends Component { suBlueprintEnd.start_time = suEndTime.hour(0).minutes(0).seconds(0).format('YYYY-MM-DDTHH:mm:ss.00000'); items.push(await this.getTimelineItem(suBlueprintStart, currentUTC)); items.push(await this.getTimelineItem(suBlueprintEnd, currentUTC)); - - } else { + + } else { items.push(await this.getTimelineItem(suBlueprint, currentUTC)); } - } + } } if (this.state.reservationEnabled) { items = this.addWeekReservations(items, startTime, endTime, currentUTC); } - } else { + } else { suBlueprintList = _.clone(this.state.suBlueprints); group = this.state.group; items = this.state.items; } - this.setState({suBlueprintList: _.filter(suBlueprintList, (suBlueprint) => {return suBlueprint.start_time!=null}), - group: group, items: items, currentUTC: currentUTC, startTime: startTime, endTime: endTime}); + this.setState({ + suBlueprintList: _.filter(suBlueprintList, (suBlueprint) => { return suBlueprint.start_time != null }), + group: group, items: items, currentUTC: currentUTC, startTime: startTime, endTime: endTime + }); // On range change close the Details pane // this.closeSUDets(); - } else { + } else { group = this.state.group; items = this.state.items; } - return {group: group, items: items}; + return { group: group, items: items }; } /** @@ -452,14 +487,14 @@ export class WeekTimelineView extends Component { let canShrinkSUList = this.state.canShrinkSUList; if (step === 1) { // Can Extend when fully shrunk and still extendable - canExtendSUList = (!canShrinkSUList && canExtendSUList)?true:false; + canExtendSUList = (!canShrinkSUList && canExtendSUList) ? true : false; canShrinkSUList = true; - } else { + } else { // Can Shrink when fully extended and still shrinkable - canShrinkSUList = (canShrinkSUList && !canExtendSUList)?true:false; + canShrinkSUList = (canShrinkSUList && !canExtendSUList) ? true : false; canExtendSUList = true; } - this.setState({canExtendSUList: canExtendSUList, canShrinkSUList: canShrinkSUList}); + this.setState({ canExtendSUList: canExtendSUList, canShrinkSUList: canShrinkSUList }); } /** @@ -482,7 +517,7 @@ export class WeekTimelineView extends Component { } filterByProject(project) { - this.setState({selectedProject: project}); + this.setState({ selectedProject: project }); } showOptionMenu(event) { @@ -490,13 +525,13 @@ export class WeekTimelineView extends Component { } selectOptionMenu(menuName) { - switch(menuName) { + switch (menuName) { case 'Reservation List': { - this.setState({redirect: `/reservation/list`}); + this.setState({ redirect: `/reservation/list` }); break; } case 'Add Reservation': { - this.setState({redirect: `/reservation/create`}); + this.setState({ redirect: `/reservation/create` }); break; } default: { @@ -528,7 +563,7 @@ export class WeekTimelineView extends Component { const jsonData = JSON.parse(data); if (jsonData.action === 'create') { this.addNewData(jsonData.object_details.id, jsonData.object_type, jsonData.object_details); - } else if (jsonData.action === 'update') { + } else if (jsonData.action === 'update') { this.updateExistingData(jsonData.object_details.id, jsonData.object_type, jsonData.object_details); } } @@ -541,18 +576,18 @@ export class WeekTimelineView extends Component { * @param {Object} object - model object with certain properties */ addNewData(id, type, object) { - switch(type) { + switch (type) { /* When a new scheduling_unit_draft is created, it should be added to the existing list of suDraft. */ case 'scheduling_unit_draft': { let suDrafts = this.state.suDrafts; let suSets = this.state.suSets; ScheduleService.getSchedulingUnitDraftById(id) - .then(suDraft => { - suDrafts.push(suDraft); - _.remove(suSets, function(suSet) { return suSet.id === suDraft.scheduling_set_id}); - suSets.push(suDraft.scheduling_set_object); - this.setState({suSet: suSets, suDrafts: suDrafts}); - }); + .then(suDraft => { + suDrafts.push(suDraft); + _.remove(suSets, function (suSet) { return suSet.id === suDraft.scheduling_set_id }); + suSets.push(suDraft.scheduling_set_object); + this.setState({ suSet: suSets, suDrafts: suDrafts }); + }); break; } case 'scheduling_unit_blueprint': { @@ -576,7 +611,7 @@ export class WeekTimelineView extends Component { */ updateExistingData(id, type, object) { const objectProps = ['status', 'start_time', 'stop_time', 'duration']; - switch(type) { + switch (type) { case 'scheduling_unit_blueprint': { let suBlueprints = this.state.suBlueprints; let existingSUB = _.find(suBlueprints, ['id', id]); @@ -594,7 +629,7 @@ export class WeekTimelineView extends Component { // } break; } - default: { break;} + default: { break; } } } @@ -605,46 +640,46 @@ export class WeekTimelineView extends Component { */ updateSchedulingUnit(id) { ScheduleService.getSchedulingUnitExtended('blueprint', id, true) - .then(async(suBlueprint) => { - const suDraft = _.find(this.state.suDrafts, ['id', suBlueprint.draft_id]); - const suSet = this.state.suSets.find((suSet) => { return suDraft.scheduling_set_id===suSet.id}); - const project = this.state.projects.find((project) => { return suSet.project_id===project.name}); - let suBlueprints = this.state.suBlueprints; - suBlueprint['actionpath'] = `/schedulingunit/view/blueprint/${id}`; - suBlueprint.suDraft = suDraft; - suBlueprint.project = project.name; - suBlueprint.suSet = suSet; - suBlueprint.durationInSec = suBlueprint.duration; - suBlueprint.duration = UnitConverter.getSecsToHHmmss(suBlueprint.duration); - suBlueprint.tasks = suBlueprint.task_blueprints; - // Add Subtask Id as control id for task if subtask type us control. Also add antenna_set & band prpoerties to the task object. - for (let task of suBlueprint.tasks) { - const subTaskIds = task.subtasks.filter(subtask => { - const template = _.find(this.subtaskTemplates, ['id', subtask.specifications_template_id]); - return (template && template.name.indexOf('control')) > 0; - }); - task.subTaskID = subTaskIds.length ? subTaskIds[0].id : ''; - if (task.specifications_template.type_value.toLowerCase() === "observation" - && task.specifications_doc.antenna_set) { - task.antenna_set = task.specifications_doc.antenna_set; - task.band = task.specifications_doc.filter; + .then(async (suBlueprint) => { + const suDraft = _.find(this.state.suDrafts, ['id', suBlueprint.draft_id]); + const suSet = this.state.suSets.find((suSet) => { return suDraft.scheduling_set_id === suSet.id }); + const project = this.state.projects.find((project) => { return suSet.project_id === project.name }); + let suBlueprints = this.state.suBlueprints; + suBlueprint['actionpath'] = `/schedulingunit/view/blueprint/${id}`; + suBlueprint.suDraft = suDraft; + suBlueprint.project = project.name; + suBlueprint.suSet = suSet; + suBlueprint.durationInSec = suBlueprint.duration; + suBlueprint.duration = UnitConverter.getSecsToHHmmss(suBlueprint.duration); + suBlueprint.tasks = suBlueprint.task_blueprints; + // Add Subtask Id as control id for task if subtask type us control. Also add antenna_set & band prpoerties to the task object. + for (let task of suBlueprint.tasks) { + const subTaskIds = task.subtasks.filter(subtask => { + const template = _.find(this.subtaskTemplates, ['id', subtask.specifications_template_id]); + return (template && template.name.indexOf('control')) > 0; + }); + task.subTaskID = subTaskIds.length ? subTaskIds[0].id : ''; + if (task.specifications_template.type_value.toLowerCase() === "observation" + && task.specifications_doc.antenna_set) { + task.antenna_set = task.specifications_doc.antenna_set; + task.band = task.specifications_doc.filter; + } } - } - // Get stations involved for this SUB - let stations = this.getSUStations(suBlueprint); - suBlueprint.stations = _.uniq(stations); - // Remove the old SUB object from the existing list and add the newly fetched SUB - _.remove(suBlueprints, function(suB) { return suB.id === id}); - suBlueprints.push(suBlueprint); - this.setState({suBlueprints: suBlueprints}); - // Create timeline group and items - let updatedItemGroupData = await this.dateRangeCallback(this.state.startTime, this.state.endTime, true); - this.timeline.updateTimeline(updatedItemGroupData); - }); + // Get stations involved for this SUB + let stations = this.getSUStations(suBlueprint); + suBlueprint.stations = _.uniq(stations); + // Remove the old SUB object from the existing list and add the newly fetched SUB + _.remove(suBlueprints, function (suB) { return suB.id === id }); + suBlueprints.push(suBlueprint); + this.setState({ suBlueprints: suBlueprints }); + // Create timeline group and items + let updatedItemGroupData = await this.dateRangeCallback(this.state.startTime, this.state.endTime, true); + this.timeline.updateTimeline(updatedItemGroupData); + }); } async showReservations(e) { - await this.setState({reservationEnabled: e.value}); + await this.setState({ reservationEnabled: e.value }); let updatedItemGroupData = await this.dateRangeCallback(this.state.startTime, this.state.endTime, true); this.timeline.updateTimeline(updatedItemGroupData); } @@ -655,28 +690,28 @@ export class WeekTimelineView extends Component { * @param {moment} startTime * @param {moment} endTime */ - addWeekReservations(items, startTime, endTime, currentUTC) { + addWeekReservations(items, startTime, endTime, currentUTC) { let reservations = this.reservations; for (const reservation of reservations) { const reservationStartTime = moment.utc(reservation.start_time); - const reservationEndTime = reservation.duration?reservationStartTime.clone().add(reservation.duration, 'seconds'):endTime; + const reservationEndTime = reservation.duration ? reservationStartTime.clone().add(reservation.duration, 'seconds') : endTime; const reservationSpec = reservation.specifications_doc; - if ( (reservationStartTime.isSame(startTime) - || reservationStartTime.isSame(endTime) - || reservationStartTime.isBetween(startTime, endTime) - || reservationEndTime.isSame(startTime) - || reservationEndTime.isSame(endTime) - || reservationEndTime.isBetween(startTime, endTime) - || (reservationStartTime.isSameOrBefore(startTime) + if ((reservationStartTime.isSame(startTime) + || reservationStartTime.isSame(endTime) + || reservationStartTime.isBetween(startTime, endTime) + || reservationEndTime.isSame(startTime) + || reservationEndTime.isSame(endTime) + || reservationEndTime.isBetween(startTime, endTime) + || (reservationStartTime.isSameOrBefore(startTime) && reservationEndTime.isSameOrAfter(endTime))) - && (!this.state.reservationFilter || // No reservation filter added - reservationSpec.activity.type === this.state.reservationFilter) ) { // Reservation reason == Filtered reaseon + && (!this.state.reservationFilter || // No reservation filter added + reservationSpec.activity.type === this.state.reservationFilter)) { // Reservation reason == Filtered reaseon reservation.stop_time = reservationEndTime; let splitReservations = this.splitReservations(reservation, startTime, endTime, currentUTC); for (const splitReservation of splitReservations) { items.push(this.getReservationItem(splitReservation, currentUTC)); } - + } } return items; @@ -694,23 +729,23 @@ export class WeekTimelineView extends Component { let weekStartDate = moment(startTime).add(-1, 'day').startOf('day'); let weekEndDate = moment(endTime).add(1, 'day').startOf('day'); let splitReservations = []; - while(weekStartDate.add(1, 'days').diff(weekEndDate) < 0) { + while (weekStartDate.add(1, 'days').diff(weekEndDate) < 0) { const dayStart = weekStartDate.clone().startOf('day'); const dayEnd = weekStartDate.clone().endOf('day'); let splitReservation = null; - if (reservationStartTime.isSameOrBefore(dayStart) && + if (reservationStartTime.isSameOrBefore(dayStart) && (reservation.stop_time.isBetween(dayStart, dayEnd) || reservation.stop_time.isSameOrAfter(dayEnd))) { splitReservation = _.cloneDeep(reservation); splitReservation.start_time = moment.utc(dayStart.format("YYYY-MM-DD HH:mm:ss")); - } else if(reservationStartTime.isBetween(dayStart, dayEnd)) { + } else if (reservationStartTime.isBetween(dayStart, dayEnd)) { splitReservation = _.cloneDeep(reservation); - splitReservation.start_time = reservationStartTime; + splitReservation.start_time = reservationStartTime; } if (splitReservation) { if (!reservation.stop_time || reservation.stop_time.isSameOrAfter(dayEnd)) { splitReservation.end_time = weekStartDate.clone().hour(23).minute(59).seconds(59); - } else if (reservation.stop_time.isSameOrBefore(dayEnd)) { + } else if (reservation.stop_time.isSameOrBefore(dayEnd)) { splitReservation.end_time = weekStartDate.clone().hour(reservation.stop_time.hours()).minutes(reservation.stop_time.minutes()).seconds(reservation.stop_time.seconds); } splitReservations.push(splitReservation); @@ -728,17 +763,18 @@ export class WeekTimelineView extends Component { const reservationSpec = reservation.specifications_doc; const group = moment.utc(reservation.start_time).format("MMM DD ddd"); const blockColor = RESERVATION_COLORS[this.getReservationType(reservationSpec.schedulability)]; - let item = { id: `Res-${reservation.id}-${group}`, - start_time: moment.utc(`${displayDate.format('YYYY-MM-DD')} ${reservation.start_time.format('HH:mm:ss')}`), - end_time: moment.utc(`${displayDate.format('YYYY-MM-DD')} ${reservation.end_time.format('HH:mm:ss')}`), - name: reservationSpec.activity.type, project: reservation.project_id, - group: group, - type: 'RESERVATION', - title: `${reservationSpec.activity.type}${reservation.project_id?("-"+ reservation.project_id):""}`, - desc: reservation.description, - duration: reservation.duration?UnitConverter.getSecsToHHmmss(reservation.duration):"Unknown", - bgColor: blockColor.bgColor, selectedBgColor: blockColor.bgColor, color: blockColor.color - }; + let item = { + id: `Res-${reservation.id}-${group}`, + start_time: moment.utc(`${displayDate.format('YYYY-MM-DD')} ${reservation.start_time.format('HH:mm:ss')}`), + end_time: moment.utc(`${displayDate.format('YYYY-MM-DD')} ${reservation.end_time.format('HH:mm:ss')}`), + name: reservationSpec.activity.type, project: reservation.project_id, + group: group, + type: 'RESERVATION', + title: `${reservationSpec.activity.type}${reservation.project_id ? ("-" + reservation.project_id) : ""}`, + desc: reservation.description, + duration: reservation.duration ? UnitConverter.getSecsToHHmmss(reservation.duration) : "Unknown", + bgColor: blockColor.bgColor, selectedBgColor: blockColor.bgColor, color: blockColor.color + }; return item; } @@ -747,14 +783,14 @@ export class WeekTimelineView extends Component { * according to the type. * @param {Object} schedulability */ - getReservationType(schedulability) { + getReservationType(schedulability) { if (schedulability.manual && schedulability.dynamic) { return 'true-true'; - } else if (!schedulability.manual && !schedulability.dynamic) { + } else if (!schedulability.manual && !schedulability.dynamic) { return 'false-false'; - } else if (schedulability.manual && !schedulability.dynamic) { + } else if (schedulability.manual && !schedulability.dynamic) { return 'true-false'; - } else { + } else { return 'false-true'; } } @@ -764,14 +800,14 @@ export class WeekTimelineView extends Component { * @param {String} filter */ async setReservationFilter(filter) { - await this.setState({reservationFilter: filter}); + await this.setState({ reservationFilter: filter }); let updatedItemGroupData = await this.dateRangeCallback(this.state.startTime, this.state.endTime, true); this.timeline.updateTimeline(updatedItemGroupData); } render() { if (this.state.redirect) { - return <Redirect to={ {pathname: this.state.redirect} }></Redirect> + return <Redirect to={{ pathname: this.state.redirect }}></Redirect> } const isSUListVisible = this.state.isSUListVisible; const isSUDetsVisible = this.state.isSUDetsVisible; @@ -780,20 +816,20 @@ export class WeekTimelineView extends Component { const canShrinkSUList = this.state.canShrinkSUList; let suBlueprint = null, reservation = null; if (isSUDetsVisible) { - suBlueprint = _.find(this.state.suBlueprints, {id: parseInt(this.state.selectedItem.id.split('-')[0])}); + suBlueprint = _.find(this.state.suBlueprints, { id: parseInt(this.state.selectedItem.id.split('-')[0]) }); } if (isReservDetsVisible) { - reservation = _.find(this.reservations, {id: parseInt(this.state.selectedItem.id.split('-')[1])}); + reservation = _.find(this.reservations, { id: parseInt(this.state.selectedItem.id.split('-')[1]) }); reservation.project = this.state.selectedItem.project; } const mouseOverItem = this.state.mouseOverItem; return ( <React.Fragment> - <TieredMenu className="app-header-menu" model={this.menuOptions} popup ref={el => this.optionsMenu = el} /> - <PageHeader location={this.props.location} title={'Scheduling Units - Week View'} + <TieredMenu className="app-header-menu" model={this.menuOptions} popup ref={el => this.optionsMenu = el} /> + <PageHeader location={this.props.location} title={'Scheduling Units - Week View'} actions={[ - {icon:'fa-bars',title: '', type:'button', actOn:'mouseOver', props : { callback: this.showOptionMenu},}, - {icon: 'fa-clock',title:'View Timeline', props : { pathname: `/su/timelineview`}}]}/> + { icon: 'fa-bars', title: '', type: 'button', actOn: 'mouseOver', props: { callback: this.showOptionMenu }, }, + { icon: 'fa-clock', title: 'View Timeline', props: { pathname: `/su/timelineview` } }]} /> { this.state.isLoading ? <AppLoader /> : <> {/* <div className="p-field p-grid"> @@ -806,10 +842,10 @@ export class WeekTimelineView extends Component { </div> */} <div className="p-grid"> {/* SU List Panel */} - <div className={isSUListVisible && (isSUDetsVisible || isReservDetsVisible || - (canExtendSUList && !canShrinkSUList)?"col-lg-4 col-md-4 col-sm-12": - ((canExtendSUList && canShrinkSUList)?"col-lg-5 col-md-5 col-sm-12":"col-lg-6 col-md-6 col-sm-12"))} - style={isSUListVisible?{position: "inherit", borderRight: "5px solid #efefef", paddingTop: "10px"}:{display: "none"}}> + <div className={isSUListVisible && (isSUDetsVisible || isReservDetsVisible || + (canExtendSUList && !canShrinkSUList) ? "col-lg-4 col-md-4 col-sm-12" : + ((canExtendSUList && canShrinkSUList) ? "col-lg-5 col-md-5 col-sm-12" : "col-lg-6 col-md-6 col-sm-12"))} + style={isSUListVisible ? { position: "inherit", borderRight: "5px solid #efefef", paddingTop: "10px" } : { display: "none" }}> <ViewTable viewInNewWindow data={this.state.suBlueprintList} defaultcolumns={[{name: "Name", @@ -823,110 +859,112 @@ export class WeekTimelineView extends Component { optionalcolumns={[{project:"Project",description: "Description", duration:"Duration (HH:mm:ss)",actionpath: "actionpath"}]} columnclassname={[{"Name":"filter-input-100", "Start Time":"filter-input-50", "End Time":"filter-input-50", "Duration (HH:mm:ss)" : "filter-input-50",}]} - defaultSortColumn= {[{id: "Start Time", desc: false}]} + defaultSortColumn= {this.defaultSortColumn} showaction="true" tablename="timeline_scheduleunit_list" showTopTotal={false} showGlobalFilter={false} showColumnFilter={false} filterCallback={this.suListFilterCallback} + lsKeySortColumn={this.lsKeySortColumn} + toggleBySorting={(sortData) => this.toggleBySorting(sortData)} /> </div> {/* Timeline Panel */} - <div className={isSUListVisible?((isSUDetsVisible || isReservDetsVisible)?"col-lg-5 col-md-5 col-sm-12": - (!canExtendSUList && canShrinkSUList)?"col-lg-6 col-md-6 col-sm-12": - ((canExtendSUList && canShrinkSUList)?"col-lg-7 col-md-7 col-sm-12":"col-lg-8 col-md-8 col-sm-12")): - ((isSUDetsVisible || isReservDetsVisible)?"col-lg-9 col-md-9 col-sm-12":"col-lg-12 col-md-12 col-sm-12")} - // style={{borderLeft: "3px solid #efefef"}} - > + <div className={isSUListVisible ? ((isSUDetsVisible || isReservDetsVisible) ? "col-lg-5 col-md-5 col-sm-12" : + (!canExtendSUList && canShrinkSUList) ? "col-lg-6 col-md-6 col-sm-12" : + ((canExtendSUList && canShrinkSUList) ? "col-lg-7 col-md-7 col-sm-12" : "col-lg-8 col-md-8 col-sm-12")) : + ((isSUDetsVisible || isReservDetsVisible) ? "col-lg-9 col-md-9 col-sm-12" : "col-lg-12 col-md-12 col-sm-12")} + // style={{borderLeft: "3px solid #efefef"}} + > {/* Panel Resize buttons */} {isSUListVisible && - <div className="resize-div"> - <button className="p-link resize-btn" disabled={!this.state.canShrinkSUList} + <div className="resize-div"> + <button className="p-link resize-btn" disabled={!this.state.canShrinkSUList} title="Shrink List/Expand Timeline" - onClick={(e)=> { this.resizeSUList(-1)}}> - <i className="pi pi-step-backward"></i> - </button> - <button className="p-link resize-btn" disabled={!this.state.canExtendSUList} + onClick={(e) => { this.resizeSUList(-1) }}> + <i className="pi pi-step-backward"></i> + </button> + <button className="p-link resize-btn" disabled={!this.state.canExtendSUList} title="Expandd List/Shrink Timeline" - onClick={(e)=> { this.resizeSUList(1)}}> - <i className="pi pi-step-forward"></i> - </button> - </div> + onClick={(e) => { this.resizeSUList(1) }}> + <i className="pi pi-step-forward"></i> + </button> + </div> } - <div className={isSUListVisible?"resize-div su-visible":"resize-div su-hidden"}> + <div className={isSUListVisible ? "resize-div su-visible" : "resize-div su-hidden"}> {isSUListVisible && - <button className="p-link resize-btn" + <button className="p-link resize-btn" title="Hide List" - onClick={(e)=> { this.setState({isSUListVisible: false})}}> - <i className="pi pi-eye-slash"></i> - </button> + onClick={(e) => { this.setState({ isSUListVisible: false }) }}> + <i className="pi pi-eye-slash"></i> + </button> } {!isSUListVisible && - <button className="p-link resize-btn" + <button className="p-link resize-btn" title="Show List" - onClick={(e)=> { this.setState({isSUListVisible: true})}}> - <i className="pi pi-eye"> Show List</i> - </button> + onClick={(e) => { this.setState({ isSUListVisible: true }) }}> + <i className="pi pi-eye"> Show List</i> + </button> } </div> <div className={`timeline-view-toolbar ${this.state.reservationEnabled && 'alignTimeLineHeader'}`}> - <div className="sub-header"> + <div className="sub-header"> <label >Show Reservations</label> - <InputSwitch checked={this.state.reservationEnabled} onChange={(e) => {this.showReservations(e)}} /> - + <InputSwitch checked={this.state.reservationEnabled} onChange={(e) => { this.showReservations(e) }} /> + </div> - + {this.state.reservationEnabled && - <div className="sub-header"> - <label style={{marginLeft: '20px'}}>Reservation</label> - <Dropdown optionLabel="name" optionValue="name" - style={{top:'2px'}} - value={this.state.reservationFilter} - options={this.reservationReasons} - filter showClear={true} filterBy="name" - onChange={(e) => {this.setReservationFilter(e.value)}} - placeholder="Reason"/> - - </div> + <div className="sub-header"> + <label style={{ marginLeft: '20px' }}>Reservation</label> + <Dropdown optionLabel="name" optionValue="name" + style={{ top: '2px' }} + value={this.state.reservationFilter} + options={this.reservationReasons} + filter showClear={true} filterBy="name" + onChange={(e) => { this.setReservationFilter(e.value) }} + placeholder="Reason" /> + + </div> } </div> - <Timeline ref={(tl)=>{this.timeline=tl}} - group={this.state.group} - items={this.state.items} - currentUTC={this.state.currentUTC} - rowHeight={50} - itemClickCallback={this.onItemClick} - itemMouseOverCallback={this.onItemMouseOver} - itemMouseOutCallback={this.onItemMouseOut} - sidebarWidth={150} - stackItems={true} - startTime={moment.utc(this.state.currentUTC).hour(0).minutes(0).seconds(0)} - endTime={moment.utc(this.state.currentUTC).hour(23).minutes(59).seconds(59)} - zoomLevel="1 Day" - showLive={false} showDateRange={false} viewType={UIConstants.timeline.types.WEEKVIEW} - dateRangeCallback={this.dateRangeCallback} - ></Timeline> + <Timeline ref={(tl) => { this.timeline = tl }} + group={this.state.group} + items={this.state.items} + currentUTC={this.state.currentUTC} + rowHeight={50} + itemClickCallback={this.onItemClick} + itemMouseOverCallback={this.onItemMouseOver} + itemMouseOutCallback={this.onItemMouseOut} + sidebarWidth={150} + stackItems={true} + startTime={moment.utc(this.state.currentUTC).hour(0).minutes(0).seconds(0)} + endTime={moment.utc(this.state.currentUTC).hour(23).minutes(59).seconds(59)} + zoomLevel="1 Day" + showLive={false} showDateRange={false} viewType={UIConstants.timeline.types.WEEKVIEW} + dateRangeCallback={this.dateRangeCallback} + ></Timeline> </div> {/* Details Panel */} {this.state.isSUDetsVisible && - <div className="col-lg-3 col-md-3 col-sm-12" - style={{borderLeft: "1px solid #efefef", marginTop: "0px", backgroundColor: "#f2f2f2"}}> - {this.state.isSummaryLoading?<AppLoader /> : + <div className="col-lg-3 col-md-3 col-sm-12" + style={{ borderLeft: "1px solid #efefef", marginTop: "0px", backgroundColor: "#f2f2f2" }}> + {this.state.isSummaryLoading ? <AppLoader /> : <SchedulingUnitSummary schedulingUnit={suBlueprint} suTaskList={this.state.suTaskList} - viewInNewWindow - constraintsTemplate={this.state.suConstraintTemplate} - closeCallback={this.closeSUDets} - stationGroup={this.state.stationGroup} - location={this.props.location}></SchedulingUnitSummary> + viewInNewWindow + constraintsTemplate={this.state.suConstraintTemplate} + closeCallback={this.closeSUDets} + stationGroup={this.state.stationGroup} + location={this.props.location}></SchedulingUnitSummary> } </div> - } + } {this.state.isReservDetsVisible && - <div className="col-lg-3 col-md-3 col-sm-12" - style={{borderLeft: "1px solid #efefef", marginTop: "0px", backgroundColor: "#f2f2f2"}}> - {this.state.isSummaryLoading?<AppLoader /> : + <div className="col-lg-3 col-md-3 col-sm-12" + style={{ borderLeft: "1px solid #efefef", marginTop: "0px", backgroundColor: "#f2f2f2" }}> + {this.state.isSummaryLoading ? <AppLoader /> : <ReservationSummary reservation={reservation} location={this.props.location} closeCallback={this.closeSUDets}></ReservationSummary> } </div> @@ -936,65 +974,65 @@ export class WeekTimelineView extends Component { } {/* SU Item Tooltip popover with SU status color */} <OverlayPanel className="timeline-popover" ref={(el) => this.popOver = el} dismissable> - {mouseOverItem && mouseOverItem.type === "SCHEDULE" && - <div className={`p-grid su-${mouseOverItem.status}`} style={{width: '350px'}}> - <label className={`col-5 su-${mouseOverItem.status}-icon`}>Project:</label> - <div className="col-7">{mouseOverItem.project}</div> - <label className={`col-5 su-${mouseOverItem.status}-icon`}>Scheduling Unit:</label> - <div className="col-7">{mouseOverItem.name}</div> - <label className={`col-5 su-${mouseOverItem.status}-icon`}>Scheduler:</label> - <div className="col-7">{mouseOverItem.scheduleMethod}</div> - <label className={`col-5 su-${mouseOverItem.status}-icon`}>Friends:</label> - <div className="col-7">{mouseOverItem.friends?mouseOverItem.friends:"-"}</div> - <label className={`col-5 su-${mouseOverItem.status}-icon`}>Start Time:</label> - <div className="col-7">{mouseOverItem.suStartTime.format(UIConstants.CALENDAR_DATETIME_FORMAT)}</div> - <label className={`col-5 su-${mouseOverItem.status}-icon`}>End Time:</label> - <div className="col-7">{mouseOverItem.suStopTime.format(UIConstants.CALENDAR_DATETIME_FORMAT)}</div> - <label className={`col-5 su-${mouseOverItem.status}-icon`}>Antenna Set:</label> - <div className="col-7">{mouseOverItem.antennaSet}</div> - <label className={`col-5 su-${mouseOverItem.status}-icon`}>Stations:</label> - <div className="col-7">{mouseOverItem.stations.groups}:{mouseOverItem.stations.counts}</div> - <label className={`col-5 su-${mouseOverItem.status}-icon`}>Status:</label> - <div className="col-7">{mouseOverItem.status}</div> - <label className={`col-5 su-${mouseOverItem.status}-icon`}>Duration:</label> - <div className="col-7">{mouseOverItem.duration}</div> - </div> - } - {(mouseOverItem && mouseOverItem.type === "RESERVATION") && - <div className={`p-grid`} style={{width: '350px', backgroundColor: mouseOverItem.bgColor, color: mouseOverItem.color}}> - <h3 className={`col-12`}>Reservation Overview</h3> - <hr></hr> - <label className={`col-5`} style={{color: mouseOverItem.color}}>Name:</label> - <div className="col-7">{mouseOverItem.name}</div> - <label className={`col-5`} style={{color: mouseOverItem.color}}>Description:</label> - <div className="col-7">{mouseOverItem.desc}</div> - <label className={`col-5`} style={{color: mouseOverItem.color}}>Type:</label> - <div className="col-7">{mouseOverItem.activity_type}</div> - <label className={`col-5`} style={{color: mouseOverItem.color}}>Stations:</label> - {/* <div className="col-7"><ListBox options={mouseOverItem.stations} /></div> */} - <div className="col-7 station-list"> - {mouseOverItem.stations.map((station, index) => ( - <div key={`stn-${index}`}>{station}</div> - ))} + {mouseOverItem && mouseOverItem.type === "SCHEDULE" && + <div className={`p-grid su-${mouseOverItem.status}`} style={{ width: '350px' }}> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Project:</label> + <div className="col-7">{mouseOverItem.project}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Scheduling Unit:</label> + <div className="col-7">{mouseOverItem.name}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Scheduler:</label> + <div className="col-7">{mouseOverItem.scheduleMethod}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Friends:</label> + <div className="col-7">{mouseOverItem.friends ? mouseOverItem.friends : "-"}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Start Time:</label> + <div className="col-7">{mouseOverItem.suStartTime.format(UIConstants.CALENDAR_DATETIME_FORMAT)}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>End Time:</label> + <div className="col-7">{mouseOverItem.suStopTime.format(UIConstants.CALENDAR_DATETIME_FORMAT)}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Antenna Set:</label> + <div className="col-7">{mouseOverItem.antennaSet}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Stations:</label> + <div className="col-7">{mouseOverItem.stations.groups}:{mouseOverItem.stations.counts}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Status:</label> + <div className="col-7">{mouseOverItem.status}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Duration:</label> + <div className="col-7">{mouseOverItem.duration}</div> </div> - <label className={`col-5`} style={{color: mouseOverItem.color}}>Project:</label> - <div className="col-7">{mouseOverItem.project?mouseOverItem.project:"-"}</div> - <label className={`col-5`} style={{color: mouseOverItem.color}}>Start Time:</label> - <div className="col-7">{mouseOverItem.displayStartTime.format(UIConstants.CALENDAR_DATETIME_FORMAT)}</div> - <label className={`col-5`} style={{color: mouseOverItem.color}}>End Time:</label> - <div className="col-7">{mouseOverItem.displayEndTime?mouseOverItem.displayEndTime.format(UIConstants.CALENDAR_DATETIME_FORMAT):'Unknown'}</div> - {/* <label className={`col-5`} style={{color: mouseOverItem.color}}>Stations:</label> + } + {(mouseOverItem && mouseOverItem.type === "RESERVATION") && + <div className={`p-grid`} style={{ width: '350px', backgroundColor: mouseOverItem.bgColor, color: mouseOverItem.color }}> + <h3 className={`col-12`}>Reservation Overview</h3> + <hr></hr> + <label className={`col-5`} style={{ color: mouseOverItem.color }}>Name:</label> + <div className="col-7">{mouseOverItem.name}</div> + <label className={`col-5`} style={{ color: mouseOverItem.color }}>Description:</label> + <div className="col-7">{mouseOverItem.desc}</div> + <label className={`col-5`} style={{ color: mouseOverItem.color }}>Type:</label> + <div className="col-7">{mouseOverItem.activity_type}</div> + <label className={`col-5`} style={{ color: mouseOverItem.color }}>Stations:</label> + {/* <div className="col-7"><ListBox options={mouseOverItem.stations} /></div> */} + <div className="col-7 station-list"> + {mouseOverItem.stations.map((station, index) => ( + <div key={`stn-${index}`}>{station}</div> + ))} + </div> + <label className={`col-5`} style={{ color: mouseOverItem.color }}>Project:</label> + <div className="col-7">{mouseOverItem.project ? mouseOverItem.project : "-"}</div> + <label className={`col-5`} style={{ color: mouseOverItem.color }}>Start Time:</label> + <div className="col-7">{mouseOverItem.displayStartTime.format(UIConstants.CALENDAR_DATETIME_FORMAT)}</div> + <label className={`col-5`} style={{ color: mouseOverItem.color }}>End Time:</label> + <div className="col-7">{mouseOverItem.displayEndTime ? mouseOverItem.displayEndTime.format(UIConstants.CALENDAR_DATETIME_FORMAT) : 'Unknown'}</div> + {/* <label className={`col-5`} style={{color: mouseOverItem.color}}>Stations:</label> <div className="col-7">{mouseOverItem.stations.groups}:{mouseOverItem.stations.counts}</div> */} - <label className={`col-5`} style={{color: mouseOverItem.color}}>Duration:</label> - <div className="col-7">{mouseOverItem.duration}</div> - <label className={`col-5`} style={{color: mouseOverItem.color}}>Planned:</label> - <div className="col-7">{mouseOverItem.planned?'Yes':'No'}</div> - </div> - } + <label className={`col-5`} style={{ color: mouseOverItem.color }}>Duration:</label> + <div className="col-7">{mouseOverItem.duration}</div> + <label className={`col-5`} style={{ color: mouseOverItem.color }}>Planned:</label> + <div className="col-7">{mouseOverItem.planned ? 'Yes' : 'No'}</div> + </div> + } </OverlayPanel> {/* Open Websocket after loading all initial data */} {!this.state.isLoading && - <Websocket url={process.env.REACT_APP_WEBSOCKET_URL} onOpen={this.onConnect} onMessage={this.handleData} onClose={this.onDisconnect} /> } + <Websocket url={process.env.REACT_APP_WEBSOCKET_URL} onOpen={this.onConnect} onMessage={this.handleData} onClose={this.onDisconnect} />} </React.Fragment> ); } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Workflow/workflow.list.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Workflow/workflow.list.js index 77348c8570fd59492b1d20fdd91488694e1bca3a..d7d2e71e0d7fca34917c8492e3e9d956dca5d3e5 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Workflow/workflow.list.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Workflow/workflow.list.js @@ -11,10 +11,14 @@ import ViewTable from '../../components/ViewTable'; import WorkflowService from '../../services/workflow.service'; import ScheduleService from '../../services/schedule.service'; import UIConstants from '../../utils/ui.constants'; +import UtilService from '../../services/util.service'; class WorkflowList extends Component{ + lsKeySortColumn = 'SortDataWorkflowList'; + defaultSortColumn = []; constructor(props) { super(props); + this.setToggleBySorting(); this.state={ ftAssignstatus: '', activeWorkflow: null, @@ -65,6 +69,7 @@ class WorkflowList extends Component{ } componentDidMount() { + this.setToggleBySorting(); const promises = [ WorkflowService.getWorkflowProcesses(), WorkflowService.getWorkflowTasks(), ScheduleService.getSchedulingUnitBlueprint(),]; @@ -75,7 +80,23 @@ class WorkflowList extends Component{ this.prepareWorkflowProcesslist(); }); } + toggleBySorting = (sortData) => { + UtilService.localStore({ type: 'set', key: this.lsKeySortColumn, value: sortData }); + } + setToggleBySorting() { + let sortData = UtilService.localStore({ type: 'get', key: this.lsKeySortColumn }); + if (sortData) { + if (Object.prototype.toString.call(sortData) === '[object Array]') { + this.defaultSortColumn = sortData; + } + else { + this.defaultSortColumn = [{ ...sortData }]; + } + } + this.defaultSortColumn = this.defaultSortColumn || []; + UtilService.localStore({ type: 'set', key: this.lsKeySortColumn, value: [...this.defaultSortColumn] }); + } /** * Prepare Workflow Process data */ @@ -249,6 +270,9 @@ class WorkflowList extends Component{ showTopTotal={true} showGlobalFilter={true} showColumnFilter={true} + lsKeySortColumn={this.lsKeySortColumn} + toggleBySorting={(sortData) => this.toggleBySorting(sortData)} + defaultSortColumn= {this.defaultSortColumn} /> </> :<div>No Workflow Process SU found</div> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/services/util.service.js b/SAS/TMSS/frontend/tmss_webapp/src/services/util.service.js index 036165fc7138afbbed3f02de55ff5a1564b4f438..f49788d507bb1b5d06c611ede6c2a115df0dab6e 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/services/util.service.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/util.service.js @@ -161,7 +161,17 @@ const UtilService = { schema["items"] = resolvedItems; } return schema; - } + }, + localStore:function(data){ + const {type,key,value}=data; + if(type=='set'){ + localStorage.setItem(key,JSON.stringify(value)); + }else if(type=='get'){ + return JSON.parse(localStorage.getItem(key)); + }else if(type=='remove'){ + localStorage.removeItem(key); + } +} } export default UtilService; \ No newline at end of file