From e7ac9dd64205af12e1910bae39f9d7c2c4ad2ba4 Mon Sep 17 00:00:00 2001
From: Chris Stavitsky <12092849+cstavitsky@users.noreply.github.com>
Date: Thu, 9 Nov 2023 14:26:09 -0800
Subject: [PATCH] Implement Frontend-Only Slow Performance Story (#312)
* 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
---
react/src/components/Home.js | 6 ++-
react/src/components/Nav.js | 5 +-
react/src/components/Products.js | 60 ++++++++++++++++++----
react/src/index.js | 62 ++++++++++++++++++++---
tda/desktop_web/test_frontend_slowdown.py | 24 +++++++++
5 files changed, 138 insertions(+), 19 deletions(-)
create mode 100644 tda/desktop_web/test_frontend_slowdown.py
diff --git a/react/src/components/Home.js b/react/src/components/Home.js
index 06bb0b22e..f5f21e24c 100644
--- a/react/src/components/Home.js
+++ b/react/src/components/Home.js
@@ -40,7 +40,11 @@ class Home extends Component {
Empower your plants
Keep your houseplants happy.
-
+
);
diff --git a/react/src/components/Nav.js b/react/src/components/Nav.js
index 6e1323a6e..205e2a95a 100644
--- a/react/src/components/Nav.js
+++ b/react/src/components/Nav.js
@@ -61,7 +61,10 @@ class Nav extends Component {
About
-
+
Products
diff --git a/react/src/components/Products.js b/react/src/components/Products.js
index 6581ae139..9f47a2ca7 100644
--- a/react/src/components/Products.js
+++ b/react/src/components/Products.js
@@ -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: {
@@ -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) => {
@@ -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));
}
@@ -80,7 +120,7 @@ class Products extends Component {
return products.length > 0 ? (
- {products.map((product,i) => {
+ {products.map((product, i) => {
const averageRating = (
product.reviews.reduce((a, b) => a + (b['rating'] || 0), 0) /
product.reviews.length
diff --git a/react/src/index.js b/react/src/index.js
index 1b5743316..d5d3418d1 100644
--- a/react/src/index.js
+++ b/react/src/index.js
@@ -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;
@@ -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);
@@ -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 {
@@ -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)];
@@ -225,10 +259,18 @@ class App extends Component {
-
+
- }>
+
+ }
+ >
}
@@ -250,6 +292,12 @@ class App extends Component {
path="/products"
element={}
>
+
+ }
+ >
}
diff --git a/tda/desktop_web/test_frontend_slowdown.py b/tda/desktop_web/test_frontend_slowdown.py
new file mode 100644
index 000000000..e94e59d3e
--- /dev/null
+++ b/tda/desktop_web/test_frontend_slowdown.py
@@ -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