diff --git a/SAS/TMSS/backend/test/t_subtasks.py b/SAS/TMSS/backend/test/t_subtasks.py
index 4fceef5dbcba25badfc1dcd6f798d473432c5833..e8d518742828500a48a65e37e7ef1446c87a9a59 100755
--- a/SAS/TMSS/backend/test/t_subtasks.py
+++ b/SAS/TMSS/backend/test/t_subtasks.py
@@ -335,125 +335,6 @@ class SubTasksCreationFromTaskBluePrint(unittest.TestCase):
         self.assertEqual(obs_subtask.output_dataproducts.first(), ingest_subtask.get_transformed_input_dataproduct(ingest_subtask.output_dataproducts.first()))
 
 
-
-
-class SubTasksCreationFromTaskBluePrintCalibrator(unittest.TestCase):
-
-    def test_create_sequence_of_subtask_from_task_blueprint_calibrator_failure(self):
-        """
-        Create multiple subtasks from a task blueprint when task is a calibrator
-        Check that exception should occur due too missing related target observation
-        """
-        task_blueprint = create_task_blueprint_object_for_testing(task_template_name="calibrator observation")
-        with self.assertRaises(SubtaskCreationException):
-            create_observation_control_subtask_from_task_blueprint(task_blueprint)
-
-    @unittest.skip("JS 2020-09-08: Cannot reproduce SubtaskCreationException. How is this test supposed to work??")
-    def test_create_sequence_of_subtask_from_task_blueprint_calibrator(self):
-        """
-        Create multiple subtasks from a task blueprint when task is a calibrator and is related to task blueprint
-        of a target observation
-        Check that exception should occur due too missing pointing setting in target observation,
-        the calibrator default is AutoSelect=True
-        Check NO exception, when AutoSelect=False
-        """
-        cal_task_blueprint = create_task_blueprint_object_for_testing(task_template_name="calibrator observation")
-        target_task_blueprint = create_task_blueprint_object_for_testing()
-        create_scheduling_relation_task_blueprint_for_testing(cal_task_blueprint, target_task_blueprint)
-
-        with self.assertRaises(SubtaskCreationException):
-            create_observation_control_subtask_from_task_blueprint(cal_task_blueprint)
-
-        cal_task_blueprint.specifications_doc['autoselect'] = False
-        cal_task_blueprint.specifications_doc['pointing']['angle1'] = 1.111
-        cal_task_blueprint.specifications_doc['pointing']['angle2'] = 2.222
-        subtask = create_observation_control_subtask_from_task_blueprint(cal_task_blueprint)
-        self.assertEqual("defined", str(subtask.state))
-        self.assertEqual("observation control", str(subtask.specifications_template.name))
-        self.assertEqual("observation", str(subtask.specifications_template.type))
-        self.assertEqual('J2000', subtask.specifications_doc['stations']['analog_pointing']['direction_type'])
-        self.assertEqual(1.111, subtask.specifications_doc['stations']['analog_pointing']['angle1'])
-        self.assertEqual(2.222, subtask.specifications_doc['stations']['analog_pointing']['angle2'])
-
-    def test_create_combined_subtask_from_task_blueprints(self):
-        """
-        Create subtasks from a target task blueprint and a separate calibrator task blueprint.
-        """
-        cal_task_blueprint = create_task_blueprint_object_for_testing(task_template_name="calibrator observation")
-        target_task_blueprint = create_task_blueprint_object_for_testing()
-        create_scheduling_relation_task_blueprint_for_testing(cal_task_blueprint, target_task_blueprint, placement='parallel')
-
-        subtask_1 = create_observation_control_subtask_from_task_blueprint(target_task_blueprint)
-        num_pointings_target = len(subtask_1.specifications_doc['stations']['digital_pointings'])
-
-        # assert target subtask still in defining state
-        self.assertEqual("defining", str(subtask_1.state))
-        self.assertTrue(subtask_1.primary)
-
-        subtask_2 = create_observation_control_subtask_from_task_blueprint(cal_task_blueprint)
-
-        # assert the same subtask is returned
-        self.assertEqual(subtask_1, subtask_2)
-
-        # assert the calibrator obs was added as an additional beam
-        num_pointings_calibrator = len(subtask_2.specifications_doc['stations']['digital_pointings'])
-        self.assertEqual(num_pointings_target + 1, num_pointings_calibrator)
-
-        # assert the subtask is now in defined state
-        self.assertEqual("defined", str(subtask_2.state))
-        self.assertTrue(subtask_2.primary)
-
-        # assert the subtask references both tasks
-        self.assertEqual(subtask_1.task_blueprints.count(), 2)
-        self.assertIn(target_task_blueprint, subtask_1.task_blueprints.all())
-        self.assertIn(cal_task_blueprint, subtask_1.task_blueprints.all())
-
-        # assert we have subtask outputs for both tasks
-        self.assertEqual(subtask_1.outputs.count(), 2)
-        self.assertEqual(subtask_1.outputs.filter(task_blueprint=target_task_blueprint).count(), 1)
-        self.assertEqual(subtask_1.outputs.filter(task_blueprint=cal_task_blueprint).count(), 1)
-
-    def test_create_combined_subtask_from_task_blueprints_fails_if_calibrator_handled_before_target(self):
-        """
-        Create subtasks from a target task blueprint and a separate calibrator task blueprint.
-        Handling calibrator before target task should raise Exception.
-        """
-        cal_task_blueprint = create_task_blueprint_object_for_testing(task_template_name="calibrator observation")
-        target_task_blueprint = create_task_blueprint_object_for_testing()
-        create_scheduling_relation_task_blueprint_for_testing(cal_task_blueprint, target_task_blueprint, placement='parallel')
-
-        with self.assertRaises(SubtaskCreationException) as cm:
-            create_observation_control_subtask_from_task_blueprint(cal_task_blueprint)
-            create_observation_control_subtask_from_task_blueprint(target_task_blueprint)
-
-        self.assertIn("cannot be added to the target subtask, because it does not exist", str(cm.exception))
-
-    def test_create_combined_subtask_from_task_blueprints_fails_if_calibrator_does_not_fit(self):
-        """
-        Create subtasks from a target task blueprint and a separate calibrator task blueprint.
-        And exception is raised when the combined number of subbands exceeds 488.
-        """
-        cal_task_blueprint = create_task_blueprint_object_for_testing(task_template_name="calibrator observation")
-        target_task_blueprint = create_task_blueprint_object_for_testing()
-        create_scheduling_relation_task_blueprint_for_testing(cal_task_blueprint, target_task_blueprint, placement='parallel')
-
-        target_task_blueprint.specifications_doc['station_configuration']['SAPs'] = [{'name': 'target1', 'subbands': list(range(0, 150)),
-                                                             'digital_pointing': {'angle1': 0.1, 'angle2': 0.1,
-                                                                                  'direction_type': 'J2000',
-                                                                                  'target': 'target1'}},
-                                                            {'name': 'target2', 'subbands': list(range(150, 300)),
-                                                             'digital_pointing': {'angle1': 0.2, 'angle2': 0.2,
-                                                                                  'direction_type': 'J2000',
-                                                                                  'target': 'target2'}}]
-        target_task_blueprint.save()
-
-        with self.assertRaises(SubtaskCreationException) as cm:
-            create_observation_control_subtask_from_task_blueprint(target_task_blueprint)
-            create_observation_control_subtask_from_task_blueprint(cal_task_blueprint)
-
-        self.assertIn("results in 600 total subbands, but only 488 are possible", str(cm.exception))
-
-
 class SubTasksCreationFromTaskBluePrintCalibrator(unittest.TestCase):
 
     def test_create_sequence_of_subtask_from_task_blueprint_calibrator_failure(self):
diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/WeekView.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/WeekView.js
index 4419d0f6c8ccd33d930fe555e1ab1d047acbe2e8..91b41dd684a7bfdb1f9255ca259a9c0bba2fd0e8 100644
--- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/WeekView.js
+++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/WeekView.js
@@ -49,10 +49,7 @@ import {
     fetchUserPermissions,
 } from "./data/week.view.data";
 
-import useWebSocket from 'react-use-websocket';
-import _ from 'lodash';
-import ScheduleService from "../../services/schedule.service";
-import ReservationService from "../../services/reservation.service";
+import useWeekViewWebSocket from "../../utils/websocket.js";
 
 function getTimelineHeaders(headerSettings) {
     return <TimelineHeaders className="sticky">
@@ -260,138 +257,12 @@ export default function WeekView() {
             setWeekString( newStart, newEnd);
          
     }
-    // websocket handling
-
-    /**
-     * Function to call wnen websocket is connected
-     */
-    function onConnect() {
-        try {
-            console.log("WS Opened");
-            const userDets = localStorage.getItem("user");
-            if (userDets) {
-                sendMessage(JSON.stringify({ "token": JSON.parse(userDets).websocket_token }));
-                console.log("Auth token submitted");
-            }
-        } catch (err) {
-            console.log('err', err)
-        }
-    }
 
-    /**
-     * Function to call when websocket is disconnected
-     */
-    function onDisconnect() {
-        console.log("WS Closed")
-    }
+    // websocket handling
 
-    /**
-     * Handles the message received through websocket
-     * @param {String} data - String of JSON data
-     */
-    function handleData(event) {
-        const jsonData = JSON.parse(event?.data) || {};
-        switch (jsonData.object_type) {
-            case 'scheduling_unit_blueprint': {
-                switch (jsonData.action) {
-                    case 'delete': {
-                        const schedulingUnits = data.schedulingUnits
-                        _.remove(schedulingUnits, function (su) { return su.id === jsonData.object_details.id });
-                        setData(prevData => ({
-                            ...prevData,
-                            schedulingUnits: schedulingUnits
-                        }))
-                        if (summaryItem?.id === jsonData.object_details.id) {
-                            setShowSummary(false);
-                        }
-                        break;
-                    }
-                    case 'update': {
-                        setData(prevData => ({
-                            ...prevData,
-                            schedulingUnits: prevData.schedulingUnits.map(
-                                unit => unit.id === jsonData.object_details.id ? { ...unit, ...jsonData.object_details } : unit
-                            )
-                        }));
-                        if (summaryItem?.id === jsonData.object_details.id) {
-                            // Note: we could also update details directly so we don't need to fetch again.
-                            // However, we would have to send full deltas via websocket instead of the subset that is
-                            // relevant for the timeline (like id, timestamps, and status).
-                            // e.g.
-                            //
-                            // setSummarySettings(prevState => ({
-                            //    ...prevState,
-                            //    schedulingUnitItem: {...prevState.schedulingUnitItem, ...jsonData.object_details}
-                            // }));
-                            //
-                            // Instead, trigger a full refresh of the details panel:
-                            setSummaryItem({ id: jsonData.object_details.id, type: "SCHEDULE" });
-                        }
-                        break;
-                    }
-                    case 'create': {
-                        // The websocket message only contains a subset of the details we need, so fetch the full set
-                        ScheduleService.getTimelineSlimBlueprints(undefined, undefined, jsonData.object_details.id)   // todo: check time
-                            .then((response) => {
-                                setData(prevData => ({
-                                    ...prevData,
-                                    schedulingUnits: prevData.schedulingUnits.concat(response)
-                                }));
-                            });
-                        break;
-                    }
-                    default: { break; }
-                }
-                break;
-            }
-            case 'reservation': {
-                switch (jsonData.action) {
-                    case 'delete': {
-                        const reservations = data.reservations
-                        _.remove(reservations, function (res) { return res.id === jsonData.object_details.id });
-                        setData(prevData => ({
-                            ...prevData,
-                            reservations: reservations
-                        }))
-                        if (summaryItem?.id === jsonData.object_details.id) {
-                            setShowSummary(false);
-                        }
-                        break;
-                    }
-                    case 'update': {
-                        setData(prevData => ({
-                            ...prevData,
-                            reservations: prevData.reservations.map(
-                                res => res.id === jsonData.object_details.id ? { ...res, ...jsonData.object_details } : res
-                            ),
-                        }));
-                        if (summaryItem?.id === jsonData.object_details.id) {
-                            // Trigger a full refresh of the details panel
-                            setSummaryItem({ id: jsonData.object_details.id, type: "RESERVATION" });
-                        }
-                        break;
-                    }
-                    case 'create': {
-                        const shouldFetchReservations = getStore(UIConstants.STORE_KEY_TIMELINE).reservationsToggle;
-                        if (shouldFetchReservations) {
-                            // The websocket message only contains a subset of the details we need, so fetch the full set
-                            ReservationService.getTimelineReservations(undefined, undefined, jsonData.object_details.id)  // todo: check time
-                                .then((response) => {
-                                    setData(prevData => ({
-                                        ...prevData,
-                                        reservations: prevData.reservations.concat(response)
-                                    }));
-                                });
-                        }
-                        break;
-                    }
-                    default: { break; }
-                }
-                break;
-            }
-            default: { break; }
-        }
-    }
+    // websocket hook that opens and allows interaction via the wss connection
+    // todo: there is probably a better way to access the component state from the hook?
+    useWeekViewWebSocket(data, setData, summaryItem, setSummaryItem, setShowSummary);
 
 
     /**
@@ -406,23 +277,6 @@ export default function WeekView() {
         setShowLegendbar(newState);
     }
 
-
-    // websocket hook that opens and allows interaction via the wss connection
-    const {
-        sendMessage,
-        sendJsonMessage,
-        lastMessage,
-        lastJsonMessage,
-        readyState,
-        getWebSocket,
-    } = useWebSocket(process.env.REACT_APP_WEBSOCKET_URL, {
-        onOpen: () => onConnect(),
-        onClose: () => onDisconnect(),
-        onMessage: (event) => handleData(event),
-        onError: (event) => { console.error(event); },
-        shouldReconnect: (closeEvent) => true,
-    });
-
     return <div>
         <div className={(isLoading ? "fix-element" : "hide-element")}>
             <ProgressBar className={isLoading ? "" : "hide-element"} mode="indeterminate"
diff --git a/SAS/TMSS/frontend/tmss_webapp/src/utils/websocket.js b/SAS/TMSS/frontend/tmss_webapp/src/utils/websocket.js
new file mode 100644
index 0000000000000000000000000000000000000000..2c47fff3df0862d238779274641ca8b965eb2b80
--- /dev/null
+++ b/SAS/TMSS/frontend/tmss_webapp/src/utils/websocket.js
@@ -0,0 +1,183 @@
+import _ from 'lodash';
+import ScheduleService from "../services/schedule.service";
+import ReservationService from "../services/reservation.service";
+import useWebSocket from 'react-use-websocket';
+import { getStore } from "../services/store.helper";
+import UIConstants from "../utils/ui.constants";
+
+function useWeekViewWebSocket(data, setData, summaryItem, setSummaryItem, setShowSummary) {
+
+    /**
+     * Function to call when websocket is connected
+     */
+    function onConnect() {
+        try {
+            console.log("WS Opened");
+            const userDets = localStorage.getItem("user");
+            if (userDets) {
+                sendMessage(JSON.stringify({ "token": JSON.parse(userDets).websocket_token }));
+                console.log("Auth token submitted");
+            }
+        } catch (err) {
+            console.log('err', err);
+        }
+    }
+
+    /**
+     * Function to call when websocket is disconnected
+     */
+    function onDisconnect() {
+        console.log("WS Closed");
+    }
+
+    function fetchBlueprintAndAddToTimeline(id) {
+        // The websocket message only contains a subset of the details we need, so fetch the full set
+        ScheduleService.getTimelineSlimBlueprints(undefined, undefined, id)   // todo: check time
+                       .then((response) => {
+                           setData(prevData => ({
+                               ...prevData,
+                               schedulingUnits: prevData.schedulingUnits.concat(response)
+                           }));
+                       })
+                       .catch(e => console.error("Couldn't retrieve scheduling unit details for id: ", id, e));
+    }
+
+
+    function fetchReservationAndAddToTimeline(id) {
+        const shouldFetchReservations = getStore(UIConstants.STORE_KEY_TIMELINE).reservationsToggle;
+        if (shouldFetchReservations) {
+            ReservationService.getTimelineReservations(undefined, undefined, id)  // todo: check time
+                              .then((response) => {
+                                  setData(prevData => ({
+                                  ...prevData,
+                                  reservations: prevData.reservations.concat(response)
+                                  }));
+                              })
+                              .catch(e => console.error("Couldn't retrieve reservation details for id: ", id, e));
+        }
+    }
+
+    /**
+     * Handles the message received through websocket
+     * @param {String} data - String of JSON data
+     */
+    function handleData(event) {
+        const jsonData = JSON.parse(event?.data) || {};
+        switch (jsonData.object_type) {
+            case 'scheduling_unit_blueprint': {
+                switch (jsonData.action) {
+                    case 'delete': {
+                        const schedulingUnits = data.schedulingUnits
+                        _.remove(schedulingUnits, function (su) { return su.id === jsonData.object_details.id });
+                        setData(prevData => ({
+                            ...prevData,
+                            schedulingUnits: schedulingUnits
+                        }))
+                        if (summaryItem?.id === jsonData.object_details.id) {
+                            setShowSummary(false);
+                        }
+                        break;
+                    }
+                    case 'update': {
+                        if (data.schedulingUnits.some(unit => unit.id === jsonData.object_details.id)) {
+                            // usually we already have most details, so only update the relevant ones.
+                            setData(prevData => ({
+                                ...prevData,
+                                schedulingUnits: prevData.schedulingUnits.map(
+                                    unit => unit.id === jsonData.object_details.id ? { ...unit, ...jsonData.object_details } : unit
+                                )
+                            }));
+                        } else {
+                            // ...but sometimes we don't have the details yet, e.g. because it at least used to be
+                            // outside the timelines time range, and we need to fetch all details so we don't miss
+                            // anything that e.g. got moved into scope.
+                            fetchBlueprintAndAddToTimeline(jsonData.object_details.id);
+                        }
+
+                        if (summaryItem?.id === jsonData.object_details.id) {
+                            // Note: we could also update details directly so we don't need to fetch again.
+                            // However, we would have to send full deltas via websocket instead of the subset that is
+                            // relevant for the timeline (like id, timestamps, and status).
+                            // e.g.
+                            //
+                            // setSummarySettings(prevState => ({
+                            //    ...prevState,
+                            //    schedulingUnitItem: {...prevState.schedulingUnitItem, ...jsonData.object_details}
+                            // }));
+                            //
+                            // Instead, trigger a full refresh of the details panel:
+                            setSummaryItem({ id: jsonData.object_details.id, type: "SCHEDULE" });
+                        }
+                        break;
+                    }
+                    case 'create': {
+                        // The websocket message only contains a subset of the details we need, so fetch the full set
+                        fetchBlueprintAndAddToTimeline(jsonData.object_details.id);
+                        break;
+                    }
+                    default: { break; }
+                }
+                break;
+            }
+            case 'reservation': {
+                switch (jsonData.action) {
+                    case 'delete': {
+                        const reservations = data.reservations
+                        _.remove(reservations, function (res) { return res.id === jsonData.object_details.id });
+                        setData(prevData => ({
+                            ...prevData,
+                            reservations: reservations
+                        }))
+                        if (summaryItem?.id === jsonData.object_details.id) {
+                            setShowSummary(false);
+                        }
+                        break;
+                    }
+                    case 'update': {
+                        if (data.reservations.some(res => res.id === jsonData.object_details.id)) {
+                            // usually we already have most details, so only update the relevant ones.
+                            setData(prevData => ({
+                                ...prevData,
+                                reservations: prevData.reservations.map(
+                                    res => res.id === jsonData.object_details.id ? { ...res, ...jsonData.object_details } : res
+                                ),
+                            }));
+                        } else {
+                            // ...but sometimes we don't have the details yet, e.g. because it at least used to be
+                            // outside the timelines time range, and we need to fetch all details so we don't miss
+                            // anything that e.g. got moved into scope.
+                            fetchReservationAndAddToTimeline(jsonData.object_details.id);
+                        }
+                        if (summaryItem?.id === jsonData.object_details.id) {
+                            // Trigger a full refresh of the details panel
+                            setSummaryItem({ id: jsonData.object_details.id, type: "RESERVATION" });
+                        }
+                        break;
+                    }
+                    case 'create': {
+                        // The websocket message only contains a subset of the details we need, so fetch the full set
+                        fetchReservationAndAddToTimeline(jsonData.object_details.id);
+                        break;
+                    }
+                    default: { break; }
+                }
+                break;
+            }
+            default: { break; }
+        }
+    }
+
+    // websocket hook that opens and allows interaction via the wss connection
+    const {
+        sendMessage
+    } = useWebSocket(process.env.REACT_APP_WEBSOCKET_URL, {
+        onOpen: () => onConnect(),
+        onClose: () => onDisconnect(),
+        onMessage: (event) => handleData(event),
+        onError: (event) => { console.error(event); },
+        shouldReconnect: (closeEvent) => true,
+    });
+
+}
+
+export default useWeekViewWebSocket;
\ No newline at end of file