Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Frontend-Only Slow Performance Story #312

Merged
merged 7 commits into from
Nov 9, 2023
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