From 109929d110e93e049dae83713a47b2fa76b750a5 Mon Sep 17 00:00:00 2001 From: Mario Minardi Date: Mon, 11 Dec 2023 10:50:06 -0700 Subject: [PATCH] client/web: add endpoint for logging device detail click metric (#10505) Add an endpoint for logging the device detail click metric to allow for this metric to be logged without having a valid session which is the case when in readonly mode. Updates https://github.com/tailscale/tailscale/issues/10261 Signed-off-by: Mario Minardi --- client/web/src/api.ts | 1 - client/web/src/components/views/home-view.tsx | 4 ++-- client/web/web.go | 23 +++++++++++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/client/web/src/api.ts b/client/web/src/api.ts index 7b0425d5f..74bdf9176 100644 --- a/client/web/src/api.ts +++ b/client/web/src/api.ts @@ -369,4 +369,3 @@ export type MetricName = | "web_client_node_connect" | "web_client_node_disconnect" | "web_client_advertise_routes_change" - | "web_client_device_details_click" diff --git a/client/web/src/components/views/home-view.tsx b/client/web/src/components/views/home-view.tsx index 21f24f0de..e1f1e3130 100644 --- a/client/web/src/components/views/home-view.tsx +++ b/client/web/src/components/views/home-view.tsx @@ -3,7 +3,7 @@ import cx from "classnames" import React, { useMemo } from "react" -import { incrementMetric } from "src/api" +import { apiFetch } from "src/api" import { ReactComponent as ArrowRight } from "src/assets/icons/arrow-right.svg" import { ReactComponent as Machine } from "src/assets/icons/machine.svg" import AddressCard from "src/components/address-copy-card" @@ -68,7 +68,7 @@ export default function HomeView({ incrementMetric("web_client_device_details_click")} + onClick={() => apiFetch("/device-details-click", "POST")} > View device details → diff --git a/client/web/web.go b/client/web/web.go index 19ee8ea9a..e6d9d706e 100644 --- a/client/web/web.go +++ b/client/web/web.go @@ -336,6 +336,9 @@ func (s *Server) authorizeRequest(w http.ResponseWriter, r *http.Request) (ok bo case r.URL.Path == "/api/data" && r.Method == httpm.GET: // Readonly endpoint allowed without valid browser session. return true + case r.URL.Path == "/api/device-details-click" && r.Method == httpm.POST: + // Special case metric endpoint that is allowed without a browser session. + return true case strings.HasPrefix(r.URL.Path, "/api/"): // All other /api/ endpoints require a valid browser session. if err != nil || !session.isAuthorized(s.timeNow()) { @@ -371,6 +374,8 @@ func (s *Server) serveLoginAPI(w http.ResponseWriter, r *http.Request) { s.serveGetNodeData(w, r) case r.URL.Path == "/api/up" && r.Method == httpm.POST: s.serveTailscaleUp(w, r) + case r.URL.Path == "/api/device-details-click" && r.Method == httpm.POST: + s.serveDeviceDetailsClick(w, r) default: http.Error(w, "invalid endpoint or method", http.StatusNotFound) } @@ -549,6 +554,9 @@ func (s *Server) serveAPI(w http.ResponseWriter, r *http.Request) { case path == "/routes" && r.Method == httpm.POST: s.servePostRoutes(w, r) return + case path == "/device-details-click" && r.Method == httpm.POST: + s.serveDeviceDetailsClick(w, r) + return case strings.HasPrefix(path, "/local/"): s.proxyRequestToLocalAPI(w, r) return @@ -970,6 +978,21 @@ func (s *Server) serveTailscaleUp(w http.ResponseWriter, r *http.Request) { } } +// serveDeviceDetailsClick increments the web_client_device_details_click metric +// by one. +// +// Metric logging from the frontend typically is proxied to the localapi. This event +// has been special cased as access to the localapi is gated upon having a valid +// session which is not always the case when we want to be logging this metric (e.g., +// when in readonly mode). +// +// Other metrics should not be logged in this way without a good reason. +func (s *Server) serveDeviceDetailsClick(w http.ResponseWriter, r *http.Request) { + s.lc.IncrementCounter(r.Context(), "web_client_device_details_click", 1) + + io.WriteString(w, "{}") +} + // proxyRequestToLocalAPI proxies the web API request to the localapi. // // The web API request path is expected to exactly match a localapi path,