Skip to content

Commit

Permalink
Implement Frontend-Only Slow Performance Story (#312)
Browse files Browse the repository at this point in the history
* add query param for frontend only slowdown (triggers quicker backend products fetch)

* move slow render problem behind frontend-only query param

* cause 'performance issue' when frontend-slowdown query param is passed

* trigger frontend-only slowdown in separate page

* TDA test to generate data for frontend slowdown page

* improve variable name
  • Loading branch information
cstavitsky authored Nov 9, 2023
1 parent c3deed1 commit e7ac9dd
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 19 deletions.
6 changes: 5 additions & 1 deletion react/src/components/Home.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@ class Home extends Component {
<div className="hero-content">
<h1>Empower your plants</h1>
<p>Keep your houseplants happy.</p>
<Button to="/products">Browse products</Button>
<Button
to={this.props.frontendSlowdown ? '/products-fes' : '/products'}
>
Browse products
</Button>
</div>
</div>
);
Expand Down
5 changes: 4 additions & 1 deletion react/src/components/Nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ class Nav extends Component {
<Link to="/about" className="sentry-unmask">
About
</Link>
<Link to="/products" className="sentry-unmask">
<Link
to={this.props.frontendSlowdown ? '/products-fes' : '/products'}
className="sentry-unmask"
>
Products
</Link>
<Link to="/cart">
Expand Down
60 changes: 50 additions & 10 deletions react/src/components/Products.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ class Products extends Component {
}

// getProducts handles error responses differently, depending on the browser used
async getProducts() {
async getProducts(frontendSlowdown) {
let se, customerType, email;
Sentry.withScope(function (scope) {
[se, customerType] = [scope._tags.se, scope._tags.customerType];
email = scope._user.email;
});

['/api', '/connect', '/organization'].forEach((endpoint) => {
[('/api', '/connect', '/organization')].forEach((endpoint) => {
fetch(this.props.backend + endpoint, {
method: 'GET',
headers: {
Expand All @@ -40,7 +40,13 @@ class Products extends Component {
});
});

let result = await fetch(this.props.backend + '/products', {
// 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 = frontendSlowdown ? '/products-join' : '/products';
console.log(`productsEndpoint: ${productsEndpoint}`);
let result = await fetch(this.props.backend + productsEndpoint, {
method: 'GET',
method: 'GET',
headers: { se, customerType, email, 'Content-Type': 'application/json' },
}).catch((err) => {
Expand All @@ -64,12 +70,46 @@ class Products extends Component {
async componentDidMount() {
var products;
try {
products = await this.getProducts();
// take first 4 products because that's all we have img/title/description for
this.props.setProducts(Array(200/4).fill(products.slice(0, 4)).flat().map((p, n) => {
p.id = n
return p
}));
products = await this.getProducts(this.props.frontendSlowdown);

// Trigger a Sentry 'Performance Issue' in the case of
// a frontend slowdown
if (this.props.frontendSlowdown) {
// Must bust cache to have force transfer size
// small compressed file
let uc_small_script = document.createElement('script');
uc_small_script.async = false;
uc_small_script.src =
this.props.backend +
'/compressed_assets/compressed_small_file.js' +
'?cacheBuster=' +
Math.random();
document.body.appendChild(uc_small_script);

// big uncompressed file
let c_big_script = document.createElement('script');
c_big_script.async = false;

c_big_script.src =
this.props.backend +
'/uncompressed_assets/uncompressed_big_file.js' +
'?cacheBuster=' +
Math.random();
document.body.appendChild(c_big_script);

// When triggering a frontend-only slowdown, cause a slow render problem
this.props.setProducts(
Array(200 / 4)
.fill(products.slice(0, 4))
.flat()
.map((p, n) => {
p.id = n;
return p;
})
);
} else {
this.props.setProducts(products.slice(0, 4));
}
} catch (err) {
Sentry.captureException(new Error('app unable to load products: ' + err));
}
Expand All @@ -80,7 +120,7 @@ class Products extends Component {
return products.length > 0 ? (
<div>
<ul className="products-list">
{products.map((product,i) => {
{products.map((product, i) => {
const averageRating = (
product.reviews.reduce((a, b) => a + (b['rating'] || 0), 0) /
product.reviews.length
Expand Down
62 changes: 55 additions & 7 deletions react/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ if (window.location.hostname === 'localhost') {
}

let BACKEND_URL;
let FRONTEND_SLOWDOWN;
const DSN = process.env.REACT_APP_DSN;
const RELEASE = process.env.REACT_APP_RELEASE;

Expand Down Expand Up @@ -166,9 +167,7 @@ class App extends Component {
let queryParams = new URLSearchParams(history.location.search);

// Set desired backend
let backendTypeParam = new URLSearchParams(history.location.search).get(
'backend'
);
let backendTypeParam = queryParams.get('backend');
const backendType = determineBackendType(backendTypeParam);
BACKEND_URL = determineBackendUrl(backendType, ENVIRONMENT);

Expand All @@ -190,9 +189,17 @@ class App extends Component {
scope.setTag('se', queryParams.get('se'));
// for use in Checkout.js when deciding whether to pre-fill form
// lasts for as long as the tab is open
sessionStorage.setItem('se', queryParams.get('se'));
sessionStorage.setItem('se', queryParams.get('se'));
}

if (queryParams.get('frontendSlowdown') === 'true') {
console.log('> frontend-only slowdown: true');
FRONTEND_SLOWDOWN = true;
scope.setTag('frontendSlowdown', true);
} else {
console.log('> frontend + backend slowdown');
scope.setTag('frontendSlowdown', false);
}
if (queryParams.get('userFeedback')) {
sessionStorage.setItem('userFeedback', queryParams.get('userFeedback'));
} else {
Expand All @@ -207,7 +214,34 @@ class App extends Component {
email = queryParams.get('userEmail');
} else {
// making fewer emails so event and user counts for an Issue are not the same
let array=['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',];
let array = [
'a',
'b',
'c',
'd',
'e',
'f',
'g',
'h',
'i',
'j',
'k',
'l',
'm',
'n',
'o',
'p',
'q',
'r',
's',
't',
'u',
'v',
'w',
'x',
'y',
'z',
];
let a = array[Math.floor(Math.random() * array.length)];
let b = array[Math.floor(Math.random() * array.length)];
let c = array[Math.floor(Math.random() * array.length)];
Expand All @@ -225,10 +259,18 @@ class App extends Component {
<Provider store={store}>
<BrowserRouter history={history}>
<ScrollToTop />
<Nav />
<Nav frontendSlowdown={FRONTEND_SLOWDOWN} />
<div id="body-container">
<SentryRoutes>
<Route path="/" element={<Home backend={BACKEND_URL} />}></Route>
<Route
path="/"
element={
<Home
backend={BACKEND_URL}
frontendSlowdown={FRONTEND_SLOWDOWN}
/>
}
></Route>
<Route
path="/about"
element={<About backend={BACKEND_URL} history={history} />}
Expand All @@ -250,6 +292,12 @@ class App extends Component {
path="/products"
element={<Products backend={BACKEND_URL} />}
></Route>
<Route
path="/products-fes" // fes = frontend slowdown (only frontend)
element={
<Products backend={BACKEND_URL} frontendSlowdown={true} />
}
></Route>
<Route
path="/nplusone"
element={<Nplusone backend={BACKEND_URL} />}
Expand Down
24 changes: 24 additions & 0 deletions tda/desktop_web/test_frontend_slowdown.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import time
import sentry_sdk
from urllib.parse import urlencode

def test_frontend_slowdown(desktop_web_driver, endpoints, random, batch_size, backend, sleep_length):

for endpoint in endpoints.react_endpoints:
endpoint_frontend_slowdown = endpoint + "/products-fes"
sentry_sdk.set_tag("endpoint", endpoint_frontend_slowdown)

for i in range(batch_size):
# Ensures a different backend endpoint gets picked each time
url = ""
# TODO make a query_string builder function for sharing this across tests
query_string = {
# 'ruby' /products /checkout endpoints not available yet
'backend': backend(exclude=['ruby', 'laravel', 'aspnetcore'])
}
url = endpoint_frontend_slowdown + '?' + urlencode(query_string)

desktop_web_driver.get(url)
time.sleep(sleep_length() + sleep_length() + 1)

# Checkout button not clicked yet in frontend slowdown flow

0 comments on commit e7ac9dd

Please sign in to comment.