Skip to content

Commit 97fba17

Browse files
authored
feat: google sheets integration (#286)
* fix: rename "GPS Accuracy" to "Location Accuracy" * feat: Add Google Sheet Export and Import * fix: google sheet column names in reversed order * fix: save files and google sheets with vehicleid * fix: Add Google Sign In library early to support Google Sheets * fix: add Google Client ID to constants.js * build: allow manual demo builds on forks * fix: clean up css * fix: clean up export names * feat: filename and google sheet name should contain the date of the logs
1 parent 0b50f14 commit 97fba17

File tree

9 files changed

+497
-19
lines changed

9 files changed

+497
-19
lines changed

.github/workflows/build-demos.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ permissions:
1616

1717
jobs:
1818
build-and-deploy:
19-
if: github.repository == 'googlemaps/fleet-debugger'
19+
if: github.repository == 'googlemaps/fleet-debugger' || github.event_name == 'workflow_dispatch'
2020
runs-on: ubuntu-latest
2121

2222
steps:

public/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
work correctly both with client-side routing and a non-root public URL.
2525
Learn how to configure a non-root public URL by running `npm run build`.
2626
-->
27+
<script src="https://accounts.google.com/gsi/client" async defer></script>
2728
<title>Fleet Debugger</title>
2829
</head>
2930
<body>

src/App.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
saveDatasetAsJson,
1818
saveToIndexedDB,
1919
} from "./localStorage";
20+
import { exportToGoogleSheet, requestSheetsToken } from "./GoogleSheets";
2021
import _ from "lodash";
2122
import { getQueryStringValue, setQueryStringValue } from "./queryString";
2223
import "./global.css";
@@ -527,6 +528,28 @@ class App extends React.Component {
527528
}
528529
};
529530

531+
const handleGoogleSheetExport = async (e) => {
532+
e.stopPropagation();
533+
log(`Google Sheet export initiated for dataset ${index}`);
534+
this.setState({ activeMenuIndex: null });
535+
536+
try {
537+
const token = await requestSheetsToken();
538+
const sheetUrl = await exportToGoogleSheet(index, token);
539+
toast.success(
540+
<span>
541+
Exported to{" "}
542+
<a href={sheetUrl} target="_blank" rel="noopener noreferrer">
543+
Google Sheet
544+
</a>
545+
</span>
546+
);
547+
} catch (error) {
548+
log(`Error exporting to Google Sheet: ${error.message}`, error);
549+
toast.error(`Google Sheet export failed: ${error.message}`);
550+
}
551+
};
552+
530553
const handlePruneClick = async (e) => {
531554
e.stopPropagation();
532555
log(`Prune initiated for dataset ${index}`);
@@ -681,7 +704,10 @@ class App extends React.Component {
681704
{isMenuOpen && (
682705
<div className="dataset-button-menu">
683706
<div className="dataset-button-menu-item export" onClick={handleSaveClick}>
684-
Export
707+
Export File
708+
</div>
709+
<div className="dataset-button-menu-item export" onClick={handleGoogleSheetExport}>
710+
Export GSheet
685711
</div>
686712
<div className="dataset-button-menu-item prune" onClick={handlePruneClick}>
687713
Prune

src/DatasetLoading.js

Lines changed: 89 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@ import ExtraDataSource from "./ExtraDataSource";
55
import { log } from "./Utils";
66
import { toast } from "react-toastify";
77
import { isTokenValid, fetchLogsWithToken, useCloudLoggingLogin, buildQueryFilter } from "./CloudLogging";
8-
import { HAS_EXTRA_DATA_SOURCE } from "./constants";
8+
import { useSheetsLogin, isSheetsTokenValid, getSheetsToken, importFromGoogleSheet } from "./GoogleSheets";
9+
import { HAS_EXTRA_DATA_SOURCE, GOOGLE_CLIENT_ID } from "./constants";
910

1011
const CloudLoggingFormComponent = ({ onLogsReceived, onFileUpload }) => {
1112
const getStoredValue = (key, defaultValue = "") => localStorage.getItem(`datasetLoading_${key}`) || defaultValue;
1213

1314
const [fetching, setFetching] = useState(false);
15+
const [sheetFormVisible, setSheetFormVisible] = useState(false);
16+
const [sheetUrl, setSheetUrl] = useState(localStorage.getItem("datasetLoading_sheetUrl") || "");
17+
const [sheetLoading, setSheetLoading] = useState(false);
1418
const [queryParams, setQueryParams] = useState({
1519
projectId: getStoredValue("projectId"),
1620
vehicleId: getStoredValue("vehicleId"),
@@ -65,6 +69,52 @@ const CloudLoggingFormComponent = ({ onLogsReceived, onFileUpload }) => {
6569
}
6670
};
6771

72+
const handleSheetImport = (token) => {
73+
setSheetLoading(true);
74+
setLocalError(null);
75+
localStorage.setItem("datasetLoading_sheetUrl", sheetUrl);
76+
77+
importFromGoogleSheet(sheetUrl, token)
78+
.then((logs) => {
79+
log(`Received ${logs.length} logs from Google Sheet`);
80+
if (logs.length > 0) {
81+
onLogsReceived(logs);
82+
} else {
83+
toast.warning("No logs found in the spreadsheet.");
84+
}
85+
})
86+
.catch((err) => {
87+
setLocalError(`Sheet import error: ${err.message}`);
88+
toast.error(`Sheet import error: ${err.message}`);
89+
})
90+
.finally(() => setSheetLoading(false));
91+
};
92+
93+
const sheetsLogin = useSheetsLogin(
94+
(token) => {
95+
log("Sheets login successful, importing...");
96+
handleSheetImport(token);
97+
},
98+
(err) => {
99+
log("Sheets login failed.", err);
100+
setLocalError(`Auth Error: ${err.error || "Unknown"}`);
101+
setSheetLoading(false);
102+
}
103+
);
104+
105+
const handleSheetLoadClick = () => {
106+
if (!sheetUrl.trim()) {
107+
setLocalError("Please enter a spreadsheet URL or ID.");
108+
return;
109+
}
110+
setLocalError(null);
111+
if (isSheetsTokenValid()) {
112+
handleSheetImport(getSheetsToken());
113+
} else {
114+
sheetsLogin();
115+
}
116+
};
117+
68118
return (
69119
<div className="cloud-logging-form">
70120
<h3>Fleet Engine Logs Loading</h3>
@@ -162,14 +212,48 @@ const CloudLoggingFormComponent = ({ onLogsReceived, onFileUpload }) => {
162212
</div>
163213
)}
164214
<div className="cloud-logging-buttons">
165-
<button type="button" onClick={handleFetch} disabled={fetching} className="primary-button">
215+
<button type="button" onClick={handleFetch} disabled={fetching} className="fetch-logs-button">
166216
{fetching ? "Fetching..." : isTokenValid() ? "Fetch Logs" : "Sign in and Fetch Logs"}
167217
</button>
168-
<label htmlFor="fileUploadInput" className="secondary-button">
169-
Load JSON or ZIP file instead
218+
<button type="button" onClick={() => setSheetFormVisible(!sheetFormVisible)} className="sideload-logs-button">
219+
Load Google Sheet
220+
</button>
221+
<label htmlFor="fileUploadInput" className="sideload-logs-button">
222+
Load JSON or ZIP
170223
</label>
171224
<input type="file" id="fileUploadInput" accept=".json,.zip" onChange={onFileUpload} className="file-input" />
172225
</div>
226+
{sheetFormVisible && (
227+
<div className="google-sheet-form">
228+
<div className="form-field">
229+
<label className="form-label">
230+
Spreadsheet URL or ID:
231+
<input
232+
type="text"
233+
value={sheetUrl}
234+
onChange={(e) => setSheetUrl(e.target.value)}
235+
placeholder="https://docs.google.com/spreadsheets/d/... or spreadsheet ID"
236+
className="form-input"
237+
/>
238+
</label>
239+
</div>
240+
<button
241+
type="button"
242+
onClick={handleSheetLoadClick}
243+
disabled={sheetLoading}
244+
className="fetch-logs-button"
245+
style={{ marginTop: "8px" }}
246+
>
247+
{sheetLoading ? "Loading..." : isSheetsTokenValid() ? "Load Sheet" : "Sign in and Load Sheet"}
248+
</button>
249+
{sheetLoading && (
250+
<div className="progress-indicator">
251+
<div>Loading from Google Sheet...</div>
252+
<progress className="progress-bar" />
253+
</div>
254+
)}
255+
</div>
256+
)}
173257
</div>
174258
);
175259
};
@@ -210,7 +294,7 @@ export default function DatasetLoading(props) {
210294
{isExtra ? (
211295
ExtraFormComponent
212296
) : (
213-
<GoogleOAuthProvider clientId="829183678942-eq2c9cd7pjdm39l2um5thgbrvgva07e7.apps.googleusercontent.com">
297+
<GoogleOAuthProvider clientId={GOOGLE_CLIENT_ID}>
214298
<CloudLoggingFormComponent {...props} />
215299
</GoogleOAuthProvider>
216300
)}

0 commit comments

Comments
 (0)