From 5acf77582faea16e31363d571ad757874ddf9028 Mon Sep 17 00:00:00 2001 From: regeter <2320305+regeter@users.noreply.github.com> Date: Thu, 10 Apr 2025 09:34:20 -0700 Subject: [PATCH 1/5] fix: Deepcopy of lastKnownState and cleanup --- src/TripLogs.js | 103 ++++++++++++------------ src/TripLogs.test.js | 184 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 238 insertions(+), 49 deletions(-) diff --git a/src/TripLogs.js b/src/TripLogs.js index 73acda8d..06bae5a8 100644 --- a/src/TripLogs.js +++ b/src/TripLogs.js @@ -48,9 +48,9 @@ function processApiCall(origLog) { if (apiType && apiType.match(regex)) { return { api: api, - request: origLog.jsonpayload.request, - response: origLog.jsonpayload.response, - error: origLog.jsonpayload.errorresponse, + request: _.cloneDeep(origLog.jsonpayload.request), + response: _.cloneDeep(origLog.jsonpayload.response), + error: _.cloneDeep(origLog.jsonpayload.errorresponse), }; } } @@ -61,9 +61,9 @@ function processApiCall(origLog) { if (key.match(regex)) { return { api: api, - request: origLog[key].request, - response: origLog[key].response, - error: origLog[key].errorresponse, + request: _.cloneDeep(origLog[key].request), + response: _.cloneDeep(origLog[key].response), + error: _.cloneDeep(origLog[key].errorresponse), }; } } @@ -120,6 +120,7 @@ function adjustFieldFormat(log, origPath, newPath, stringToTrim) { function processRawLogs(rawLogs, solutionType) { log(`Processing ${rawLogs.length} raw logs for ${solutionType}`); + const vehiclePath = solutionType === "LMFS" ? "request.deliveryvehicle" : "request.vehicle"; const origLogs = rawLogs.map(toLowerKeys); const isReversed = origLogs.length > 1 && new Date(origLogs[0].timestamp) > new Date(origLogs[origLogs.length - 1].timestamp); @@ -135,8 +136,6 @@ function processRawLogs(rawLogs, solutionType) { routeSegmentTraffic: null, }; - const vehiclePath = solutionType === "LMFS" ? "request.deliveryvehicle" : "request.vehicle"; - for (let idx = 0; idx < sortedLogs.length; idx++) { const origLog = sortedLogs[idx]; const newLog = {}; @@ -155,35 +154,64 @@ function processRawLogs(rawLogs, solutionType) { adjustFieldFormats(solutionType, newLog); - // Update last known location, heading, traffic + // Get current data from the API call const currentLocation = _.get(newLog, `${vehiclePath}.lastlocation`); const currentRouteSegment = _.get(newLog, `${vehiclePath}.currentroutesegment`); const currentRouteSegmentTraffic = _.get(newLog, `${vehiclePath}.currentroutesegmenttraffic`); + const navigationStatus = _.get(newLog, `${vehiclePath}.navstatus`); - if (currentLocation?.rawlocation) { - lastKnownState.location = currentLocation.rawlocation; - lastKnownState.heading = currentLocation.heading ?? lastKnownState.heading; + // Create lastlocation object using deep cloned data where available + newLog.lastlocation = currentLocation ? _.cloneDeep(currentLocation) : {}; + + // Apply last known location if needed + if (!newLog.lastlocation.location && lastKnownState.location) { + newLog.lastlocation.location = _.cloneDeep(lastKnownState.location); + newLog.lastlocation.heading = lastKnownState.heading; } - // If Navigation SDK is NO_GUIDANCE, reset the lastKnownState planned route and traffic. - if (_.get(newLog, `${vehiclePath}.navstatus`) == "NAVIGATION_STATUS_NO_GUIDANCE") { + // If Navigation SDK is NO_GUIDANCE, reset the lastKnownState planned route and traffic + if (navigationStatus === "NAVIGATION_STATUS_NO_GUIDANCE") { lastKnownState.routeSegment = null; lastKnownState.routeSegmentTraffic = null; - } else if (currentRouteSegment) { - lastKnownState.routeSegment = currentRouteSegment; - lastKnownState.routeSegmentTraffic = currentRouteSegmentTraffic; } + // Update lastKnownState with current route if available + else if (currentRouteSegment) { + lastKnownState.routeSegment = _.cloneDeep(currentRouteSegment); + lastKnownState.routeSegmentTraffic = _.cloneDeep(currentRouteSegmentTraffic); + + // Add current route segment to lastlocation + newLog.lastlocation.currentroutesegment = _.cloneDeep(currentRouteSegment); + if (currentRouteSegmentTraffic) { + newLog.lastlocation.currentroutesegmenttraffic = _.cloneDeep(currentRouteSegmentTraffic); + } + } + // Apply last known route segment if no current route segment and we have stored one + else if (lastKnownState.routeSegment) { + newLog.lastlocation.currentroutesegment = _.cloneDeep(lastKnownState.routeSegment); + if (lastKnownState.routeSegmentTraffic) { + newLog.lastlocation.currentroutesegmenttraffic = _.cloneDeep(lastKnownState.routeSegmentTraffic); + } + } + + // Create other synthetic fields needed for the app + // For calculations of server/client time deltas + newLog.lastlocationResponse = _.get(newLog, "response.lastlocation") + ? _.cloneDeep(_.get(newLog, "response.lastlocation")) + : null; - // Apply last known state to a log entry - const basePath = `${vehiclePath}.lastlocation`; - if (!_.get(newLog, `${basePath}.rawlocation`) && lastKnownState.location) { - _.set(newLog, `${basePath}.rawlocation`, lastKnownState.location); - _.set(newLog, `${basePath}.heading`, lastKnownState.heading); + // Navigation status (use response because it's typically only in the request when it changes) + newLog.navStatus = _.get(newLog, "response.navigationstatus"); + newLog.error = _.get(newLog, "error.message"); + + // Sort currentTrips array since sometimes it could contain multiple trip ids in random order + if (_.get(newLog, "response.currenttrips")) { + newLog.response.currenttrips.sort(); } - if (!_.get(newLog, `${vehiclePath}.currentroutesegment`) && lastKnownState.routeSegment) { - _.set(newLog, `${basePath}.currentroutesegment`, lastKnownState.routeSegment); - _.set(newLog, `${basePath}.currentroutesegmenttraffic`, lastKnownState.routeSegmentTraffic); + // Update lastKnownState for next iterations + if (currentLocation?.location) { + lastKnownState.location = _.cloneDeep(currentLocation.location); + lastKnownState.heading = currentLocation.heading ?? lastKnownState.heading; } newLogs.push(newLog); @@ -212,29 +240,6 @@ class TripLogs { this.dwellLocations = []; this.etaDeltas = []; - const lastLocationPath = this.vehiclePath + ".lastlocation"; - _.map(this.rawLogs, (le) => { - // "synthetic" entries that hides some of the differences - // between lmfs & odrd log entries - le.lastlocation = _.get(le, lastLocationPath); - - // utilized for calculations of serve/client time deltas (where the - // server time isn't populated in the request). - le.lastlocationResponse = _.get(le, "response.lastlocation"); - - // use the response because nav status is typically only - // in the request when it changes ... and visualizations - // make more sense when the nav status can be shown along the route - le.navStatus = _.get(le, "response.navigationstatus"); - le.error = _.get(le, "error.message"); - - // Sort currentTrips array since sometimes it could contain multiple trip ids - // but in a random order - if (_.get(le, "response.currenttrips")) { - le.response.currenttrips.sort(); - } - }); - if (this.rawLogs.length > 0) { this.minDate = this.rawLogs[0].date; this.maxDate = _.last(this.rawLogs).date; @@ -495,7 +500,7 @@ class TripLogs { curTripData.updateRequests++; } const lastLocation = le.lastlocation; - if (lastLocation && lastLocation.rawlocation) { + if (lastLocation && lastLocation.location) { curTripData.appendCoords(lastLocation, le.timestamp); } } diff --git a/src/TripLogs.test.js b/src/TripLogs.test.js index 51b80dd5..955d9173 100644 --- a/src/TripLogs.test.js +++ b/src/TripLogs.test.js @@ -2,6 +2,7 @@ import TripLogs from "./TripLogs"; import fs from "fs"; +import _ from "lodash"; async function loadTripLogs(dataset) { const parsedData = JSON.parse(fs.readFileSync(dataset)); @@ -39,3 +40,186 @@ test("basic lmfs trip log loading", async () => { "Stops Left 3", ]); }); + +test("lastKnownState location is correctly applied to subsequent logs", () => { + // Create mock data with two log entries + const mockLogs = [ + { + timestamp: "2023-01-01T10:00:00Z", + jsonpayload: { + "@type": "updateVehicle", + request: { + vehicle: { + lastlocation: { + location: { latitude: 37.7749, longitude: -122.4194 }, + heading: 90 + } + } + }, + response: {} + } + }, + { + timestamp: "2023-01-01T10:01:00Z", + jsonpayload: { + "@type": "updateVehicle", + request: { + vehicle: {} + }, + response: {} + } + } + ]; + + const tripLogs = new TripLogs(mockLogs, "ODRD"); + + // Check that the second log entry received the lastKnownState from the first + expect(tripLogs.rawLogs[1].lastlocation.location).toEqual({ latitude: 37.7749, longitude: -122.4194 }); + expect(tripLogs.rawLogs[1].lastlocation.heading).toBe(90); +}); + +test("request and response objects are not mutated", () => { + // Create a log with location data + const originalLocation = { latitude: 37.7749, longitude: -122.4194 }; + const mockLog = { + timestamp: "2023-01-01T10:00:00Z", + jsonpayload: { + "@type": "updateVehicle", + request: { + vehicle: { + lastlocation: { + location: originalLocation, + heading: 90 + } + } + }, + response: {} + } + }; + + const tripLogs = new TripLogs([mockLog], "ODRD"); + + // Verify the original request object was not modified + expect(tripLogs.rawLogs[0].request.vehicle.lastlocation.location).not.toBe(originalLocation); + + // But the data is still equal + expect(tripLogs.rawLogs[0].request.vehicle.lastlocation.location).toEqual(originalLocation); + + // Modify the synthetic field + tripLogs.rawLogs[0].lastlocation.location.latitude = 38.0; + + // Verify the original was not affected + expect(tripLogs.rawLogs[0].request.vehicle.lastlocation.location).toEqual(originalLocation); +}); + +test("lastKnownState route segments are reset with NO_GUIDANCE", () => { + // Create mock data with three log entries + const mockLogs = [ + { + timestamp: "2023-01-01T10:00:00Z", + jsonpayload: { + "@type": "updateVehicle", + request: { + vehicle: { + currentroutesegment: { id: "segment1", path: [{ latitude: 37.7, longitude: -122.4 }] }, + currentroutesegmenttraffic: { speed: 30 } + } + }, + response: {} + } + }, + { + timestamp: "2023-01-01T10:01:00Z", + jsonpayload: { + "@type": "updateVehicle", + request: { + vehicle: { + navstatus: "NAVIGATION_STATUS_NO_GUIDANCE" + } + }, + response: {} + } + }, + { + timestamp: "2023-01-01T10:02:00Z", + jsonpayload: { + "@type": "updateVehicle", + request: { + vehicle: {} + }, + response: {} + } + } + ]; + + const tripLogs = new TripLogs(mockLogs, "ODRD"); + + // First log should have the route segment + expect(tripLogs.rawLogs[0].lastlocation.currentroutesegment).toBeDefined(); + + // Second log sets navstatus to NO_GUIDANCE, so lastKnownState should be reset + expect(tripLogs.rawLogs[1].lastlocation.currentroutesegment).not.toBeDefined(); + + // Third log should not have route segment since it was reset + expect(tripLogs.rawLogs[2].lastlocation.currentroutesegment).not.toBeDefined(); +}); + +test("lastKnownState is properly propagated through a sequence of logs", () => { + // Create a sequence of logs with varying data + const mockLogs = [ + { + // Log 1: Has location but no route + timestamp: "2023-01-01T10:00:00Z", + jsonpayload: { + "@type": "updateVehicle", + request: { + vehicle: { + lastlocation: { + location: { latitude: 37.7749, longitude: -122.4194 }, + heading: 90 + } + } + }, + response: {} + } + }, + { + // Log 2: Has route but no location + timestamp: "2023-01-01T10:01:00Z", + jsonpayload: { + "@type": "updateVehicle", + request: { + vehicle: { + currentroutesegment: { id: "segment1", path: [{ latitude: 37.7, longitude: -122.4 }] } + } + }, + response: {} + } + }, + { + // Log 3: Has neither location nor route + timestamp: "2023-01-01T10:02:00Z", + jsonpayload: { + "@type": "updateVehicle", + request: { + vehicle: {} + }, + response: {} + } + } + ]; + + const tripLogs = new TripLogs(mockLogs, "ODRD"); + + // Log 1: Should have location but no route + expect(tripLogs.rawLogs[0].lastlocation.location).toEqual({ latitude: 37.7749, longitude: -122.4194 }); + expect(tripLogs.rawLogs[0].lastlocation.currentroutesegment).not.toBeDefined(); + + // Log 2: Should have location from Log 1 and its own route + expect(tripLogs.rawLogs[1].lastlocation.location).toEqual({ latitude: 37.7749, longitude: -122.4194 }); + expect(tripLogs.rawLogs[1].lastlocation.currentroutesegment).toBeDefined(); + + // Log 3: Should have both location from Log 1 and route from Log 2 + expect(tripLogs.rawLogs[2].lastlocation.location).toEqual({ latitude: 37.7749, longitude: -122.4194 }); + expect(tripLogs.rawLogs[2].lastlocation.currentroutesegment).toBeDefined(); +}); From b6bde38de09ebc06c35cdaee3175ed704d9c720e Mon Sep 17 00:00:00 2001 From: regeter <2320305+regeter@users.noreply.github.com> Date: Thu, 10 Apr 2025 09:36:19 -0700 Subject: [PATCH 2/5] feat: visualize getTrip and use location instead of rawLocation --- src/Map.js | 80 +++++++++++++++++++++++++++++++----------- src/TrafficPolyline.js | 30 +++++++++++++++- src/Trip.js | 4 +-- src/TripObjects.js | 1 - 4 files changed, 91 insertions(+), 24 deletions(-) diff --git a/src/Map.js b/src/Map.js index 809a27d4..f25c1d89 100644 --- a/src/Map.js +++ b/src/Map.js @@ -133,11 +133,6 @@ function MyMapComponent(props) { map.setHeading(parseInt(urlHeading)); } map.setOptions({ maxZoom: 100 }); - map.addListener("zoom_changed", () => { - setQueryStringValue("zoom", map.getZoom()); - setIsFollowingVehicle(false); // zoom disables following - log("Follow mode disabled due to zoom change"); - }); map.addListener("heading_changed", () => { setQueryStringValue("heading", map.getHeading()); @@ -318,7 +313,35 @@ function MyMapComponent(props) { // Update the polylines state to remove all route segments setPolylines(polylines.filter((p) => !p.isRouteSegment)); - // Get the current route segment from the selected row + const eventType = props.selectedRow["@type"]; + const isTripEvent = ["getTrip", "updateTrip", "createTrip"].includes(eventType); + + if (isTripEvent) { + // Create a thin red polyline with arrows for trip events + const routeSegment = _.get(props.selectedRow, "response.currentroutesegment"); + if (routeSegment) { + try { + const decodedPoints = decode(routeSegment); + if (decodedPoints && decodedPoints.length > 0) { + const validWaypoints = decodedPoints.map((point) => ({ + lat: point.latDegrees(), + lng: point.lngDegrees(), + })); + + const trafficPolyline = new TrafficPolyline({ + path: validWaypoints, + zIndex: 3, + isTripEvent: true, + map: map, + }); + setPolylines((prev) => [...prev, ...trafficPolyline.polylines]); + } + } catch (error) { + console.error("Error processing trip event polyline:", error); + } + } + } + const routeSegment = _.get(props.selectedRow, "request.vehicle.currentroutesegment") || _.get(props.selectedRow, "lastlocation.currentroutesegment"); @@ -337,23 +360,19 @@ function MyMapComponent(props) { _.get(props.selectedRow, "request.vehicle.currentroutesegmenttraffic.trafficrendering") || _.get(props.selectedRow, "lastlocation.currentroutesegmenttraffic.trafficrendering"); - const rawLocation = _.get(props.selectedRow.lastlocation, "rawlocation"); + const location = _.get(props.selectedRow.lastlocation, "location"); const trafficPolyline = new TrafficPolyline({ path: validWaypoints, zIndex: 2, trafficRendering: structuredClone(trafficRendering), - currentLatLng: rawLocation, + currentLatLng: location, map: map, }); setPolylines((prev) => [...prev, ...trafficPolyline.polylines]); } } catch (error) { - console.error("Error processing route segment polyline:", { - error, - routeSegment, - rowData: props.selectedRow, - }); + console.error("Error processing route segment polyline:", error); } } }, [props.selectedRow]); @@ -384,17 +403,25 @@ function MyMapComponent(props) { strokeWeight: 1, rotation: 0, }, + rawLocation: { + path: google.maps.SymbolPath.CIRCLE, + fillColor: "#FF0000", + fillOpacity: 1, + scale: 2, + strokeColor: "#FF0000", + strokeWeight: 1, + }, }; - const rawLocation = _.get(data.lastlocation, "rawlocation"); - if (rawLocation) { - lastValidPositionRef.current = { lat: rawLocation.latitude, lng: rawLocation.longitude }; + const location = _.get(data.lastlocation, "location") || _.get(data.lastlocationResponse, "location"); + if (location) { + lastValidPositionRef.current = { lat: location.latitude, lng: location.longitude }; - const heading = _.get(data.lastlocation, "heading") || 0; + const heading = _.get(data.lastlocation, "heading") || _.get(data.lastlocationResponse, "heading") || 0; markerSymbols.chevron.rotation = heading; const backgroundMarker = new window.google.maps.Marker({ - position: { lat: rawLocation.latitude, lng: rawLocation.longitude }, + position: { lat: location.latitude, lng: location.longitude }, map: map, icon: markerSymbols.background, clickable: false, @@ -402,7 +429,7 @@ function MyMapComponent(props) { }); const chevronMarker = new window.google.maps.Marker({ - position: { lat: rawLocation.latitude, lng: rawLocation.longitude }, + position: { lat: location.latitude, lng: location.longitude }, map: map, icon: markerSymbols.chevron, zIndex: 10, @@ -410,8 +437,21 @@ function MyMapComponent(props) { dataMakers.push(backgroundMarker, chevronMarker); + const rawLocation = _.get(data.lastlocation, "rawlocation"); + if (rawLocation) { + const rawLocationMarker = new window.google.maps.Marker({ + position: { lat: rawLocation.latitude, lng: rawLocation.longitude }, + map: map, + icon: markerSymbols.rawLocation, + zIndex: 8, + clickable: false, + }); + + dataMakers.push(rawLocationMarker); + } + if (isFollowingVehicle) { - map.setCenter({ lat: rawLocation.latitude, lng: rawLocation.longitude }); + map.setCenter({ lat: location.latitude, lng: location.longitude }); } } }, [props.selectedRow, isFollowingVehicle]); diff --git a/src/TrafficPolyline.js b/src/TrafficPolyline.js index ca334dad..1fbab91f 100644 --- a/src/TrafficPolyline.js +++ b/src/TrafficPolyline.js @@ -15,10 +15,11 @@ export const TRAFFIC_COLORS = { }; export class TrafficPolyline { - constructor({ path, zIndex, trafficRendering, currentLatLng, map }) { + constructor({ path, zIndex, isTripEvent, trafficRendering, currentLatLng, map }) { this.polylines = []; this.path = path; this.zIndex = zIndex; + this.isTripEvent = isTripEvent; this.currentLatLng = currentLatLng; this.map = map; this.segments = this.calculateSegments(trafficRendering); @@ -142,6 +143,33 @@ export class TrafficPolyline { } createPolylines() { + if (this.isTripEvent) { + const polyline = new google.maps.Polyline({ + path: this.path, + geodesic: true, + strokeColor: "#000000", + strokeOpacity: 1, + strokeWeight: 1.5, + zIndex: this.zIndex || 3, + isRouteSegment: true, + icons: [ + { + icon: { + path: google.maps.SymbolPath.FORWARD_OPEN_ARROW, + scale: 1.5, + strokeWeight: 1, + }, + offset: "50%", + repeat: "100px", + }, + ], + map: this.map, + clickable: false, + }); + this.polylines.push(polyline); + return; + } + this.segments.forEach((segment) => { const polyline = new google.maps.Polyline({ path: segment.path, diff --git a/src/Trip.js b/src/Trip.js index 8fe3d88c..03f42c8f 100644 --- a/src/Trip.js +++ b/src/Trip.js @@ -55,8 +55,8 @@ class Trip { // or synthesize pathCoords on the fly? appendCoords(lastLocation, timestamp) { this.pathCoords.push({ - lat: lastLocation.rawlocation.latitude, - lng: lastLocation.rawlocation.longitude, + lat: lastLocation.location.latitude, + lng: lastLocation.location.longitude, trip_id: this.tripName, date: new Date(timestamp), }); diff --git a/src/TripObjects.js b/src/TripObjects.js index 2fb6170f..70d9c44b 100644 --- a/src/TripObjects.js +++ b/src/TripObjects.js @@ -145,7 +145,6 @@ export class TripObjects { path.setOptions({ strokeOpacity: 0.5, strokeWeight: 6 }); }); - this.paths.set(tripId, path); } From ef8e251eaa4417bb2956c5102ef6ce35f52216b3 Mon Sep 17 00:00:00 2001 From: regeter <2320305+regeter@users.noreply.github.com> Date: Fri, 11 Apr 2025 11:18:57 -0700 Subject: [PATCH 3/5] fix: small fix NO_GUIDANCE difference between log formats --- src/TripLogs.js | 8 ++-- src/TripLogs.test.js | 100 +++++++++++++++++++++---------------------- 2 files changed, 54 insertions(+), 54 deletions(-) diff --git a/src/TripLogs.js b/src/TripLogs.js index 06bae5a8..6752be4d 100644 --- a/src/TripLogs.js +++ b/src/TripLogs.js @@ -158,7 +158,9 @@ function processRawLogs(rawLogs, solutionType) { const currentLocation = _.get(newLog, `${vehiclePath}.lastlocation`); const currentRouteSegment = _.get(newLog, `${vehiclePath}.currentroutesegment`); const currentRouteSegmentTraffic = _.get(newLog, `${vehiclePath}.currentroutesegmenttraffic`); - const navigationStatus = _.get(newLog, `${vehiclePath}.navstatus`); + + // Navigation status (fallback to response because it's typically only in the request when it changes) + newLog.navStatus = _.get(newLog, `${vehiclePath}.navstatus`) || _.get(newLog, "response.navigationstatus"); // Create lastlocation object using deep cloned data where available newLog.lastlocation = currentLocation ? _.cloneDeep(currentLocation) : {}; @@ -170,7 +172,7 @@ function processRawLogs(rawLogs, solutionType) { } // If Navigation SDK is NO_GUIDANCE, reset the lastKnownState planned route and traffic - if (navigationStatus === "NAVIGATION_STATUS_NO_GUIDANCE") { + if (typeof newLog.navStatus === "string" && newLog.navStatus.endsWith("NO_GUIDANCE")) { lastKnownState.routeSegment = null; lastKnownState.routeSegmentTraffic = null; } @@ -199,8 +201,6 @@ function processRawLogs(rawLogs, solutionType) { ? _.cloneDeep(_.get(newLog, "response.lastlocation")) : null; - // Navigation status (use response because it's typically only in the request when it changes) - newLog.navStatus = _.get(newLog, "response.navigationstatus"); newLog.error = _.get(newLog, "error.message"); // Sort currentTrips array since sometimes it could contain multiple trip ids in random order diff --git a/src/TripLogs.test.js b/src/TripLogs.test.js index 955d9173..33d5f9f8 100644 --- a/src/TripLogs.test.js +++ b/src/TripLogs.test.js @@ -52,27 +52,27 @@ test("lastKnownState location is correctly applied to subsequent logs", () => { vehicle: { lastlocation: { location: { latitude: 37.7749, longitude: -122.4194 }, - heading: 90 - } - } + heading: 90, + }, + }, }, - response: {} - } + response: {}, + }, }, { timestamp: "2023-01-01T10:01:00Z", jsonpayload: { "@type": "updateVehicle", request: { - vehicle: {} + vehicle: {}, }, - response: {} - } - } + response: {}, + }, + }, ]; const tripLogs = new TripLogs(mockLogs, "ODRD"); - + // Check that the second log entry received the lastKnownState from the first expect(tripLogs.rawLogs[1].lastlocation.location).toEqual({ latitude: 37.7749, longitude: -122.4194 }); expect(tripLogs.rawLogs[1].lastlocation.heading).toBe(90); @@ -89,25 +89,25 @@ test("request and response objects are not mutated", () => { vehicle: { lastlocation: { location: originalLocation, - heading: 90 - } - } + heading: 90, + }, + }, }, - response: {} - } + response: {}, + }, }; const tripLogs = new TripLogs([mockLog], "ODRD"); - + // Verify the original request object was not modified expect(tripLogs.rawLogs[0].request.vehicle.lastlocation.location).not.toBe(originalLocation); - + // But the data is still equal expect(tripLogs.rawLogs[0].request.vehicle.lastlocation.location).toEqual(originalLocation); - + // Modify the synthetic field tripLogs.rawLogs[0].lastlocation.location.latitude = 38.0; - + // Verify the original was not affected expect(tripLogs.rawLogs[0].request.vehicle.lastlocation.location).toEqual(originalLocation); }); @@ -122,11 +122,11 @@ test("lastKnownState route segments are reset with NO_GUIDANCE", () => { request: { vehicle: { currentroutesegment: { id: "segment1", path: [{ latitude: 37.7, longitude: -122.4 }] }, - currentroutesegmenttraffic: { speed: 30 } - } + currentroutesegmenttraffic: { speed: 30 }, + }, }, - response: {} - } + response: {}, + }, }, { timestamp: "2023-01-01T10:01:00Z", @@ -134,32 +134,32 @@ test("lastKnownState route segments are reset with NO_GUIDANCE", () => { "@type": "updateVehicle", request: { vehicle: { - navstatus: "NAVIGATION_STATUS_NO_GUIDANCE" - } + navstatus: "NAVIGATION_STATUS_NO_GUIDANCE", + }, }, - response: {} - } + response: {}, + }, }, { timestamp: "2023-01-01T10:02:00Z", jsonpayload: { "@type": "updateVehicle", request: { - vehicle: {} + vehicle: {}, }, - response: {} - } - } + response: {}, + }, + }, ]; const tripLogs = new TripLogs(mockLogs, "ODRD"); - + // First log should have the route segment expect(tripLogs.rawLogs[0].lastlocation.currentroutesegment).toBeDefined(); - + // Second log sets navstatus to NO_GUIDANCE, so lastKnownState should be reset expect(tripLogs.rawLogs[1].lastlocation.currentroutesegment).not.toBeDefined(); - + // Third log should not have route segment since it was reset expect(tripLogs.rawLogs[2].lastlocation.currentroutesegment).not.toBeDefined(); }); @@ -176,12 +176,12 @@ test("lastKnownState is properly propagated through a sequence of logs", () => { vehicle: { lastlocation: { location: { latitude: 37.7749, longitude: -122.4194 }, - heading: 90 - } - } + heading: 90, + }, + }, }, - response: {} - } + response: {}, + }, }, { // Log 2: Has route but no location @@ -190,11 +190,11 @@ test("lastKnownState is properly propagated through a sequence of logs", () => { "@type": "updateVehicle", request: { vehicle: { - currentroutesegment: { id: "segment1", path: [{ latitude: 37.7, longitude: -122.4 }] } - } + currentroutesegment: { id: "segment1", path: [{ latitude: 37.7, longitude: -122.4 }] }, + }, }, - response: {} - } + response: {}, + }, }, { // Log 3: Has neither location nor route @@ -202,23 +202,23 @@ test("lastKnownState is properly propagated through a sequence of logs", () => { jsonpayload: { "@type": "updateVehicle", request: { - vehicle: {} + vehicle: {}, }, - response: {} - } - } + response: {}, + }, + }, ]; const tripLogs = new TripLogs(mockLogs, "ODRD"); - + // Log 1: Should have location but no route expect(tripLogs.rawLogs[0].lastlocation.location).toEqual({ latitude: 37.7749, longitude: -122.4194 }); expect(tripLogs.rawLogs[0].lastlocation.currentroutesegment).not.toBeDefined(); - + // Log 2: Should have location from Log 1 and its own route expect(tripLogs.rawLogs[1].lastlocation.location).toEqual({ latitude: 37.7749, longitude: -122.4194 }); expect(tripLogs.rawLogs[1].lastlocation.currentroutesegment).toBeDefined(); - + // Log 3: Should have both location from Log 1 and route from Log 2 expect(tripLogs.rawLogs[2].lastlocation.location).toEqual({ latitude: 37.7749, longitude: -122.4194 }); expect(tripLogs.rawLogs[2].lastlocation.currentroutesegment).toBeDefined(); From 859b4e0cde52fb5ed9797af6ed66e40b0730ab65 Mon Sep 17 00:00:00 2001 From: regeter <2320305+regeter@users.noreply.github.com> Date: Fri, 11 Apr 2025 12:58:09 -0700 Subject: [PATCH 4/5] fix: Select and focus the row in the table for map and timeline selected events --- src/App.js | 2 ++ src/Map.js | 4 ++++ src/TimeSlider.js | 2 ++ 3 files changed, 8 insertions(+) diff --git a/src/App.js b/src/App.js index 44dc14c6..932a8bf1 100644 --- a/src/App.js +++ b/src/App.js @@ -805,6 +805,7 @@ class App extends React.Component { setFeaturedObject={this.setFeaturedObject} setTimeRange={this.setTimeRange} setCenterOnLocation={this.setCenterOnLocation} + focusSelectedRow={this.focusOnSelectedRow} /> this.onSelectionChange(row, rowIndex)} centerOnLocation={this.centerOnLocation} + focusSelectedRow={this.focusOnSelectedRow} /> { @@ -54,6 +55,8 @@ function addTripPolys(map) { if (closestEvent) { log("Found closest event:", closestEvent.timestamp); setFeaturedObject(closestEvent); + + setTimeout(() => focusSelectedRow(), 0); } }); @@ -489,6 +492,7 @@ function Map(props) { jwt = props.logData.jwt; projectId = props.logData.projectId; setFeaturedObject = props.setFeaturedObject; + focusSelectedRow = props.focusSelectedRow; setTimeRange = props.setTimeRange; function centerOnLocation(lat, lng) { diff --git a/src/TimeSlider.js b/src/TimeSlider.js index 985f47bc..28a77c4e 100644 --- a/src/TimeSlider.js +++ b/src/TimeSlider.js @@ -108,6 +108,8 @@ function TimeSlider(props) { if (props.centerOnLocation && lat && lng) { props.centerOnLocation(lat, lng); } + + setTimeout(() => props.focusSelectedRow(), 0); } } else { log("TimeSlider - No logs found in current time range"); From c2468f5c14e8a4efb0156cea14c093860d04351b Mon Sep 17 00:00:00 2001 From: regeter <2320305+regeter@users.noreply.github.com> Date: Sat, 12 Apr 2025 19:00:19 -0700 Subject: [PATCH 5/5] fix: allow unused variables and arguments starting with _ --- .eslintrc.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.eslintrc.json b/.eslintrc.json index a4b0cf9d..3681d525 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -21,7 +21,10 @@ "plugins": ["react", "jest"], "rules": { "no-var": "error", - "no-unused-vars": "warn", + "no-unused-vars": ["warn", { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_" + }], "prefer-arrow-callback": "error", "react/prop-types": "off", "react/jsx-key": "off",