From 9f4a850ed927b9f0bb2651add3aa90932a79bdd6 Mon Sep 17 00:00:00 2001 From: Gideon Lapshun Date: Wed, 29 Jan 2025 17:17:32 -0500 Subject: [PATCH] [react] feat/restore critical exp (#667) * replace metrics * Squashed update metrics with span attributes * fix export countItems and clean up logs * remove config-overrides.js --------- Co-authored-by: sergiosentry <109162568+serglom21@users.noreply.github.com> --- .gitignore | 1 + react/src/components/Cart.jsx | 13 +++-- react/src/components/Checkout.jsx | 63 +++++++++++++------- react/src/components/Products.jsx | 70 +++++++++++------------ react/src/utils/cart.js | 16 ++++++ react/src/utils/measureRequestDuration.js | 13 +++-- 6 files changed, 108 insertions(+), 68 deletions(-) create mode 100644 react/src/utils/cart.js diff --git a/.gitignore b/.gitignore index 39d0ceb6b..02c1f28a9 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ express/node_modules /.pnp .pnp.js .vercel +react/config-overrides.js # testing /coverage diff --git a/react/src/components/Cart.jsx b/react/src/components/Cart.jsx index fb454b71b..0dcf93fc0 100644 --- a/react/src/components/Cart.jsx +++ b/react/src/components/Cart.jsx @@ -4,13 +4,16 @@ import * as Sentry from '@sentry/react'; import Button from './ButtonLink'; import { connect } from 'react-redux'; import { setProducts, addProduct, removeProduct } from '../actions'; -import { getTag, itemsInCart } from '../utils/utils'; +import { countItemsInCart } from '../utils/cart'; +import { getTag } from '../utils/utils'; -function Cart({ cart, removeProduct, addProduct }) { - - let tags = { 'backendType': getTag('backendType'), 'cexp': getTag('cexp') } - Sentry.metrics.increment('checkout.items_added_to_cart', itemsInCart(cart), { tags }); +function Cart({ cart, removeProduct, addProduct }) { + const itemsInCart = countItemsInCart(cart); + let tags = { 'backendType': getTag('backendType'), 'cexp': getTag('cexp'), 'items_in_cart': itemsInCart }; + const span = Sentry.startInactiveSpan({ name: "items_added_to_cart", op: "function"}); + span.setAttributes(tags); + span.end(); return (

Cart

diff --git a/react/src/components/Checkout.jsx b/react/src/components/Checkout.jsx index d3eaa1fca..8eee4647c 100644 --- a/react/src/components/Checkout.jsx +++ b/react/src/components/Checkout.jsx @@ -5,7 +5,9 @@ import './checkout.css'; import * as Sentry from '@sentry/react'; import { connect } from 'react-redux'; import Loader from 'react-loader-spinner'; -import { getTag, itemsInCart } from '../utils/utils'; +import { countItemsInCart } from '../utils/cart'; +import { getTag } from '../utils/utils'; + function Checkout({ backend, rageclick, checkout_success, cart }) { const navigate = useNavigate(); @@ -41,11 +43,28 @@ function Checkout({ backend, rageclick, checkout_success, cart }) { } const [form, setForm] = useState(initialFormValues); - let tags = { 'backendType': getTag('backendType'), 'cexp': getTag('cexp') } +async function checkout(cart, checkout_span) { + console.log("Checkout called with cart:", cart); + console.log("Checkout span:", checkout_span); + const itemsInCart = countItemsInCart(cart); + console.log("Calculated itemsInCart:", itemsInCart); + + if (!checkout_span || typeof checkout_span.setAttribute !== 'function') { + console.error("Invalid checkout_span object:", checkout_span); + return; + } + + checkout_span.setAttribute("checkout.click", 1); + checkout_span.setAttribute("items_at_checkout", itemsInCart); + + // Verify attributes were set + console.log("Span attributes after setting:", { + click: checkout_span.getAttribute("checkout.click"), + items: checkout_span.getAttribute("items_at_checkout") + }); - async function checkout(cart) { - Sentry.metrics.increment('checkout.click', 1, { tags }); - Sentry.metrics.increment('checkout.items_in_cart', itemsInCart(cart), { tags }); + let tags = { 'backendType': getTag('backendType'), 'cexp': getTag('cexp'), 'items_at_checkout': itemsInCart, 'checkout.click': 1 }; + checkout_span.setAttributes(tags); const stopMeasurement = measureRequestDuration('/checkout'); const response = await fetch(backend + '/checkout?v2=true', { method: 'POST', @@ -56,28 +75,32 @@ function Checkout({ backend, rageclick, checkout_success, cart }) { validate_inventory: checkout_success ? "false" : "true", }), }) - .catch((err) => { - Sentry.metrics.increment('checkout.error', 1, { - tags: { status: 500 }, - }); - return { ok: false, status: 500 }; + .catch((err) => { + checkout_span.setAttributes({ + "checkout.error": 1, + "status": 500 }) - .then((res) => { - stopMeasurement(); - return res; - }); + return { ok: false, status: 500 }; + }) + .then((res) => { + stopMeasurement(); + return res; + }); if (!response.ok) { - Sentry.metrics.increment('checkout.error', 1, { - tags: { status: response.status, ...tags }, - }); + checkout_span.setAttributes({ + "checkout.error": 1, + "status": response.status + }) + throw new Error( [response.status, response.statusText || ' Internal Server Error'].join( ' -' ) ); } - Sentry.metrics.increment('checkout.success', 1, { tags }); - Sentry.metrics.distribution('checkout.order.total', cart.total); + checkout_span.setAttribute("checkout.success", 1) + checkout_span.setAttribute("checkout.order.total", cart.total); + return response; } function generateUrl(product_id) { @@ -114,7 +137,7 @@ function Checkout({ backend, rageclick, checkout_success, cart }) { setLoading(true); try { - await checkout(cart); + await checkout(cart, span); } catch (error) { Sentry.captureException(error); hadError = true; diff --git a/react/src/components/Products.jsx b/react/src/components/Products.jsx index 4802e510a..63bcef655 100644 --- a/react/src/components/Products.jsx +++ b/react/src/components/Products.jsx @@ -85,49 +85,43 @@ function Products({ frontendSlowdown, backend, productsExtremelySlow, productsBe related to the async keyword + babel transform, hence why it probably got fixed with hooks (no transform on that class method anymore)" */ - useEffect(() => { - // getProducts handles error responses differently, depending on the browser used - function getProducts(frontendSlowdown) { - [('/api', '/connect', '/organization')].forEach((endpoint) => { - fetch(backend + endpoint, { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - }).catch((err) => { - // If there's an error, it won't stop the Products http request and page from loading - Sentry.captureException(err); - }); + async function getProducts(frontendSlowdown) { + [('/api', '/connect', '/organization')].forEach((endpoint, activeSpan) => { + fetch(backend + endpoint, { + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + }).catch((err) => { + // If there's an error, it won't stop the Products http request and page from loading + Sentry.captureException(err); }); - - // When triggering a frontend-only slowdown, use the products-join endpoint - // because it returns product data with a fast backend response. - // Otherwise use the /products endpoint, which provides a slow backend response. - const productsEndpoint = determineProductsEndpoint(); - const stopMeasurement = measureRequestDuration(productsEndpoint); - fetch(backend + productsEndpoint, { + }); + const productsEndpoint = determineProductsEndpoint(); + Sentry.startSpan({ name: "Fetch Products"}, async (span) => { + const stopMeasurement = measureRequestDuration(productsEndpoint, span); + const response = await fetch(backend + productsEndpoint, { method: 'GET', headers: { 'Content-Type': 'application/json' }, - }) - .then((result) => { - if (!result.ok) { - Sentry.setContext('err', { - status: result.status, - statusText: result.statusText, - }); - return Promise.reject(); - } else { - return result.json(); - } - }) - .then(renderProducts) - .catch((err) => { - return { ok: false, status: 500 }; - }).then((res) => { - stopMeasurement() - return res + }); + const data = await response.json(); + + if (!response.ok) { + Sentry.setContext('err', { + status: response.status, + statusText: response.statusText, }); - } + return; + } + renderProducts(data); + stopMeasurement(); + }) + } - getProducts(frontendSlowdown); + useEffect(() => { + try { + getProducts(frontendSlowdown) + } catch (error) { + Sentry.captureException(error) + } }, []); return products.length > 0 ? ( diff --git a/react/src/utils/cart.js b/react/src/utils/cart.js new file mode 100644 index 000000000..887748c13 --- /dev/null +++ b/react/src/utils/cart.js @@ -0,0 +1,16 @@ +export function countItemsInCart(cart) { + let totalItems = 0; + + if (!cart || !cart.quantities) { + console.log("Cart or cart.quantities is undefined!"); + return totalItems; + } + + totalItems = Object.values(cart.quantities) + .reduce((sum, quantity) => { + console.log("Adding quantity:", quantity); + return sum + quantity; + }, 0); + + return totalItems; +} \ No newline at end of file diff --git a/react/src/utils/measureRequestDuration.js b/react/src/utils/measureRequestDuration.js index ae17e21ab..a7d830cd2 100644 --- a/react/src/utils/measureRequestDuration.js +++ b/react/src/utils/measureRequestDuration.js @@ -6,16 +6,19 @@ import * as Sentry from '@sentry/react'; * @param {string} endpoint the endpoint that was called * @returns {() => void} a function to stop the measurement */ -export default function measureRequestDuration(endpoint) { +export default function measureRequestDuration(endpoint, requestSpan) { const start = Date.now(); function stopMeasurement() { const end = Date.now(); const duration = end - start; - Sentry.metrics.distribution('request.duration', duration, { - unit: 'millisecond', - tags: { endpoint } - }); + if (requestSpan !== undefined) { + requestSpan.setAttributes({ + "request.duration": duration, + "unit": "milisecond", + "endpoint": endpoint + }) + } } return stopMeasurement;