Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 76 additions & 82 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,137 +1,131 @@
# Fleet Debugger Tool

A visualization and debugging tool for Google Maps Platform's Mobility Solutions, supporting [Scheduled tasks](https://developers.google.com/maps/documentation/mobility/fleet-engine/essentials/tasks-intro) and [On-demand trips](https://developers.google.com/maps/documentation/mobility/fleet-engine/essentials/trip-intro).
Fleet Debugger is an open-source web tool designed to help you visualize and analyze logs from Google Maps Platform's Mobility solutions, supporting both [Scheduled tasks](https://developers.google.com/maps/documentation/mobility/fleet-engine/essentials/tasks-intro) and [On-demand trips](https://developers.google.com/maps/documentation/mobility/fleet-engine/essentials/trip-intro). It provides an interactive map and timeline to analyze vehicle and task or trip data, running entirely in your browser.

![Screenshot](docs/screenshots/vehiclereplay.gif)
![Fleet Debugger interface showing a map and log entries](docs/screenshots/vehiclereplay.gif)

## Using the Demo Site(s)
## Key Features

The fastest way to get started is using our GitHub hosted site: \
[googlemaps.github.io/fleet-debugger/demos/multiple-trips](https://googlemaps.github.io/fleet-debugger/demos/multiple-trips)
Fleet Debugger helps you understand complex journey and vehicle behaviors by offering:

We also have demo data for:
- [Scheduled task](https://googlemaps.github.io/fleet-debugger/demos/lmfs/)
* **Interactive map and timeline replay:** Observe vehicle movement in real time or at an accelerated time-lapse.
* **Synchronization:** Events are linked across the map, data table, and timeline. Click an event in one place, and it's highlighted everywhere.
* **Detailed log entry inspection:** Deep dive into API requests and responses.
* **Filter & inspect log messages:** Use customizable table views to easily find and analyze specific log entries.
* **File Import:** Load logs from JSON or ZIP files.
* **Direct Cloud Logging Connection:** Securely fetch logs directly from your Google Cloud project.
* **Flexible Filtering:** Easily narrow down data by time range, entity IDs, and more.
* **View status changes:** Track changes in vehicle, trip, task, and navigation status.
* **Visualize multiple trips:** View all trips or tasks for a single vehicle.
* **Analyze GPS data:** Examine location, accuracy, and heading information.
* **GPS accuracy, speed, and heading analysis:** Detailed analysis tools for these metrics ([GPS accuracy](docs/GPSAccuracy.md), [speed](docs/Speed.md), [heading](docs/Heading.md)).
* **Analyze dwell times:** Measure time spent at specific locations ([dwell times](docs/DwellTimes.md)).
* **View planned navigation routes:** See the routes with traffic conditions as experienced by drivers (requires [Restricted Use Logs](#restricted-use-logs)).
* **See requested vs. actual pickup and dropoff points:** (requires [Restricted Use Logs](#restricted-use-logs)).
* **Map and Timeslider Interaction:** Click directly on the map or the timeslider to select the nearest log event.
* **Tracking (Chevron):** Use the tracking button to keep the map centered on the current event during replay.
* **Exporting Logs:** Export loaded dataset to a local file for easy collaboration.

### Loading Your Data
## Using the Demo Site

Click on any empy Dataset buttons `Load Dataset` to get the `Fleet Engine Logs Loading` UI.
The fastest way to get started is using our GitHub hosted site.

![Fleet Engine Logs Loading](docs/screenshots/Fleet_Engine_Logs_Loading.png)
This site includes demo data for:

#### Direct Cloud Logging Connection (Recommended)
* [On-demand trips](https://googlemaps.github.io/fleet-debugger/demos/multiple-trips)
* [Scheduled tasks](https://googlemaps.github.io/fleet-debugger/demos/lmfs/)

1. **Configure Parameters:** Configure the Cloud Logging query parameters directly within UI.
## Loading Your Data

2. **Connect to Cloud Logging:** The Fleet Debugger can connect directly to your Google Cloud project's Cloud Logging. Click the `Sign in and Fetch Logs` button and follow the prompts to authenticate and grant access. You'll need appropriate IAM permissions (`roles/logging.viewer` which is also granted via `roles/viewer`) for the Fleet Debugger to read logs.
Click on any empty "Load Dataset" button to open the data loading interface.

#### Log Files in JSON Format
![Fleet Engine Logs Loading](docs/screenshots/Fleet_Engine_Logs_Loading.png)

1. Export your Fleet Engine logs from Cloud Logging using one of the following filters (customize as needed):
### 1. Direct Cloud Logging Connection (Recommended)

```sql
-- On-demand trips
resource.type="fleetengine.googleapis.com/Fleet"
AND (labels.vehicle_id="YOUR_VEHICLE_ID" OR
labels.trip_id=~"(TRIP_ID_1|TRIP_ID_2)")
AND timestamp >= "START_TIME" -- ISO 8601 format (YYYY-MM-DDTHH:MM:SS)
AND timestamp <= "END_TIME" -- ISO 8601 format (YYYY-MM-DDTHH:MM:SS)
AND (
logName:"logs/fleetengine.googleapis.com%2Fcreate_vehicle" OR
logName:"logs/fleetengine.googleapis.com%2Fupdate_vehicle" OR
logName:"logs/fleetengine.googleapis.com%2Fcreate_trip" OR
logName:"logs/fleetengine.googleapis.com%2Fupdate_trip"
)
```
1. **Configure Parameters:** Input your Project ID, Vehicle ID(s) or Trip/Task ID(s), and the selected time range within the UI.
2. **Connect to Cloud Logging:** Click the "Sign in and Fetch Logs" button and follow the prompts to authenticate with your Google Account and grant access. You'll need appropriate IAM permissions (e.g., `roles/logging.viewer`) to read logs.

```sql
-- Scheduled tasks
resource.type="fleetengine.googleapis.com/DeliveryFleet"
AND (labels.delivery_vehicle_id="YOUR_VEHICLE_ID" OR
labels.task_id=~"(TASK_ID_1|TASK_ID_2)")
AND timestamp >= "START_TIME" -- ISO 8601 format (YYYY-MM-DDTHH:MM:SS)
AND timestamp <= "END_TIME" -- ISO 8601 format (YYYY-MM-DDTHH:MM:SS)
AND (
logName:"logs/fleetengine.googleapis.com%2Fcreate_delivery_vehicle" OR
logName:"logs/fleetengine.googleapis.com%2Fupdate_delivery_vehicle" OR
logName:"logs/fleetengine.googleapis.com%2Fcreate_task" OR
logName:"logs/fleetengine.googleapis.com%2Fupdate_task"
)
```
### 2. Import from Log Files

2. Download the logs in JSON format and optionally zip them
3. Import the JSON/ZIP file to Fleet Debugger, using the `Load JSON or ZIP file instead` button.
You can load log data from JSON or ZIP files using the "Load JSON or ZIP file instead" button. This is useful for:

> **Note**: All data processing happens client-side. Your logs remain in your browser's Local Storage and are not uploaded to Google/GitHub.
* Analyzing logs shared with you.
* Loading previously exported datasets.

### Key Features
You can export logs from the Google Cloud Console's Logs Explorer.

- **Filter & inspect log messages:** Use customizable table views to easily find and analyze specific log entries.
- **View planned navigation routes:** See the routes with traffic conditions as experienced by drivers (requires [Restricted Use Logs](#restricted-use-logs)).
- **Replay vehicle movement:** Observe vehicle movement in real time or at an accelerated time-lapse.
- **See requested vs. actual pickup and dropoff points:** (requires [Restricted Use Logs](#restricted-use-logs)).
- **View status changes:** Track changes in vehicle, trip, and navigation status.
- **Analyze GPS data:** Examine location, accuracy, and heading information.
- **Visualize multiple trips:** View all trips for a single vehicle.
- **Analyze GPS accuracy, speed, and heading:** Detailed analysis tools for these metrics ([GPS accuracy](docs/GPSAccuracy.md), [speed](docs/Speed.md), [heading](docs/Heading.md)).
- **Analyze dwell times:** Measure time spent at specific locations ([dwell times](docs/DwellTimes.md)).
- **Map and Timeslider Interaction:** Click directly on the map or the timeslider to select the nearest log event.
- **Tracking (Chevron):** Use the tracking button to keep the map centered on the current event during replay.
- **Exporting Logs:** Export loaded dataset to a local file for easy collaboration.
> **Note**: All data processing happens client-side. Your logs or API keys are not uploaded to any server. Data is stored in your browser's Local Storage.

### Restricted Use Logs
## Restricted Use Logs

Planned navigation routes and requested Pickup/Dropoff points require enablement of [Restricted Use Logs](https://developers.google.com/maps/documentation/mobility/operations/cloud-logging/setup#enable_restricted_use_logs).
To see features like the driver's planned navigation route, traffic, and original requested stop locations, you need to enable
[Restricted Use Logs](https://developers.google.com/maps/documentation/mobility/operations/cloud-logging/setup#enable_restricted_use_logs) in your Google Cloud project. This is not required for the tool to function but highly recommended for a richer analysis.

### Managing Datasets
## Managing Datasets

Each dataset (loaded from a file or Cloud Logging) has a dropdown menu:
Each dataset has a dropdown menu:

- **Save (Export):** Save the current dataset as a JSON file.
- **Delete:** Remove the dataset from the Fleet Debugger. This clears the data from your browser's local storage.
- **Export:** Save the current dataset as a JSON file to your local system.
- **Delete:** Remove the dataset from the Fleet Debugger and your browser's local storage.

### Restoring Demo Data

To reload the original demo data:
1. Select "Delete" from `Dataset 1` dropdown menu.
2. Refresh the page. The demo data will be automatically reloaded into Dataset 1.
1. Select "Delete" from the `Dataset 1` dropdown menu.
2. Refresh the page. The demo data will be automatically reloaded.

## Running Your Own Server

### Development Setup

1. Install dependencies:
- [Node.js](https://nodejs.org/en/download)

2. Install node modules:
```bash
npm install
```
1. Install dependencies:
* [Node.js](https://nodejs.org/en/download)
2. Clone the repository:
```bash
git clone https://github.com/googlemaps/fleet-debugger.git
cd fleet-debugger
```
3. Install node modules:
```bash
npm install
```
4. Add Maps Javascript API Key to src/constants.js

### Start development server

```bash
npm start
```
This will open the app in your default browser, usually at `http://localhost:3000`.

### Building and Deploying

```bash
# Generate static build
npm run build
```
The static files will be in the `build` folder. You can deploy this folder to any static site hosting service. For Firebase Hosting:

# Deploy to firebase
```bash
# Optional: Install Firebase CLI globally
npm install -g firebase-tools

# Deploy to Firebase Hosting
firebase deploy --only hosting
```
(Requires Firebase project setup and `firebase login`)

## Privacy Policy

This project is 100% client-side and does not collect or store any user data on servers. Please see our [Privacy Policy](docs/PRIVACY.md) for full details.
## Privacy

## Disclaimer
This project is 100% client-side. Please see our [Privacy Policy](docs/PRIVACY.md) for full details.

This is not an officially supported Google product.
## Support & Contributing

## Additional Resources
This Fleet Debugger tool is offered under an open source license. It is not an officially supported Google product.

- [Reporting Issues](docs/reporting-issues.md)
* To report bugs or request features, file an issue on
[GitHub](https://github.com/googlemaps/fleet-debugger/issues).
* For technical questions and discussions, use the
[Google Maps Platform developer community channels](https://developers.google.com/maps/developer-community).
* To contribute, check the [CONTRIBUTING.md](CONTRIBUTING.md) guide.
52 changes: 43 additions & 9 deletions src/CloudLogging.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,53 @@ export function buildQueryFilter(params) {
const startDate = params.startTime || new Date(0).toISOString();
const endDate = params.endTime || new Date(Date.now() + 86400000).toISOString();

// Build vehicle/trip filter part
let entityFilter = "";
/*
* Query for both On-demand Trips (Fleet) AND Scheduled Tasks (DeliveryFleet)
*
* We use "resource.type" OR logic because a single log entry has only one resource type.
*
* Structure:
* (
* (On-demand Trips (ODRD) Conditions) OR (Scheduled Tasks (LMFS) Conditions)
* )
* AND timestamp...
* AND logName...
*/

let vehicleFilter = "";
if (params.vehicleId?.trim()) {
entityFilter += `labels.vehicle_id="${params.vehicleId.trim()}"`;
const vId = params.vehicleId.trim();
vehicleFilter = `(labels.vehicle_id="${vId}" OR labels.delivery_vehicle_id="${vId}")`;
}

let tripTaskFilter = "";
if (params.tripIds?.trim()) {
const trips = params.tripIds
const ids = params.tripIds
.split(",")
.map((t) => t.trim())
.filter(Boolean);
if (trips.length > 0) {
const tripFilter = trips.length === 1 ? `labels.trip_id="${trips[0]}"` : `labels.trip_id=~"(${trips.join("|")})"`;
entityFilter = entityFilter ? `(${entityFilter} OR ${tripFilter})` : tripFilter;

if (ids.length > 0) {
if (ids.length === 1) {
const id = ids[0];
tripTaskFilter = `(labels.trip_id="${id}" OR labels.task_id="${id}")`;
} else {
const joined = ids.join("|");
tripTaskFilter = `(labels.trip_id=~"(${joined})" OR labels.task_id=~"(${joined})")`;
}
}
}

// If both present: (vehicleFilter OR tripTaskFilter)
let entityFilter = "";
if (vehicleFilter && tripTaskFilter) {
entityFilter = `(${vehicleFilter} OR ${tripTaskFilter})`;
} else {
entityFilter = vehicleFilter || tripTaskFilter;
}

const filter = `
resource.type="fleetengine.googleapis.com/Fleet"
(resource.type="fleetengine.googleapis.com/Fleet" OR resource.type="fleetengine.googleapis.com/DeliveryFleet")
AND ${entityFilter}
AND timestamp >= "${startDate}"
AND timestamp <= "${endDate}"
Expand All @@ -48,7 +77,12 @@ export function buildQueryFilter(params) {
logName:"logs/fleetengine.googleapis.com%2Fupdate_vehicle" OR
logName:"logs/fleetengine.googleapis.com%2Fcreate_trip" OR
logName:"logs/fleetengine.googleapis.com%2Fupdate_trip" OR
logName:"logs/fleetengine.googleapis.com%2Fget_trip"
logName:"logs/fleetengine.googleapis.com%2Fget_trip" OR
logName:"logs/fleetengine.googleapis.com%2Fcreate_delivery_vehicle" OR
logName:"logs/fleetengine.googleapis.com%2Fupdate_delivery_vehicle" OR
logName:"logs/fleetengine.googleapis.com%2Fcreate_task" OR
logName:"logs/fleetengine.googleapis.com%2Fupdate_task" OR
logName:"logs/fleetengine.googleapis.com%2Fget_task"
)
`;

Expand Down
18 changes: 11 additions & 7 deletions src/CloudLogging.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ describe("buildQueryFilter", () => {
const filter = buildQueryFilter(params);

// Check for key components in the filter
expect(filter).toContain('resource.type="fleetengine.googleapis.com/Fleet"');
expect(filter).toContain('labels.vehicle_id="vehicle1"');
expect(filter).toContain(
'(resource.type="fleetengine.googleapis.com/Fleet" OR resource.type="fleetengine.googleapis.com/DeliveryFleet")'
);
expect(filter).toContain('(labels.vehicle_id="vehicle1" OR labels.delivery_vehicle_id="vehicle1")');
expect(filter).toContain("2023-01-01T01:00:00");
expect(filter).toContain("2023-01-02T02:00:00");
});
Expand All @@ -31,8 +33,8 @@ describe("buildQueryFilter", () => {

const filter = buildQueryFilter(params);

// Should include regex for multiple trip IDs
expect(filter).toContain('labels.trip_id=~"(trip1|trip2)"');
// Should include regex for multiple trip IDs and verify task_id is included
expect(filter).toContain('(labels.trip_id=~"(trip1|trip2)" OR labels.task_id=~"(trip1|trip2)")');
});

test("builds filter with single trip ID", () => {
Expand All @@ -46,8 +48,8 @@ describe("buildQueryFilter", () => {

const filter = buildQueryFilter(params);

// Should use exact match for single trip ID
expect(filter).toContain('labels.trip_id="trip1"');
// Should use exact match for single trip ID and verify task_id is included
expect(filter).toContain('(labels.trip_id="trip1" OR labels.task_id="trip1")');
});

test("builds filter with both vehicle and trip IDs", () => {
Expand All @@ -62,7 +64,9 @@ describe("buildQueryFilter", () => {
const filter = buildQueryFilter(params);

// Should combine vehicle and trip filters with OR
expect(filter).toContain('(labels.vehicle_id="vehicle1" OR labels.trip_id=~"(trip1|trip2)")');
expect(filter).toContain(
'((labels.vehicle_id="vehicle1" OR labels.delivery_vehicle_id="vehicle1") OR (labels.trip_id=~"(trip1|trip2)" OR labels.task_id=~"(trip1|trip2)"))'
);
});

test("throws error for missing project ID", () => {
Expand Down
4 changes: 2 additions & 2 deletions src/DatasetLoading.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,14 @@ const CloudLoggingFormComponent = ({ onLogsReceived, onFileUpload }) => {
</div>
<div className="form-field">
<label className="form-label">
Trip IDs (comma-separated):
Trip/Task IDs (comma-separated):
<input
type="text"
name="tripIds"
value={queryParams.tripIds}
onChange={(e) => setQueryParams({ ...queryParams, tripIds: e.target.value })}
className="form-input"
placeholder="TRIP_ID_1,TRIP_ID_2"
placeholder="TRIP_ID, TASK_ID"
autoComplete="on"
/>
</label>
Expand Down
8 changes: 7 additions & 1 deletion src/Task.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,13 @@ class Task {
taskInfo.taskoutcomelocation = lastUpdate.taskResp.taskoutcomelocation;
taskInfo.taskoutcometime = lastUpdate.taskResp.taskoutcometime;
taskInfo.trackingid = lastUpdate.taskResp.trackingid || lastUpdate.taskReq.task.trackingid;
if (taskInfo.taskoutcomelocationsource && taskInfo.plannedlocation && taskInfo.taskoutcomelocation) {
if (
taskInfo.taskoutcomelocationsource &&
taskInfo.plannedlocation &&
taskInfo.plannedlocation.point &&
taskInfo.taskoutcomelocation &&
taskInfo.taskoutcomelocation.point
) {
taskInfo.plannedVsActualDeltaMeters = window.google.maps.geometry.spherical.computeDistanceBetween(
{
lat: taskInfo.plannedlocation.point.latitude,
Expand Down
Loading