diff --git a/SAS/TMSS/frontend/tmss_webapp/package.json b/SAS/TMSS/frontend/tmss_webapp/package.json index b8a53534056c2171aa2bddc4e885529473f5ef16..11b49d269318768f1595a2d1e95c4354b6b66544 100644 --- a/SAS/TMSS/frontend/tmss_webapp/package.json +++ b/SAS/TMSS/frontend/tmss_webapp/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@apidevtools/json-schema-ref-parser": "^9.0.6", "@fortawesome/fontawesome-free": "^5.13.1", "@json-editor/json-editor": "^2.3.0", "@testing-library/jest-dom": "^4.2.4", diff --git a/SAS/TMSS/frontend/tmss_webapp/src/App.css b/SAS/TMSS/frontend/tmss_webapp/src/App.css index 32f05ad2e240c048576131b367528ede7d947971..d19517b7d6d383cb7d804784960ec03a8c68dc2f 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/App.css +++ b/SAS/TMSS/frontend/tmss_webapp/src/App.css @@ -94,7 +94,7 @@ p { margin-bottom: 5px; } -.p-grid span { +.main-content .p-grid span { margin-bottom: 10px; } @@ -102,6 +102,10 @@ p { margin-bottom: 0px; } +.p-chips-token .p-chips-token-label { + margin-bottom: 0px !important; +} + .p-field { margin-bottom: 0.5rem; } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/__mocks__/project.service.data.js b/SAS/TMSS/frontend/tmss_webapp/src/__mocks__/project.service.data.js index 42680f340c62c61e5ef8432cc223493880842a2b..ab49f92eec7585581a21305ed884ead2f8c929a9 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/__mocks__/project.service.data.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/__mocks__/project.service.data.js @@ -219,7 +219,115 @@ const ProjectServiceMock= { "resource_type_id": "Support hours", "value": 32400 } - ] + ], + projectList: [ + { + "name": "OSR-01", + "url": "http://192.168.99.100:8008/api/project/OSR-01", + "can_trigger": false, + "created_at": "2020-08-25T14:29:04.881620", + "cycles": [ + "http://192.168.99.100:8008/api/cycle/Cycle%2014" + ], + "cycles_ids": [ + "Cycle 14" + ], + "description": "OSR-01", + "expert": false, + "filler": false, + "period_category": "http://192.168.99.100:8008/api/period_category/single_cycle", + "period_category_value": "single_cycle", + "priority_rank": 1, + "private_data": true, + "project_category": "http://192.168.99.100:8008/api/project_category/regular", + "project_category_value": "regular", + "quota": [ + "http://192.168.99.100:8008/api/project_quota/1", + "http://192.168.99.100:8008/api/project_quota/2", + "http://192.168.99.100:8008/api/project_quota/3", + "http://192.168.99.100:8008/api/project_quota/4", + "http://192.168.99.100:8008/api/project_quota/5", + "http://192.168.99.100:8008/api/project_quota/6", + "http://192.168.99.100:8008/api/project_quota/7" + ], + "quota_ids": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7 + ], + "tags": [], + "trigger_priority": 1000, + "updated_at": "2020-08-25T14:29:04.881640" + }, + { + "name": "OSR-02", + "url": "http://192.168.99.100:8008/api/project/OSR-02", + "can_trigger": false, + "created_at": "2020-08-28T07:52:07.411136", + "cycles": [], + "cycles_ids": [], + "description": "OSR-02", + "expert": false, + "filler": false, + "period_category": null, + "period_category_value": null, + "priority_rank": 1, + "private_data": true, + "project_category": null, + "project_category_value": null, + "quota": [ + "http://192.168.99.100:8008/api/project_quota/8", + "http://192.168.99.100:8008/api/project_quota/9", + "http://192.168.99.100:8008/api/project_quota/10", + "http://192.168.99.100:8008/api/project_quota/11", + "http://192.168.99.100:8008/api/project_quota/12", + "http://192.168.99.100:8008/api/project_quota/13", + "http://192.168.99.100:8008/api/project_quota/14" + ], + "quota_ids": [ + 8, + 9, + 10, + 11, + 12, + 13, + 14 + ], + "tags": [], + "trigger_priority": 1000, + "updated_at": "2020-08-28T07:52:07.411167" + }, + { + "name": "TMSS-Commissioning", + "url": "http://192.168.99.100:8008/api/project/TMSS-Commissioning", + "can_trigger": false, + "created_at": "2020-08-25T13:28:34.760707", + "cycles": [ + "http://192.168.99.100:8008/api/cycle/Cycle%2014" + ], + "cycles_ids": [ + "Cycle 14" + ], + "description": "Project for all TMSS tests and commissioning", + "expert": true, + "filler": false, + "period_category": null, + "period_category_value": null, + "priority_rank": 1, + "private_data": true, + "project_category": null, + "project_category_value": null, + "quota": [], + "quota_ids": [], + "tags": [], + "trigger_priority": 1000, + "updated_at": "2020-08-25T13:28:34.760729" + } + ] } export default ProjectServiceMock; \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/__mocks__/scheduleunit.service.data.js b/SAS/TMSS/frontend/tmss_webapp/src/__mocks__/scheduleunit.service.data.js new file mode 100644 index 0000000000000000000000000000000000000000..3dc1484cf61fa7e7f68250f0e986cbc794defd8f --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/__mocks__/scheduleunit.service.data.js @@ -0,0 +1,753 @@ +const SUServiceMock= { + scheduleSetList: [ + { + "id": 1, + "url": "http://192.168.99.100:8008/api/scheduling_set/1", + "created_at": "2020-08-25T13:28:42.045214", + "description": "", + "generator_doc": {}, + "generator_source": null, + "generator_source_id": null, + "generator_template": "http://192.168.99.100:8008/api/generator_template/1", + "generator_template_id": 1, + "name": "Test Scheduling Set UC1 example 0", + "project": "http://192.168.99.100:8008/api/project/TMSS-Commissioning", + "project_id": "TMSS-Commissioning", + "scheduling_unit_drafts": [ + "http://192.168.99.100:8008/api/scheduling_unit_draft/2", + "http://192.168.99.100:8008/api/scheduling_unit_draft/1" + ], + "scheduling_unit_drafts_ids": [ + 2, + 1 + ], + "tags": [ + "TEST", + "UC1" + ], + "updated_at": "2020-08-25T13:28:42.047512" + }, + { + "id": 2, + "url": "http://192.168.99.100:8008/api/scheduling_set/2", + "created_at": "2020-08-25T13:28:49.545042", + "description": "", + "generator_doc": {}, + "generator_source": null, + "generator_source_id": null, + "generator_template": "http://192.168.99.100:8008/api/generator_template/2", + "generator_template_id": 2, + "name": "Test Scheduling Set UC1 example 1", + "project": "http://192.168.99.100:8008/api/project/TMSS-Commissioning", + "project_id": "TMSS-Commissioning", + "scheduling_unit_drafts": [ + "http://192.168.99.100:8008/api/scheduling_unit_draft/4", + "http://192.168.99.100:8008/api/scheduling_unit_draft/3" + ], + "scheduling_unit_drafts_ids": [ + 4, + 3 + ], + "tags": [ + "TEST", + "UC1" + ], + "updated_at": "2020-08-25T13:28:49.546151" + }, + { + "id": 3, + "url": "http://192.168.99.100:8008/api/scheduling_set/3", + "created_at": "2020-08-25T13:28:57.025339", + "description": "", + "generator_doc": {}, + "generator_source": null, + "generator_source_id": null, + "generator_template": "http://192.168.99.100:8008/api/generator_template/3", + "generator_template_id": 3, + "name": "Test Scheduling Set UC1 example 2", + "project": "http://192.168.99.100:8008/api/project/TMSS-Commissioning", + "project_id": "TMSS-Commissioning", + "scheduling_unit_drafts": [ + "http://192.168.99.100:8008/api/scheduling_unit_draft/6", + "http://192.168.99.100:8008/api/scheduling_unit_draft/5" + ], + "scheduling_unit_drafts_ids": [ + 6, + 5 + ], + "tags": [ + "TEST", + "UC1" + ], + "updated_at": "2020-08-25T13:28:57.026492" + } + ], + observStrategies: [ + { + "id": 1, + "url": "http://192.168.99.100:8008/api/scheduling_unit_observing_strategy_template/1", + "created_at": "2020-08-25T13:28:33.974187", + "description": "UC1 observation strategy template", + "name": "UC1 observation strategy template", + "scheduling_unit_template": "http://192.168.99.100:8008/api/scheduling_unit_template/1", + "scheduling_unit_template_id": 1, + "tags": [ + "UC1" + ], + "template": { + "tasks": { + "Pipeline 1": { + "tags": [], + "description": "Preprocessing Pipeline for Calibrator Observation 1", + "specifications_doc": { + "flag": { + "rfi_strategy": "auto", + "outerchannels": true, + "autocorrelations": true + }, + "demix": { + "sources": {}, + "time_steps": 10, + "ignore_target": false, + "frequency_steps": 64 + }, + "average": { + "time_steps": 1, + "frequency_steps": 4 + }, + "storagemanager": "dysco" + }, + "specifications_template": "preprocessing schema" + }, + "Pipeline 2": { + "tags": [], + "description": "Preprocessing Pipeline for Calibrator Observation 2", + "specifications_doc": { + "flag": { + "rfi_strategy": "auto", + "outerchannels": true, + "autocorrelations": true + }, + "demix": { + "sources": {}, + "time_steps": 10, + "ignore_target": false, + "frequency_steps": 64 + }, + "average": { + "time_steps": 1, + "frequency_steps": 4 + }, + "storagemanager": "dysco" + }, + "specifications_template": "preprocessing schema" + }, + "Pipeline SAP0": { + "tags": [], + "description": "Preprocessing Pipeline for Target Observation SAP0", + "specifications_doc": { + "flag": { + "rfi_strategy": "auto", + "outerchannels": true, + "autocorrelations": true + }, + "demix": { + "sources": {}, + "time_steps": 10, + "ignore_target": false, + "frequency_steps": 64 + }, + "average": { + "time_steps": 1, + "frequency_steps": 4 + }, + "storagemanager": "dysco" + }, + "specifications_template": "preprocessing schema" + }, + "Pipeline SAP1": { + "tags": [], + "description": "Preprocessing Pipeline for Target Observation SAP1", + "specifications_doc": { + "flag": { + "rfi_strategy": "auto", + "outerchannels": true, + "autocorrelations": true + }, + "demix": { + "sources": {}, + "time_steps": 10, + "ignore_target": false, + "frequency_steps": 64 + }, + "average": { + "time_steps": 1, + "frequency_steps": 4 + }, + "storagemanager": "dysco" + }, + "specifications_template": "preprocessing schema" + }, + "Target Observation": { + "tags": [], + "description": "Target Observation for UC1 HBA scheduling unit", + "specifications_doc": { + "QA": { + "plots": { + "enabled": true, + "autocorrelation": true, + "crosscorrelation": true + }, + "file_conversion": { + "enabled": true, + "nr_of_subbands": -1, + "nr_of_timestamps": 256 + } + }, + "SAPs": [ + { + "name": "target0", + "subbands": [ + 349, + 372 + ], + "digital_pointing": { + "angle1": 3.9935314947195253, + "angle2": 0.5324708659626034, + "angle3": 24, + "direction_type": "J2000" + } + }, + { + "name": "target1", + "subbands": [ + 349, + 372 + ], + "digital_pointing": { + "angle1": 3.9935314947195253, + "angle2": 0.5324708659626034, + "angle3": 24, + "direction_type": "J2000" + } + } + ], + "filter": "HBA_110_190", + "duration": 28800, + "stations": [ + { + "group": "ALL", + "min_stations": 1 + } + ], + "tile_beam": { + "angle1": 5.324708659626033, + "angle2": 0.7099611546168045, + "angle3": 42, + "direction_type": "J2000" + }, + "correlator": { + "storage_cluster": "CEP4", + "integration_time": 1, + "channels_per_subband": 64 + }, + "antenna_set": "HBA_DUAL_INNER" + }, + "specifications_template": "observation schema" + }, + "Calibrator Observation 1": { + "tags": [], + "description": "Calibrator Observation for UC1 HBA scheduling unit", + "specifications_doc": { + "duration": 600, + "pointing": { + "angle1": 0, + "angle2": 0, + "angle3": 0, + "direction_type": "J2000" + }, + "autoselect": false + }, + "specifications_template": "calibrator schema" + }, + "Calibrator Observation 2": { + "tags": [], + "description": "Calibrator Observation for UC1 HBA scheduling unit", + "specifications_doc": { + "duration": 600, + "pointing": { + "angle1": 0, + "angle2": 0, + "angle3": 0, + "direction_type": "J2000" + }, + "autoselect": false + }, + "specifications_template": "calibrator schema" + } + }, + "parameters": [ + { + "name": "Target Pointing 0", + "refs": [ + "#/tasks/Target Observation/specifications_doc/SAPs/0/digital_pointing" + ] + }, + { + "name": "Target Pointing 1", + "refs": [ + "#/tasks/Target Observation/specifications_doc/SAPs/1/digital_pointing" + ] + }, + { + "name": "Tile Beam", + "refs": [ + "#/tasks/Target Observation/specifications_doc/tile_beam" + ] + } + ], + "task_relations": [ + { + "tags": [], + "input": { + "role": "input", + "datatype": "visibilities" + }, + "output": { + "role": "correlator", + "datatype": "visibilities" + }, + "consumer": "Pipeline 1", + "producer": "Calibrator Observation 1", + "dataformat": "MeasurementSet", + "selection_doc": {}, + "selection_template": "All" + }, + { + "tags": [], + "input": { + "role": "input", + "datatype": "visibilities" + }, + "output": { + "role": "correlator", + "datatype": "visibilities" + }, + "consumer": "Pipeline 2", + "producer": "Calibrator Observation 2", + "dataformat": "MeasurementSet", + "selection_doc": {}, + "selection_template": "All" + }, + { + "tags": [], + "input": { + "role": "input", + "datatype": "visibilities" + }, + "output": { + "role": "correlator", + "datatype": "visibilities" + }, + "consumer": "Pipeline SAP0", + "producer": "Target Observation", + "dataformat": "MeasurementSet", + "selection_doc": { + "sap": [ + 0 + ] + }, + "selection_template": "SAP" + }, + { + "tags": [], + "input": { + "role": "input", + "datatype": "visibilities" + }, + "output": { + "role": "correlator", + "datatype": "visibilities" + }, + "consumer": "Pipeline SAP1", + "producer": "Target Observation", + "dataformat": "MeasurementSet", + "selection_doc": { + "sap": [ + 1 + ] + }, + "selection_template": "SAP" + } + ], + "task_scheduling_relations": [ + { + "first": "Calibrator Observation 1", + "second": "Target Observation", + "placement": "before", + "time_offset": 60 + }, + { + "first": "Calibrator Observation 2", + "second": "Target Observation", + "placement": "after", + "time_offset": 60 + } + ] + }, + "updated_at": "2020-08-25T13:28:33.974209", + "version": "0.1" + } + ], + schedulingUnitFromObservStrategy: { + "id": 1, + "url": "http://192.168.99.100:8008/api/scheduling_unit_draft/1", + "copies": null, + "copies_id": null, + "copy_reason": null, + "copy_reason_value": null, + "created_at": "2020-08-25T13:28:42.092602", + "description": "", + "duration": 30120, + "generator_instance_doc": null, + "name": "UC1 test scheduling unit 1.1", + "observation_strategy_template": "http://192.168.99.100:8008/api/scheduling_unit_observing_strategy_template/1", + "observation_strategy_template_id": 1, + "requirements_doc": { + "tasks": { + "Pipeline 1": { + "tags": [], + "description": "Preprocessing Pipeline for Calibrator Observation 1", + "specifications_doc": { + "flag": { + "rfi_strategy": "auto", + "outerchannels": true, + "autocorrelations": true + }, + "demix": { + "sources": {}, + "time_steps": 10, + "ignore_target": false, + "frequency_steps": 64 + }, + "average": { + "time_steps": 1, + "frequency_steps": 4 + }, + "storagemanager": "dysco" + }, + "specifications_template": "preprocessing schema" + }, + "Pipeline 2": { + "tags": [], + "description": "Preprocessing Pipeline for Calibrator Observation 2", + "specifications_doc": { + "flag": { + "rfi_strategy": "auto", + "outerchannels": true, + "autocorrelations": true + }, + "demix": { + "sources": {}, + "time_steps": 10, + "ignore_target": false, + "frequency_steps": 64 + }, + "average": { + "time_steps": 1, + "frequency_steps": 4 + }, + "storagemanager": "dysco" + }, + "specifications_template": "preprocessing schema" + }, + "Pipeline SAP0": { + "tags": [], + "description": "Preprocessing Pipeline for Target Observation SAP0", + "specifications_doc": { + "flag": { + "rfi_strategy": "auto", + "outerchannels": true, + "autocorrelations": true + }, + "demix": { + "sources": {}, + "time_steps": 10, + "ignore_target": false, + "frequency_steps": 64 + }, + "average": { + "time_steps": 1, + "frequency_steps": 4 + }, + "storagemanager": "dysco" + }, + "specifications_template": "preprocessing schema" + }, + "Pipeline SAP1": { + "tags": [], + "description": "Preprocessing Pipeline for Target Observation SAP1", + "specifications_doc": { + "flag": { + "rfi_strategy": "auto", + "outerchannels": true, + "autocorrelations": true + }, + "demix": { + "sources": {}, + "time_steps": 10, + "ignore_target": false, + "frequency_steps": 64 + }, + "average": { + "time_steps": 1, + "frequency_steps": 4 + }, + "storagemanager": "dysco" + }, + "specifications_template": "preprocessing schema" + }, + "Target Observation": { + "tags": [], + "description": "Target Observation for UC1 HBA scheduling unit", + "specifications_doc": { + "QA": { + "plots": { + "enabled": true, + "autocorrelation": true, + "crosscorrelation": true + }, + "file_conversion": { + "enabled": true, + "nr_of_subbands": -1, + "nr_of_timestamps": 256 + } + }, + "SAPs": [ + { + "name": "target0", + "subbands": [ + 349, + 372 + ], + "digital_pointing": { + "angle1": 3.9935314947195253, + "angle2": 0.5324708659626034, + "angle3": 24, + "direction_type": "J2000" + } + }, + { + "name": "target1", + "subbands": [ + 349, + 372 + ], + "digital_pointing": { + "angle1": 3.9935314947195253, + "angle2": 0.5324708659626034, + "angle3": 24, + "direction_type": "J2000" + } + } + ], + "filter": "HBA_110_190", + "duration": 28800, + "stations": [ + { + "group": "ALL", + "min_stations": 1 + } + ], + "tile_beam": { + "angle1": 5.324708659626033, + "angle2": 0.7099611546168045, + "angle3": 42, + "direction_type": "J2000" + }, + "correlator": { + "storage_cluster": "CEP4", + "integration_time": 1, + "channels_per_subband": 64 + }, + "antenna_set": "HBA_DUAL_INNER" + }, + "specifications_template": "observation schema" + }, + "Calibrator Observation 1": { + "tags": [], + "description": "Calibrator Observation for UC1 HBA scheduling unit", + "specifications_doc": { + "duration": 600, + "pointing": { + "angle1": 0, + "angle2": 0, + "angle3": 0, + "direction_type": "J2000" + }, + "autoselect": false + }, + "specifications_template": "calibrator schema" + }, + "Calibrator Observation 2": { + "tags": [], + "description": "Calibrator Observation for UC1 HBA scheduling unit", + "specifications_doc": { + "duration": 600, + "pointing": { + "angle1": 0, + "angle2": 0, + "angle3": 0, + "direction_type": "J2000" + }, + "autoselect": false + }, + "specifications_template": "calibrator schema" + } + }, + "parameters": [ + { + "name": "Target Pointing 0", + "refs": [ + "#/tasks/Target Observation/specifications_doc/SAPs/0/digital_pointing" + ] + }, + { + "name": "Target Pointing 1", + "refs": [ + "#/tasks/Target Observation/specifications_doc/SAPs/1/digital_pointing" + ] + }, + { + "name": "Tile Beam", + "refs": [ + "#/tasks/Target Observation/specifications_doc/tile_beam" + ] + } + ], + "task_relations": [ + { + "tags": [], + "input": { + "role": "input", + "datatype": "visibilities" + }, + "output": { + "role": "correlator", + "datatype": "visibilities" + }, + "consumer": "Pipeline 1", + "producer": "Calibrator Observation 1", + "dataformat": "MeasurementSet", + "selection_doc": {}, + "selection_template": "All" + }, + { + "tags": [], + "input": { + "role": "input", + "datatype": "visibilities" + }, + "output": { + "role": "correlator", + "datatype": "visibilities" + }, + "consumer": "Pipeline 2", + "producer": "Calibrator Observation 2", + "dataformat": "MeasurementSet", + "selection_doc": {}, + "selection_template": "All" + }, + { + "tags": [], + "input": { + "role": "input", + "datatype": "visibilities" + }, + "output": { + "role": "correlator", + "datatype": "visibilities" + }, + "consumer": "Pipeline SAP0", + "producer": "Target Observation", + "dataformat": "MeasurementSet", + "selection_doc": { + "sap": [ + 0 + ] + }, + "selection_template": "SAP" + }, + { + "tags": [], + "input": { + "role": "input", + "datatype": "visibilities" + }, + "output": { + "role": "correlator", + "datatype": "visibilities" + }, + "consumer": "Pipeline SAP1", + "producer": "Target Observation", + "dataformat": "MeasurementSet", + "selection_doc": { + "sap": [ + 1 + ] + }, + "selection_template": "SAP" + } + ], + "task_scheduling_relations": [ + { + "first": "Calibrator Observation 1", + "second": "Target Observation", + "placement": "before", + "time_offset": 60 + }, + { + "first": "Calibrator Observation 2", + "second": "Target Observation", + "placement": "after", + "time_offset": 60 + } + ] + }, + "requirements_template": "http://192.168.99.100:8008/api/scheduling_unit_template/1", + "requirements_template_id": 1, + "scheduling_set": "http://192.168.99.100:8008/api/scheduling_set/1", + "scheduling_set_id": 1, + "scheduling_unit_blueprints": [ + "http://192.168.99.100:8008/api/scheduling_unit_blueprint/1" + ], + "scheduling_unit_blueprints_ids": [ + 1 + ], + "tags": [ + "TEST", + "UC1" + ], + "task_drafts": [ + "http://192.168.99.100:8008/api/task_draft/5", + "http://192.168.99.100:8008/api/task_draft/7", + "http://192.168.99.100:8008/api/task_draft/6", + "http://192.168.99.100:8008/api/task_draft/4", + "http://192.168.99.100:8008/api/task_draft/3", + "http://192.168.99.100:8008/api/task_draft/2", + "http://192.168.99.100:8008/api/task_draft/1" + ], + "task_drafts_ids": [ + 5, + 7, + 6, + 4, + 3, + 2, + 1 + ], + "updated_at": "2020-08-25T13:28:42.119417" + } +}; + +export default SUServiceMock; \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/__mocks__/task.service.data.js b/SAS/TMSS/frontend/tmss_webapp/src/__mocks__/task.service.data.js new file mode 100644 index 0000000000000000000000000000000000000000..6b7abd1e336416472b6360461fdbd23345871d7e --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/__mocks__/task.service.data.js @@ -0,0 +1,676 @@ +const TaskServiceMock= { + taskTemplates: [ + { + "id": 1, + "url": "http://192.168.99.100:8008/api/task_template/1", + "created_at": "2020-08-25T13:28:33.979487", + "description": "preprocessing settings", + "name": "preprocessing schema", + "schema": { + "$id": "http://example.com/example.json", + "type": "object", + "$schema": "http://json-schema.org/draft-06/schema#", + "required": [ + "storagemanager" + ], + "properties": { + "flag": { + "type": "object", + "title": "Flagging", + "default": {}, + "required": [ + "outerchannels", + "autocorrelations", + "rfi_strategy" + ], + "properties": { + "rfi_strategy": { + "enum": [ + "none", + "auto", + "HBAdefault", + "LBAdefault" + ], + "type": "string", + "title": "RFI flagging strategy", + "default": "auto" + }, + "outerchannels": { + "type": "boolean", + "title": "Flag outer channels", + "default": true + }, + "autocorrelations": { + "type": "boolean", + "title": "Flag auto correlations", + "default": true + } + }, + "additionalProperties": false + }, + "demix": { + "type": "object", + "title": "Demixing", + "default": {}, + "options": { + "dependencies": { + "demix": true + } + }, + "required": [ + "frequency_steps", + "time_steps", + "ignore_target", + "sources" + ], + "properties": { + "sources": { + "type": "object", + "title": "Sources", + "default": {}, + "properties": { + "CasA": { + "$ref": "#/definitions/demix_strategy", + "title": "CasA" + }, + "CygA": { + "$ref": "#/definitions/demix_strategy", + "title": "CygA" + }, + "HerA": { + "$ref": "#/definitions/demix_strategy", + "title": "HerA" + }, + "TauA": { + "$ref": "#/definitions/demix_strategy", + "title": "TauA" + }, + "VirA": { + "$ref": "#/definitions/demix_strategy", + "title": "VirA" + }, + "HydraA": { + "$ref": "#/definitions/demix_strategy", + "title": "HyrdraA" + } + }, + "additionalProperties": false + }, + "time_steps": { + "type": "integer", + "title": "Time steps", + "default": 10, + "minimum": 1, + "description": "Must be a multiple of the averaging time steps" + }, + "ignore_target": { + "type": "boolean", + "title": "Ignore target", + "default": false + }, + "frequency_steps": { + "type": "integer", + "title": "Frequency steps", + "default": 64, + "minimum": 1, + "description": "Must be a multiple of the averaging frequency steps" + } + }, + "additionalProperties": false + }, + "average": { + "type": "object", + "title": "Averaging", + "default": {}, + "required": [ + "frequency_steps", + "time_steps" + ], + "properties": { + "time_steps": { + "type": "integer", + "title": "Time steps", + "default": 1, + "minimum": 1 + }, + "frequency_steps": { + "type": "integer", + "title": "Frequency steps", + "default": 4, + "minimum": 1 + } + }, + "additionalProperties": false + }, + "storagemanager": { + "enum": [ + "basic", + "dysco" + ], + "type": "string", + "title": "Storage Manager", + "default": "dysco" + } + }, + "definitions": { + "demix_strategy": { + "enum": [ + "auto", + "yes", + "no" + ], + "type": "string", + "default": "auto" + } + }, + "additionalProperties": false + }, + "tags": [], + "type": "http://192.168.99.100:8008/api/task_type/pipeline", + "type_value": "pipeline", + "updated_at": "2020-08-25T13:28:33.979514", + "validation_code_js": "", + "version": "0.1" + }, + { + "id": 2, + "url": "http://192.168.99.100:8008/api/task_template/2", + "created_at": "2020-08-25T13:28:33.983945", + "description": "schema for observations", + "name": "observation schema", + "schema": { + "$id": "http://example.com/example.json", + "type": "object", + "$schema": "http://json-schema.org/draft-06/schema#", + "required": [ + "stations", + "antenna_set", + "filter", + "SAPs", + "duration", + "correlator" + ], + "properties": { + "QA": { + "type": "object", + "title": "Quality Assurance", + "default": {}, + "properties": { + "plots": { + "type": "object", + "title": "Plots", + "default": {}, + "properties": { + "enabled": { + "type": "boolean", + "title": "enabled", + "default": true, + "description": "Do/Don't create plots from the QA file from the observation" + }, + "autocorrelation": { + "type": "boolean", + "title": "autocorrelation", + "default": true, + "description": "Create autocorrelation plots for all stations" + }, + "crosscorrelation": { + "type": "boolean", + "title": "crosscorrelation", + "default": true, + "description": "Create crosscorrelation plots for all baselines" + } + }, + "description": "Create dynamic spectrum plots", + "additionalProperties": false + }, + "file_conversion": { + "type": "object", + "title": "File Conversion", + "default": {}, + "properties": { + "enabled": { + "type": "boolean", + "title": "enabled", + "default": true, + "description": "Do/Don't create a QA file for the observation" + }, + "nr_of_subbands": { + "type": "integer", + "title": "#subbands", + "default": -1, + "description": "Keep this number of subbands from the observation in the QA file, or all if -1" + }, + "nr_of_timestamps": { + "type": "integer", + "title": "#timestamps", + "default": 256, + "minimum": 1, + "description": "Extract this number of timestamps from the observation in the QA file (equidistantanly sampled, no averaging/interpolation)" + } + }, + "description": "Create a QA file for the observation", + "additionalProperties": false + } + }, + "description": "Specify Quality Assurance steps for this observation", + "additionalProperties": false + }, + "SAPs": { + "type": "array", + "items": { + "type": "object", + "title": "SAP", + "default": {}, + "required": [ + "digital_pointing", + "subbands" + ], + "properties": { + "name": { + "type": "string", + "title": "Name/target", + "default": "", + "description": "Identifier for this beam" + }, + "subbands": { + "type": "array", + "items": { + "type": "integer", + "title": "Subband", + "maximum": 511, + "minimum": 0 + }, + "title": "Subband list", + "default": [], + "additionalItems": false + }, + "digital_pointing": { + "$ref": "#/definitions/pointing", + "title": "Digital pointing", + "default": {} + } + }, + "headerTemplate": "{{ i0 }} - {{ self.name }}", + "additionalProperties": false + }, + "title": "SAPs", + "default": [ + {} + ], + "description": "Station beams", + "additionalItems": false + }, + "filter": { + "enum": [ + "LBA_10_70", + "LBA_30_70", + "LBA_10_90", + "LBA_30_90", + "HBA_110_190", + "HBA_210_250" + ], + "type": "string", + "title": "Band-pass filter", + "default": "HBA_110_190", + "description": "Must match antenna type" + }, + "duration": { + "type": "number", + "title": "Duration (seconds)", + "default": 300, + "minimum": 1, + "description": "Duration of this observation" + }, + "stations": { + "oneOf": [ + { + "type": "array", + "items": { + "enum": [ + "CS001", + "CS002", + "CS003", + "CS004", + "CS005", + "CS006", + "CS007", + "CS011", + "CS013", + "CS017", + "CS021", + "CS024", + "CS026", + "CS028", + "CS030", + "CS031", + "CS032", + "CS101", + "CS103", + "CS201", + "CS301", + "CS302", + "CS401", + "CS501", + "RS104", + "RS106", + "RS205", + "RS208", + "RS210", + "RS305", + "RS306", + "RS307", + "RS310", + "RS406", + "RS407", + "RS409", + "RS410", + "RS503", + "RS508", + "RS509", + "DE601", + "DE602", + "DE603", + "DE604", + "DE605", + "FR606", + "SE607", + "UK608", + "DE609", + "PL610", + "PL611", + "PL612", + "IE613", + "LV614" + ], + "type": "string", + "title": "Station", + "description": "" + }, + "title": "Fixed list", + "default": [ + "CS001" + ], + "minItems": 1, + "uniqueItems": true, + "additionalItems": false, + "additionalProperties": false + }, + { + "type": "array", + "items": { + "type": "object", + "title": "Station set", + "required": [ + "group", + "min_stations" + ], + "properties": { + "group": { + "enum": [ + "ALL", + "SUPERTERP", + "CORE", + "REMOTE", + "DUTCH", + "INTERNATIONAL" + ], + "type": "string", + "title": "Group/station", + "default": "ALL", + "description": "Which (group of) station(s) to select from" + }, + "min_stations": { + "type": "integer", + "title": "Minimum nr of stations", + "default": 1, + "minimum": 0, + "description": "Number of stations to use within group/station" + } + }, + "headerTemplate": "{{ self.group }}", + "additionalProperties": false + }, + "title": "Dynamic list", + "default": [ + {} + ], + "additionalItems": false + } + ], + "title": "Station list", + "default": [ + "CS001" + ] + }, + "tile_beam": { + "$ref": "#/definitions/pointing", + "title": "Tile beam", + "description": "HBA only" + }, + "correlator": { + "type": "object", + "title": "Correlator Settings", + "default": {}, + "required": [ + "channels_per_subband", + "integration_time", + "storage_cluster" + ], + "properties": { + "storage_cluster": { + "enum": [ + "CEP4", + "DragNet" + ], + "type": "string", + "title": "Storage cluster", + "default": "CEP4", + "description": "Cluster to write output to" + }, + "integration_time": { + "type": "number", + "title": "Integration time (seconds)", + "default": 1, + "minimum": 0.1, + "description": "Desired integration period" + }, + "channels_per_subband": { + "enum": [ + 8, + 16, + 32, + 64, + 128, + 256, + 512, + 1024 + ], + "type": "integer", + "title": "Channels/subband", + "default": 64, + "minimum": 8, + "description": "Number of frequency bands per subband" + } + }, + "additionalProperties": false + }, + "antenna_set": { + "enum": [ + "HBA_DUAL", + "HBA_DUAL_INNER", + "HBA_ONE", + "HBA_ONE_INNER", + "HBA_ZERO", + "HBA_ZERO_INNER", + "LBA_INNER", + "LBA_OUTER", + "LBA_SPARSE_EVEN", + "LBA_SPARSE_ODD", + "LBA_ALL" + ], + "type": "string", + "title": "Antenna set", + "default": "HBA_DUAL", + "description": "Fields & antennas to use" + } + }, + "definitions": { + "pointing": { + "type": "object", + "required": [ + "angle1", + "angle2" + ], + "properties": { + "angle1": { + "type": "number", + "title": "Angle 1", + "default": 0, + "description": "First angle (e.g. RA)" + }, + "angle2": { + "type": "number", + "title": "Angle 2", + "default": 0, + "description": "Second angle (e.g. DEC)" + }, + "angle3": { + "type": "number", + "title": "Angle 3", + "default": 0, + "description": "Third angle (e.g. N in LMN)" + }, + "direction_type": { + "enum": [ + "J2000", + "AZELGEO", + "LMN", + "SUN", + "MOON", + "MERCURY", + "VENUS", + "MARS", + "JUPITER", + "SATURN", + "URANUS", + "NEPTUNE", + "PLUTO" + ], + "type": "string", + "title": "Reference frame", + "default": "J2000", + "description": "" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "tags": [], + "type": "http://192.168.99.100:8008/api/task_type/observation", + "type_value": "observation", + "updated_at": "2020-08-25T13:28:33.983964", + "validation_code_js": "", + "version": "0.1" + }, + { + "id": 3, + "url": "http://192.168.99.100:8008/api/task_template/3", + "created_at": "2020-08-25T13:28:33.988294", + "description": "addon schema for calibrator observations", + "name": "calibrator schema", + "schema": { + "$id": "http://example.com/example.json", + "type": "object", + "$schema": "http://json-schema.org/draft-06/schema#", + "required": [ + "autoselect", + "duration", + "pointing" + ], + "properties": { + "duration": { + "type": "number", + "title": "Duration (seconds)", + "default": 600, + "minimum": 1, + "description": "Duration of this observation" + }, + "pointing": { + "$ref": "#/definitions/pointing", + "title": "Digital pointing", + "default": {}, + "description": "Manually selected calibrator" + }, + "autoselect": { + "type": "boolean", + "title": "Auto-select", + "default": true, + "description": "Auto-select calibrator based on elevation" + } + }, + "definitions": { + "pointing": { + "type": "object", + "required": [ + "angle1", + "angle2" + ], + "properties": { + "angle1": { + "type": "number", + "title": "Angle 1", + "default": 0, + "description": "First angle [rad] (e.g. RA)" + }, + "angle2": { + "type": "number", + "title": "Angle 2", + "default": 0, + "description": "Second angle [rad] (e.g. DEC)" + }, + "angle3": { + "type": "number", + "title": "Angle 3", + "default": 0, + "description": "Third angle [rad] (e.g. N in LMN)" + }, + "direction_type": { + "enum": [ + "J2000", + "AZELGEO", + "LMN", + "SUN", + "MOON", + "MERCURY", + "VENUS", + "MARS", + "JUPITER", + "SATURN", + "URANUS", + "NEPTUNE", + "PLUTO" + ], + "type": "string", + "title": "Reference frame", + "default": "J2000", + "description": "" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "tags": [], + "type": "http://192.168.99.100:8008/api/task_type/observation", + "type_value": "observation", + "updated_at": "2020-08-25T13:28:33.988312", + "validation_code_js": "", + "version": "0.1" + } + ] +}; + +export default TaskServiceMock; \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/components/PageHeader.js b/SAS/TMSS/frontend/tmss_webapp/src/layout/components/PageHeader.js index 6948ab85a4d1b0ce987dc50f7e3e81454287b5c3..0c7274c3471c957a2b2d961eade605d8a444d1bd 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/components/PageHeader.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/components/PageHeader.js @@ -21,6 +21,18 @@ export default ({ title, subTitle, actions, ...props}) => { } }; + const onButtonClick = (e, action) => { + if (action.actOn && action.actOn === 'click') { + action.props.callback(e); + } + }; + + const onButtonMouseOver = (e, action) => { + if (action.actOn && action.actOn === 'mouseOver') { + action.props.callback(e); + } + } + return ( <div className="page-header"> <div className="title"> @@ -29,11 +41,22 @@ export default ({ title, subTitle, actions, ...props}) => { </div> <div className="page-action-menu"> {(actions || []).map(action => { - return ( - <Link to={{ ...action.props }} title={action.title || ''} onClick={() => onClickLink(action)}> - <i className={`fa ${action.icon}`}></i> - </Link> - )})} + if (action.type === 'button') { + return ( + <button className="p-link"> + <i className={`fa ${action.icon}`} + onMouseOver={(e) => onButtonMouseOver(e, action)} + onClick={(e) => onButtonClick(e, action)} /> + </button> + ); + } else { + return ( + <Link to={{ ...action.props }} title={action.title || ''} onClick={() => onClickLink(action)}> + <i className={`fa ${action.icon}`}></i> + </Link> + ); + } + })} </div> </div> ); diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_pageheader.scss b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_pageheader.scss index fd360e6aaaefa18b093d946c5cfeacd662745b15..32a00c556353bf9a2324cc7d421b1d627525f637 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_pageheader.scss +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_pageheader.scss @@ -17,4 +17,8 @@ } .page-action-menu i { margin-left: 5px; +} + +.page-header .fa { + font-size: 25px !important; } \ 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 7932c68177614ea0cb5085e74d41d073069d56c7..3a311b921c0d62f1111badea3ce00d53117fb8d8 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Cycle/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Cycle/view.js @@ -93,7 +93,10 @@ export class CycleView extends Component { </div> } </div> */ } - <PageHeader location={this.props.location} title={'Cycle - View'} actions={[{icon:'fa-edit',title:'Click to Edit Cycle', props:{ pathname: `/cycle/edit/${this.state.cycle.name}`, state: {id: this.state.cycle?this.state.cycle.name:''}}},{name: 'fa-times',props:{ pathname: `/cycle`}}]}/> + <PageHeader location={this.props.location} title={'Cycle - Details'} + actions={[ {icon:'fa-edit', title:'Click to Edit Cycle', props:{ pathname: `/cycle/edit/${this.state.cycle.name}`, + state: {id: this.state.cycle?this.state.cycle.name:''}}}, + {icon: 'fa-window-close',props:{ pathname: `/cycle`}}]}/> { this.state.isLoading && <AppLoader /> } { this.state.cycle && <React.Fragment> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/view.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/view.js index 37c03871c0e9d32442349be2a94466be8efe7f47..799d8cef35b4b39270d1d6cce36c79fe0f50606a 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/view.js @@ -4,6 +4,7 @@ import moment from 'moment'; import _ from 'lodash'; import { Chips } from 'primereact/chips'; +import { TieredMenu } from 'primereact/tieredmenu'; import ResourceDisplayList from './ResourceDisplayList'; @@ -30,6 +31,11 @@ export class ProjectView extends Component { } this.state.redirect = this.state.projectId?"":'/project' // If no project id is passed, redirect to Project list page this.resourceUnitMap = UnitConverter.resourceUnitMap; // Resource unit conversion factor and constraints + this.optionsMenu = React.createRef(); + this.menuOptions = [ {label:'Add Scheduling Unit', icon: "fa fa-", command: () => {this.selectOptionMenu('Add SU')}} ]; + + this.showOptionMenu = this.showOptionMenu.bind(this); + this.selectOptionMenu = this.selectOptionMenu.bind(this); } componentDidMount() { @@ -70,6 +76,22 @@ export class ProjectView extends Component { } + showOptionMenu(event) { + this.optionsMenu.toggle(event); + } + + selectOptionMenu(menuName) { + switch(menuName) { + case 'Add SU': { + this.setState({redirect: `/project/${this.state.project.name}/schedulingunit/create`}); + break; + } + default: { + break; + } + } + } + render() { if (this.state.redirect) { return <Redirect to={ {pathname: this.state.redirect} }></Redirect> @@ -88,12 +110,27 @@ export class ProjectView extends Component { </Link> <Link to={{ pathname: `/project/edit/${this.state.project.name}`, state: {id: this.state.project?this.state.project.name:''}}} title="Edit Project" style={{float: "right"}}> - <i className="fa fa-edit" style={{marginTop: "10px"}}></i> + <i className="fa fa-edit" style={{marginTop: "10px", marginLeft: "5px"}}></i> </Link> + <TieredMenu model={this.menuOptions} popup ref={el => this.optionsMenu = el} /> + <button className="p-link" style={{float: "right"}}> + <i className="fa fa-bars" label="Toggle Columns" style={{marginTop: "10px", marginLeft: "5px"}} + onMouseOver={(e) => this.optionsMenu.toggle(e)} /> + </button> + </div> } </div> */} - <PageHeader location={this.props.location} title={'Project - View'} actions={[{icon: 'fa-edit',title:'Click to Edit Project', props : { pathname: `/project/edit/${this.state.project.name}`, state: {id: this.state.project?this.state.project.name:''&& this.state.project}}},{icon:'fa-times',title: 'Click to Close Project View', props : { pathname: `/project`}}]}/> + <TieredMenu model={this.menuOptions} popup ref={el => this.optionsMenu = el} /> + <PageHeader location={this.props.location} title={'Project - View'} + actions={[ {icon:'fa-bars',title: '', type:'button', + actOn:'mouseOver', props : { callback: this.showOptionMenu}}, + {icon: 'fa-edit',title:'Click to Edit Project', type:'link', + props : { pathname: `/project/edit/${this.state.project.name}`, + state: {id: this.state.project?this.state.project.name:''&& this.state.project}}}, + {icon:'fa-window-close',title: 'Click to Close Project View', type:'link', + props : { pathname: `/project`}}, + ]}/> { this.state.isLoading && <AppLoader /> } { this.state.project && <React.Fragment> 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 a2af2edb155edd28f6de0001ee375d029fcd359f..096a7c62e9530967a72d32fd558a38d549c9af75 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/ViewSchedulingUnit.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/ViewSchedulingUnit.js @@ -121,7 +121,8 @@ class ViewSchedulingUnit extends Component{ </Link> </div> </div> */} - <PageHeader location={this.props.location} title={'Scheduling Unit - Details'} actions={[{icon: 'fa-times',title:'Click to Close Scheduling Unit View', props : { pathname: '/schedulingunit'}}]}/> + <PageHeader location={this.props.location} title={'Scheduling Unit - Details'} + actions={[{icon: 'fa-window-close',title:'Click to Close Scheduling Unit View', props : { pathname: '/schedulingunit'}}]}/> { this.state.isLoading ? <AppLoader/> :this.state.scheduleunit && <> <div className="main-content"> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/create.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/create.js new file mode 100644 index 0000000000000000000000000000000000000000..5aafacbe4360cb71c87c3741b62f5d6034aee552 --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/create.js @@ -0,0 +1,440 @@ +import React, {Component} from 'react'; +import { Link, Redirect } from 'react-router-dom'; +import _ from 'lodash'; +import $RefParser from "@apidevtools/json-schema-ref-parser"; + +import {InputText} from 'primereact/inputtext'; +import {InputTextarea} from 'primereact/inputtextarea'; +import {Dropdown} from 'primereact/dropdown'; +import { Button } from 'primereact/button'; +import {Dialog} from 'primereact/components/dialog/Dialog'; +import {Growl} from 'primereact/components/growl/Growl'; + +import AppLoader from '../../layout/components/AppLoader'; +import Jeditor from '../../components/JSONEditor/JEditor'; + +import ProjectService from '../../services/project.service'; +import ScheduleService from '../../services/schedule.service'; +import TaskService from '../../services/task.service'; +import UIConstants from '../../utils/ui.constants'; + +/** + * Component to create a new SchedulingUnit from Observation strategy template + */ +export class SchedulingUnitCreate extends Component { + constructor(props) { + super(props); + this.state = { + isLoading: true, // Flag for loading spinner + dialog: { header: '', detail: ''}, // Dialog properties + redirect: null, // URL to redirect + errors: [], // Form Validation errors + schedulingSets: [], // Scheduling set of the selected project + schedulingUnit: { + project: (props.match?props.match.params.project:null) || null, + }, + projectDisabled: (props.match?(props.match.params.project? true:false):false), // Disable project selection if + observStrategy: {}, // Selected strategy to create SU + paramsSchema: null, // JSON Schema to be generated from strategy template to pass to JSOn editor + validEditor: false, // For JSON editor validation + validFields: {}, // For Form Validation + } + this.projects = []; // All projects to load project dropdown + this.schedulingSets = []; // All scheduling sets to be filtered for project + this.observStrategies = []; // All Observing strategy templates + this.taskTemplates = []; // All task templates to be filtered based on tasks in selected strategy template + this.tooltipOptions = UIConstants.tooltipOptions; + this.nameInput = React.createRef(); // Ref to Name field for auto focus + this.formRules = { // Form validation rules + name: {required: true, message: "Name can not be empty"}, + description: {required: true, message: "Description can not be empty"}, + project: {required: true, message: "Select project to get Scheduling Sets"}, + scheduling_set_id: {required: true, message: "Select the Scheduling Set"}, + }; + + this.setEditorOutput = this.setEditorOutput.bind(this); + this.changeProject = this.changeProject.bind(this); + this.changeStrategy = this.changeStrategy.bind(this); + this.setSchedUnitParams = this.setSchedUnitParams.bind(this); + this.validateForm = this.validateForm.bind(this); + this.validateEditor = this.validateEditor.bind(this); + this.setEditorFunction = this.setEditorFunction.bind(this); + this.saveSchedulingUnit = this.saveSchedulingUnit.bind(this); + this.cancelCreate = this.cancelCreate.bind(this); + this.reset = this.reset.bind(this); + } + + componentDidMount() { + const promises = [ ProjectService.getProjectList(), + ScheduleService.getSchedulingSets(), + ScheduleService.getObservationStrategies(), + TaskService.getTaskTemplates()] + Promise.all(promises).then(responses => { + this.projects = responses[0]; + this.schedulingSets = responses[1]; + this.observStrategies = responses[2]; + this.taskTemplates = responses[3]; + if (this.state.schedulingUnit.project) { + const projectSchedSets = _.filter(this.schedulingSets, {'project_id': this.state.schedulingUnit.project}); + this.setState({isLoading: false, schedulingSets: projectSchedSets}); + } else { + this.setState({isLoading: false}); + } + }); + } + + /** + * Function to call on change of project and reload scheduling set dropdown + * @param {string} projectName + */ + changeProject(projectName) { + const projectSchedSets = _.filter(this.schedulingSets, {'project_id': projectName}); + let schedulingUnit = this.state.schedulingUnit; + schedulingUnit.project = projectName; + this.setState({schedulingUnit: schedulingUnit, schedulingSets: projectSchedSets, validForm: this.validateForm('project')}); + } + + /** + * Function called when observation strategy template is changed. + * It generates the JSON schema for JSON editor and defult vales for the parameters to be captured + * @param {number} strategyId + */ + async changeStrategy (strategyId) { + const observStrategy = _.find(this.observStrategies, {'id': strategyId}); + const tasks = observStrategy.template.tasks; + let paramsOutput = {}; + let schema = { type: 'object', additionalProperties: false, + properties: {}, definitions:{} + }; + + for (const taskName in tasks) { + const task = tasks[taskName]; + //Resolve task from the strategy template + const $taskRefs = await $RefParser.resolve(task); + + // Identify the task specification template of every task in the strategy template + const taskTemplate = _.find(this.taskTemplates, {'name': task['specifications_template']}); + schema['$id'] = taskTemplate.schema['$id']; + schema['$schema'] = taskTemplate.schema['$schema']; + observStrategy.template.parameters.forEach(async(param, index) => { + if (param.refs[0].indexOf(`/tasks/${taskName}`) > 0) { + // Resolve the identified template + const $templateRefs = await $RefParser.resolve(taskTemplate); + let property = { }; + let tempProperty = null; + // Get the property type from the template and create new property in the schema for the parameters + try { + tempProperty = $templateRefs.get(param.refs[0].replace(`#/tasks/${taskName}/specifications_doc`, '#/schema/properties')) + } catch(error) { + const taskPaths = param.refs[0].split("/"); + tempProperty = _.cloneDeep(taskTemplate.schema.properties[taskPaths[4]]); + if (tempProperty.type === 'array') { + tempProperty = tempProperty.items.properties[taskPaths[6]]; + } + property = tempProperty; + } + property.title = param.name; + property.default = $taskRefs.get(param.refs[0].replace(`#/tasks/${taskName}`, '#')); + paramsOutput[`param_${index}`] = property.default; + schema.properties[`param_${index}`] = property; + // Set property defintions taken from the task template in new schema + for (const definitionName in taskTemplate.schema.definitions) { + schema.definitions[definitionName] = taskTemplate.schema.definitions[definitionName]; + } + } + }); + } + this.setState({observStrategy: observStrategy, paramsSchema: schema, paramsOutput: paramsOutput}); + + // Function called to clear the JSON Editor fields and reload with new schema + if (this.state.editorFunction) { + this.state.editorFunction(); + } + } + + /** + * This is the callback method to be passed to the JSON editor. + * JEditor will call this function when there is change in the editor. + * @param {Object} jsonOutput + * @param {Array} errors + */ + setEditorOutput(jsonOutput, errors) { + this.paramsOutput = jsonOutput; + this.validEditor = errors.length === 0; + this.setState({ paramsOutput: jsonOutput, + validEditor: errors.length === 0, + validForm: this.validateForm()}); + } + + /** + * This function is mainly added for Unit Tests. If this function is removed Unit Tests will fail. + */ + validateEditor() { + return this.validEditor?true:false; + } + + /** + * Function to set form values to the SU object + * @param {string} key + * @param {object} value + */ + setSchedUnitParams(key, value) { + let schedulingUnit = this.state.schedulingUnit; + schedulingUnit[key] = value; + this.setState({schedulingUnit: schedulingUnit, validForm: this.validateForm(key), validEditor: this.validateEditor()}); + this.validateEditor(); + } + + /** + * JEditor's function that to be called when parent wants to trigger change in the JSON Editor + * @param {Function} editorFunction + */ + setEditorFunction(editorFunction) { + this.setState({editorFunction: editorFunction}); + } + + /** + * Validation function to validate the form or field based on the form rules. + * If no argument passed for fieldName, validates all fields in the form. + * @param {string} fieldName + */ + validateForm(fieldName) { + let validForm = false; + let errors = this.state.errors; + let validFields = this.state.validFields; + if (fieldName) { + delete errors[fieldName]; + delete validFields[fieldName]; + if (this.formRules[fieldName]) { + const rule = this.formRules[fieldName]; + const fieldValue = this.state.schedulingUnit[fieldName]; + if (rule.required) { + if (!fieldValue) { + errors[fieldName] = rule.message?rule.message:`${fieldName} is required`; + } else { + validFields[fieldName] = true; + } + } + } + } else { + errors = {}; + validFields = {}; + for (const fieldName in this.formRules) { + const rule = this.formRules[fieldName]; + const fieldValue = this.state.schedulingUnit[fieldName]; + if (rule.required) { + if (!fieldValue) { + errors[fieldName] = rule.message?rule.message:`${fieldName} is required`; + } else { + validFields[fieldName] = true; + } + } + } + } + this.setState({errors: errors, validFields: validFields}); + if (Object.keys(validFields).length === Object.keys(this.formRules).length) { + validForm = true; + } + return validForm; + } + + /** + * Function to create Scheduling unit + */ + async saveSchedulingUnit() { + let observStrategy = _.cloneDeep(this.state.observStrategy); + const $refs = await $RefParser.resolve(observStrategy.template); + observStrategy.template.parameters.forEach(async(param, index) => { + $refs.set(observStrategy.template.parameters[index]['refs'][0], this.state.paramsOutput['param_' + index]); + }); + + const schedulingUnit = await ScheduleService.saveSUDraftFromObservStrategy(observStrategy, this.state.schedulingUnit); + if (schedulingUnit) { + // this.growl.show({severity: 'success', summary: 'Success', detail: 'Scheduling Unit and tasks created successfully!'}); + const dialog = {header: 'Success', detail: 'Scheduling Unit and Tasks are created successfully. Do you want to create another Scheduling Unit?'}; + this.setState({schedulingUnit: schedulingUnit, dialogVisible: true, dialog: dialog}) + } else { + this.growl.show({severity: 'error', summary: 'Error Occured', detail: 'Unable to save Scheduling Unit/Tasks'}); + } + } + + /** + * Cancel SU creation and redirect + */ + cancelCreate() { + this.setState({redirect: '/schedulingunit'}) + } + + /** + * Reset function to be called when user wants to create new SU + */ + reset() { + const schedulingSets = this.state.schedulingSets; + this.nameInput.element.focus(); + this.setState({ + dialogVisible: false, + dialog: { header: '', detail: ''}, + errors: [], + schedulingSets: this.props.match.params.project?schedulingSets:[], + schedulingUnit: { + name: '', + description: '', + project: this.props.match.params.project || null, + }, + projectDisabled: (this.props.match.params.project? true:false), + observStrategy: {}, + paramsOutput: null, + validEditor: false, + validFields: {} + }); + this.state.editorFunction(); + } + + render() { + if (this.state.redirect) { + return <Redirect to={ {pathname: this.state.redirect} }></Redirect> + } + + const schema = this.state.paramsSchema; + + let jeditor = null; + if (schema) { + jeditor = React.createElement(Jeditor, {title: "Task Parameters", + schema: schema, + initValue: this.state.paramsOutput, + callback: this.setEditorOutput, + parentFunction: this.setEditorFunction + }); + } + return ( + <React.Fragment> + <div className="p-grid"> + <Growl ref={(el) => this.growl = el} /> + + <div className="p-col-10 p-lg-10 p-md-10"> + <h2>Scheduling Unit - Add</h2> + </div> + <div className="p-col-2 p-lg-2 p-md-2"> + <Link to={{ pathname: '/schedulingunit'}} tite="Close" style={{float: "right"}}> + <i className="fa fa-window-close" style={{marginTop: "10px"}}></i> + </Link> + </div> + </div> + { this.state.isLoading ? <AppLoader /> : + <> + <div> + <div className="p-fluid"> + <div className="p-field p-grid"> + <label htmlFor="schedUnitName" className="col-lg-2 col-md-2 col-sm-12">Name <span style={{color:'red'}}>*</span></label> + <div className="col-lg-3 col-md-3 col-sm-12"> + <InputText className={this.state.errors.name ?'input-error':''} id="schedUnitName" data-testid="name" + tooltip="Enter name of the Scheduling Unit" tooltipOptions={this.tooltipOptions} maxLength="128" + ref={input => {this.nameInput = input;}} + value={this.state.schedulingUnit.name} autoFocus + onChange={(e) => this.setSchedUnitParams('name', e.target.value)} + onBlur={(e) => this.setSchedUnitParams('name', e.target.value)}/> + <label className={this.state.errors.name?"error":"info"}> + {this.state.errors.name ? this.state.errors.name : "Max 128 characters"} + </label> + </div> + <div className="col-lg-1 col-md-1 col-sm-12"></div> + <label htmlFor="description" className="col-lg-2 col-md-2 col-sm-12">Description <span style={{color:'red'}}>*</span></label> + <div className="col-lg-3 col-md-3 col-sm-12"> + <InputTextarea className={this.state.errors.description ?'input-error':''} rows={3} cols={30} + tooltip="Longer description of the scheduling unit" tooltipOptions={this.tooltipOptions} maxLength="128" + data-testid="description" value={this.state.schedulingUnit.description} + onChange={(e) => this.setSchedUnitParams('description', e.target.value)} + onBlur={(e) => this.setSchedUnitParams('description', e.target.value)}/> + <label className={this.state.errors.description ?"error":"info"}> + {this.state.errors.description ? this.state.errors.description : "Max 255 characters"} + </label> + </div> + </div> + <div className="p-field p-grid"> + <label htmlFor="project" className="col-lg-2 col-md-2 col-sm-12">Project <span style={{color:'red'}}>*</span></label> + <div className="col-lg-3 col-md-3 col-sm-12" data-testid="project" > + <Dropdown inputId="project" optionLabel="name" optionValue="name" + tooltip="Project" tooltipOptions={this.tooltipOptions} + value={this.state.schedulingUnit.project} disabled={this.state.projectDisabled} + options={this.projects} + onChange={(e) => {this.changeProject(e.value)}} + placeholder="Select Project" /> + <label className={this.state.errors.project ?"error":"info"}> + {this.state.errors.project ? this.state.errors.project : "Select Project to get Scheduling Sets"} + </label> + </div> + <div className="col-lg-1 col-md-1 col-sm-12"></div> + <label htmlFor="schedSet" className="col-lg-2 col-md-2 col-sm-12">Scheduling Set <span style={{color:'red'}}>*</span></label> + <div className="col-lg-3 col-md-3 col-sm-12"> + <Dropdown data-testid="schedSet" id="schedSet" optionLabel="name" optionValue="id" + tooltip="Scheduling set of the project" tooltipOptions={this.tooltipOptions} + value={this.state.schedulingUnit.scheduling_set_id} + options={this.state.schedulingSets} + onChange={(e) => {this.setSchedUnitParams('scheduling_set_id',e.value)}} + placeholder="Select Scheduling Set" /> + <label className={this.state.errors.scheduling_set_id ?"error":"info"}> + {this.state.errors.scheduling_set_id ? this.state.errors.scheduling_set_id : "Scheduling Set of the Project"} + </label> + </div> + </div> + <div className="p-field p-grid"> + <label htmlFor="observStrategy" className="col-lg-2 col-md-2 col-sm-12">Observation Strategy <span style={{color:'red'}}>*</span></label> + <div className="col-lg-3 col-md-3 col-sm-12" data-testid="observStrategy" > + <Dropdown inputId="observStrategy" optionLabel="name" optionValue="id" + tooltip="Observation Strategy Template to be used to create the Scheduling Unit and Tasks" tooltipOptions={this.tooltipOptions} + value={this.state.observStrategy.id} + options={this.observStrategies} + onChange={(e) => {this.changeStrategy(e.value)}} + placeholder="Select Strategy" /> + </div> + <div className="col-lg-1 col-md-1 col-sm-12"></div> + </div> + + </div> + <div className="p-fluid"> + <div className="p-grid"> + <div className="p-col-12"> + {this.state.paramsSchema?jeditor:""} + </div> + </div> + </div> + + <div className="p-grid p-justify-start"> + <div className="p-col-1"> + <Button label="Save" className="p-button-primary" icon="pi pi-check" onClick={this.saveSchedulingUnit} + disabled={!this.state.validEditor || !this.state.validForm} data-testid="save-btn" /> + </div> + <div className="p-col-1"> + <Button label="Cancel" className="p-button-danger" icon="pi pi-times" onClick={this.cancelCreate} /> + </div> + </div> + </div> + + </> + } + + {/* Dialog component to show messages and get input */} + <div className="p-grid" data-testid="confirm_dialog"> + <Dialog header={this.state.dialog.header} visible={this.state.dialogVisible} style={{width: '25vw'}} inputId="confirm_dialog" + modal={true} onHide={() => {this.setState({dialogVisible: false})}} + footer={<div> + <Button key="back" onClick={() => {this.setState({dialogVisible: false, redirect: `/schedulingunit/view/draft/${this.state.schedulingUnit.id}`});}} label="No" /> + <Button key="submit" type="primary" onClick={this.reset} label="Yes" /> + </div> + } > + <div className="p-grid"> + <div className="col-lg-2 col-md-2 col-sm-2" style={{margin: 'auto'}}> + <i className="pi pi-check-circle pi-large pi-success"></i> + </div> + <div className="col-lg-10 col-md-10 col-sm-10"> + {this.state.dialog.detail} + </div> + </div> + </Dialog> + </div> + </React.Fragment> + ); + } +} + +export default SchedulingUnitCreate; \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/create.test.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/create.test.js new file mode 100644 index 0000000000000000000000000000000000000000..ccdaf6f98f69ceb13f23508624b911590cd9f148 --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/create.test.js @@ -0,0 +1,140 @@ +import React from 'react'; +import { BrowserRouter as Router } from 'react-router-dom'; +import { act } from "react-dom/test-utils"; +import { render, cleanup, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; + +import {SchedulingUnitCreate} from './create'; + +import ScheduleService from '../../services/schedule.service'; +import ProjectService from '../../services/project.service'; +import TaskService from '../../services/task.service'; + +import SUServiceMock from '../../__mocks__/scheduleunit.service.data'; +import ProjectServiceMock from '../../__mocks__/project.service.data'; +import TaskServiceMock from '../../__mocks__/task.service.data'; + + + +let projectListSpy, scheduleSetListSpy, observStrategiesSpy, taskTemplatesSpy, saveSUFromStrategySpy, updateSUSpy, createSUTasksSpy; + +beforeEach(() => { + setMockSpy(); +}); + +afterEach(() => { + // cleanup on exiting + clearMockSpy(); + cleanup(); +}); + +const setMockSpy = () => { + projectListSpy = jest.spyOn(ProjectService, 'getProjectList'); + projectListSpy.mockImplementation(() => { return Promise.resolve(ProjectServiceMock.projectList)}); + scheduleSetListSpy = jest.spyOn(ScheduleService, 'getSchedulingSets'); + scheduleSetListSpy.mockImplementation(() => { return Promise.resolve(SUServiceMock.scheduleSetList)}); + observStrategiesSpy = jest.spyOn(ScheduleService, 'getObservationStrategies'); + observStrategiesSpy.mockImplementation(() => { return Promise.resolve(SUServiceMock.observStrategies)}); + taskTemplatesSpy = jest.spyOn(TaskService, 'getTaskTemplates'); + taskTemplatesSpy.mockImplementation(() => { return Promise.resolve(TaskServiceMock.taskTemplates)}); + saveSUFromStrategySpy = jest.spyOn(ScheduleService, 'saveSUDraftFromObservStrategy'); + saveSUFromStrategySpy.mockImplementation((observStrategy, schedulingUnit) => { + return Promise.resolve(SUServiceMock.schedulingUnitFromObservStrategy); + }); + updateSUSpy = jest.spyOn(ScheduleService, 'updateSchedulingUnitDraft'); + updateSUSpy.mockImplementation((schedulingUnit) => { + return Promise.resolve(SUServiceMock.schedulingUnitFromObservStrategy); + }); + createSUTasksSpy = jest.spyOn(ScheduleService, 'createSUTaskDrafts'); + createSUTasksSpy.mockImplementation((schedulingUnit) => { + return Promise.resolve(SUServiceMock.schedulingUnitFromObservStrategy); + }); + +} + +const clearMockSpy = () => { + projectListSpy.mockRestore(); + scheduleSetListSpy.mockRestore(); + observStrategiesSpy.mockRestore(); + taskTemplatesSpy.mockRestore(); + saveSUFromStrategySpy.mockRestore(); + updateSUSpy.mockRestore(); + createSUTasksSpy.mockRestore(); +} + +it("renders create page with all fields and default values", async() => { + console.log("renders create page with all fields and default values ------------------------"); + + let content; + await act(async () => { + content = render(<Router><SchedulingUnitCreate /></Router>); + }); + + expect(content.queryByText('Scheduling Unit - Add')).not.toBe(null); // Page loaded successfully + expect(projectListSpy).toHaveBeenCalled(); // Mock Spy called successfully + expect(observStrategiesSpy).toHaveBeenCalled(); // Mock Spy called successfully + expect(scheduleSetListSpy).toHaveBeenCalled(); // Mock Spy called successfully + expect(taskTemplatesSpy).toHaveBeenCalled(); // Mock Spy called successfully + expect(content.queryByText('TMSS-Commissioning')).toBeInTheDocument(); // Project Dropdown loaded successfully + expect(content.queryByText('UC1 observation strategy template')).toBeInTheDocument(); // Observation Strategy Dropdown loaded successfully + expect(content.queryByText('Task Parameters')).not.toBeInTheDocument(); // JSON Editor not rendered + expect(content.queryByTestId('save-btn')).toHaveAttribute("disabled"); +}); + +it("creates new Scheduling Unit with default values", async() => { + console.log("creates new Scheduling Unit with default values ------------------------"); + + let content; + await act(async () => { + content = render(<Router><SchedulingUnitCreate /></Router>); + }); + + const nameInput = content.queryByTestId('name'); + const descInput = content.queryByTestId('description'); + const projInput = content.getAllByRole("listbox")[0].children[2] ; + const observStrategyInput = content.getAllByRole("listbox")[2].children[0] ; + + // Set values for all mandatory input and test if save button is enabled + fireEvent.change(nameInput, { target: { value: 'UC1 test scheduling unit 1.1' } }); + expect(nameInput.value).toBe("UC1 test scheduling unit 1.1"); + fireEvent.change(descInput, { target: { value: 'UC1 test scheduling unit 1.1' } }); + expect(descInput.value).toBe("UC1 test scheduling unit 1.1"); + + // After selecting values for all dropdowns + await act(async () => { + fireEvent.click(projInput); + }); + const schedulingSetInput = content.getAllByRole("listbox")[1].children[0] ; + expect(content.queryAllByText('Select Project').length).toBe(1); + expect(content.queryAllByText('TMSS-Commissioning').length).toBe(3); + + await act(async () => { + fireEvent.click(schedulingSetInput); + }); + expect(content.queryAllByText('Select Scheduling Set').length).toBe(1); + expect(content.queryAllByText('Test Scheduling Set UC1 example 0').length).toBe(3); + + await act( async() => { + fireEvent.click(observStrategyInput); + }); + expect(content.queryAllByText('Select Strategy').length).toBe(1); + expect(content.queryAllByText('UC1 observation strategy template').length).toBe(3); + expect(content.queryByText('Task Parameters')).toBeInTheDocument(); + expect(content.queryByText('Target Pointing 0')).toBeInTheDocument(); + expect(content.queryByText('Not a valid input. Mimimum: 00:00:00, Maximum:23:59:59.')).not.toBeInTheDocument(); + expect(content.queryByText('Not a valid input. Mimimum: 00:00:00, Maximum:90:00:00.')).not.toBeInTheDocument(); + + /* This is set again to call the validateEditor function in the component. + If this is removed, the editor validation will not occur in the test but works in browser.*/ + await act( async() => { + fireEvent.change(nameInput, { target: { value: 'UC1 test scheduling unit 1.1' } }); + }); + + expect(content.queryByTestId('save-btn').hasAttribute("disabled")).toBeFalsy(); + + await act(async () => { + fireEvent.click(content.queryByTestId('save-btn')); + }); + expect(saveSUFromStrategySpy).toHaveBeenCalled(); + +}); \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/index.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/index.js index 2d0364fdcf3d74ea08a0c3fd29031fdc36b7fc03..6d202556c02b04be61955e04696017798ce623ae 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/index.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/index.js @@ -15,7 +15,9 @@ export class Scheduling extends Component { render() { return ( <> - <PageHeader location={this.props.location} title={'Scheduling Unit - List'}/> + <PageHeader location={this.props.location} title={'Scheduling Unit - List'} + actions={[{icon: 'fa fa-plus-square', title: 'Add New Scheduling Unit', + props: {pathname: '/schedulingunit/create'}}]} /> {this.state.scheduleunit && <SchedulingUnitList /> } </> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/view.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/view.js index d17d9aeeef26500df5affb2114f2aaf0dc89b904..8fba525a164738a9c539e668eff231345742deb9 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/view.js @@ -123,7 +123,7 @@ export class TaskView extends Component { state: {taskId: this.state.task?this.state.task.id:''} } }, - { icon: 'fa-times', + { icon: 'fa-window-close', title:'Click to Close Task', props : { pathname:'/task' }}]; } else { diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js index 82b793387cdfa9072288db1069874521d8ed8d7b..bebe99d8eb04460c38b2097e576b94ce6d5dfc16 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js @@ -10,7 +10,8 @@ import {ProjectList, ProjectCreate, ProjectView, ProjectEdit} from './Project'; import {Dashboard} from './Dashboard'; import {Scheduling} from './Scheduling'; import {TaskEdit, TaskView} from './Task'; -import ViewSchedulingUnit from './Scheduling/ViewSchedulingUnit' +import ViewSchedulingUnit from './Scheduling/ViewSchedulingUnit'; +import SchedulingUnitCreate from './Scheduling/create'; import { CycleList, CycleCreate, CycleView, CycleEdit } from './Cycle'; export const routes = [ @@ -28,6 +29,10 @@ export const routes = [ component: Scheduling, name: 'Scheduling Unit', title: 'Scheduling Unit - List' + },{ + path: "/schedulingunit/create", + component: SchedulingUnitCreate, + name: 'Scheduling Unit Add' },{ path: "/task", component: TaskView, @@ -83,8 +88,12 @@ export const routes = [ component: ProjectEdit, name: 'Project Edit', title: 'Project Edit' - }, - { + },{ + path: "/project/:project/schedulingunit/create", + component: SchedulingUnitCreate, + name: 'Scheduling Unit Add', + title: 'Scheduling Unit - Add' + },{ path: "/cycle/edit/:id", component: CycleEdit, name: 'Cycle Edit', diff --git a/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js b/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js index ac5c0e5658ae4f23e4b58c3f4ae37ffcbe11748b..a7e65ace0ccd24316cb5902bf1741a14cc94fddb 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js @@ -166,6 +166,68 @@ const ScheduleService = { }); return res; }, + getSchedulingSets: async function() { + try { + const response = await axios.get('/api/scheduling_set/'); + return response.data.results; + } catch(error) { + console.error(error); + return []; + }; + }, + getObservationStrategies: async function() { + try { + const response = await axios.get('/api/scheduling_unit_observing_strategy_template/'); + return response.data.results; + } catch(error) { + console.error(error); + return []; + }; + }, + saveSUDraftFromObservStrategy: async function(observStrategy, schedulingUnit) { + try { + // Create the scheduling unit draft with observation strategy and scheduling set + const url = `/api/scheduling_unit_observing_strategy_template/${observStrategy.id}/create_scheduling_unit/?scheduling_set_id=${schedulingUnit.scheduling_set_id}&name=${schedulingUnit.name}&description=${schedulingUnit.description}` + const suObsResponse = await axios.get(url); + schedulingUnit = suObsResponse.data; + if (schedulingUnit && schedulingUnit.id) { + // Update the newly created SU draft requirement_doc with captured parameter values + schedulingUnit.requirements_doc = observStrategy.template; + delete schedulingUnit['duration']; + schedulingUnit = await this.updateSchedulingUnitDraft(schedulingUnit); + if (!schedulingUnit || !schedulingUnit.id) { + return null; + } + // Create task drafts with updated requirement_doc + schedulingUnit = await this.createSUTaskDrafts(schedulingUnit); + if (schedulingUnit && schedulingUnit.task_drafts.length > 0) { + return schedulingUnit; + } + } + return null; + } catch(error) { + console.error(error); + return null; + }; + }, + updateSchedulingUnitDraft: async function(schedulingUnit) { + try { + const suUpdateResponse = await axios.put(`/api/scheduling_unit_draft/${schedulingUnit.id}/`, schedulingUnit); + return suUpdateResponse.data; + } catch(error) { + console.error(error); + return null + } + }, + createSUTaskDrafts: async (schedulingUnit) => { + try { + const suCreateTaskResponse = await axios.get(`/api/scheduling_unit_draft/${schedulingUnit.id}/create_task_drafts/`); + return suCreateTaskResponse.data; + } catch(error) { + console.error(error); + return null; + } + } } export default ScheduleService; \ No newline at end of file