Skip to content

Commit

Permalink
[react] feat/restore critical exp (#667)
Browse files Browse the repository at this point in the history
* replace metrics

* Squashed update metrics with span attributes

* fix export countItems and clean up logs

* remove config-overrides.js

---------

Co-authored-by: sergiosentry <[email protected]>
  • Loading branch information
gid-sentry and serglom21 authored Jan 29, 2025
1 parent 9cea64b commit 9f4a850
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 68 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ express/node_modules
/.pnp
.pnp.js
.vercel
react/config-overrides.js

# testing
/coverage
Expand Down
13 changes: 8 additions & 5 deletions react/src/components/Cart.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div className="cart-container">
<h2 className="sentry-unmask">Cart</h2>
Expand Down
63 changes: 43 additions & 20 deletions react/src/components/Checkout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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',
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down
70 changes: 32 additions & 38 deletions react/src/components/Products.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 ? (
Expand Down
16 changes: 16 additions & 0 deletions react/src/utils/cart.js
Original file line number Diff line number Diff line change
@@ -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;
}
13 changes: 8 additions & 5 deletions react/src/utils/measureRequestDuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 9f4a850

Please sign in to comment.