Browse Source

feat(demo): create scenario 1 for demo mode (#24514)

Stephen Cefali 4 years ago
parent
commit
fdd847343e

+ 5 - 9
bin/load-mocks

@@ -64,8 +64,11 @@ from sentry.signals import mocks_loaded
 from sentry.similarity import features
 from sentry.utils import loremipsum
 from sentry.utils.hashlib import md5_text
-from sentry.utils.samples import create_sample_event as _create_sample_event
-from sentry.utils.samples import generate_user
+from sentry.utils.samples import (
+    create_sample_event as _create_sample_event,
+    random_normal,
+    generate_user,
+)
 
 PLATFORMS = itertools.cycle(["ruby", "php", "python", "java", "javascript"])
 
@@ -724,13 +727,6 @@ def main(num_events=1, extra_events=False, load_trends=False, slow=False):
     create_system_time_series()
 
 
-def random_normal(mu, sigma, minimum, maximum=None):
-    random_value = max(random.normalvariate(mu, sigma), minimum)
-    if maximum is not None:
-        random_value = min(random_value, maximum)
-    return random_value
-
-
 def create_mock_transactions(project_map, load_trends=False, slow=False):
     backend_project = project_map["Earth"]
     frontend_project = project_map["Fire"]

+ 11 - 0
src/sentry/conf/server.py

@@ -2133,5 +2133,16 @@ DEMO_NO_ORG_BUFFER = False
 # all demo orgs are owned by the user with this email
 DEMO_ORG_OWNER_EMAIL = None
 
+# paramters that determine how demo events are generated
+DEMO_DATA_GEN_PARAMS = {
+    "MAX_DAYS": 7,  # how many days of data
+    "SCALE_FACTOR": 1,  # scales the frequency of events
+    "BASE_OFFSET": 0.5,  # higher values increases the minum number of events in an hour
+    "NAME_STEP_SIZE": 20,  # higher value means fewr possible test users in sample
+    "BREADCRUMB_LOOKBACK_TIME": 5,  # how far back should breadcrumbs go from the time of the event
+    "DEFAULT_BACKOFF_TIME": 0,  # backoff time between sending events
+    "ERROR_BACKOFF_TIME": 0.5,  # backoff time after a snuba error
+}
+
 # adds an extra JS to HTML template
 INJECTED_SCRIPT_ASSETS = []

+ 165 - 0
src/sentry/demo/data/python_transaction_1.json

@@ -0,0 +1,165 @@
+{
+  "event_id": "2e75afe87658407cb6f11c65ed3d65fa",
+  "project": 5641789,
+  "release": "52f7c8a62a97cecf029f6a68adb411c1714c4836",
+  "dist": null,
+  "platform": "python",
+  "message": "",
+  "datetime": "2021-03-15T19:37:14.460185+00:00",
+  "tags": [
+    ["browser", "Safari 12.1.1"],
+    ["browser.name", "Safari"],
+    ["client_os", "Mac OS X 10.13.6"],
+    ["client_os.name", "Mac OS X"],
+    ["device", "Mac"],
+    ["device.family", "Mac"],
+    ["environment", "prod"],
+    ["http.status_code", "500"],
+    ["level", "info"],
+    ["runtime", "CPython 2.7.18"],
+    ["runtime.name", "CPython"],
+    ["release", "52f7c8a62a97cecf029f6a68adb411c1714c4836"],
+    ["user", "email:zllm@yahoo.com"],
+    ["server_name", "localhost"],
+    ["transaction", "checkout"],
+    ["url", "http://aj-flask-m3uuizd7iq-uc.a.run.app/checkout"]
+  ],
+  "_meta": {
+    "request": {"headers": {"13": {"1": {"": {"rem": [["!config", "x", 0, 11]]}}}}}
+  },
+  "_metrics": {"bytes.ingested.event": 3434, "bytes.stored.event": 4534},
+  "contexts": {
+    "browser": {"name": "Safari", "version": "12.1.1", "type": "browser"},
+    "client_os": {"name": "Mac OS X", "version": "10.13.6", "type": "os"},
+    "device": {"family": "Mac", "model": "Mac", "brand": "Apple", "type": "device"},
+    "runtime": {
+      "name": "CPython",
+      "version": "2.7.18",
+      "build": "2.7.18 (default, Apr 20 2020, 19:34:11) \n[GCC 8.3.0]",
+      "type": "runtime"
+    },
+    "trace": {
+      "trace_id": "eaf14e4485864075aec039797501aca2",
+      "span_id": "9f629125fa90064c",
+      "parent_span_id": "923dcc257d229405",
+      "op": "http.server",
+      "status": "internal_error",
+      "type": "trace"
+    }
+  },
+  "culprit": "checkout",
+  "environment": "prod",
+  "extra": {
+    "inventory": {"hammer": 1, "nails": 1, "wrench": 0},
+    "sys.argv": ["/usr/local/bin/flask", "run", "--host=0.0.0.0", "-p", "8080"]
+  },
+  "grouping_config": {
+    "enhancements": "eJybzDhxY3J-bm5-npWRgaGlroGxrpHxBABcTQcY",
+    "id": "newstyle:2019-10-29"
+  },
+  "key_id": "1512188",
+  "level": "info",
+  "location": "checkout",
+  "logger": "",
+  "metadata": {"location": "checkout", "title": "checkout"},
+  "received": 1615837034.494997,
+  "request": {
+    "url": "http://aj-flask-m3uuizd7iq-uc.a.run.app/checkout",
+    "method": "POST",
+    "headers": [
+      ["Accept", "*/*"],
+      ["Accept-Encoding", "br, gzip, deflate"],
+      ["Accept-Language", "en-us"],
+      ["Content-Length", "526"],
+      ["Content-Type", "application/json"],
+      ["Email", "zllm@yahoo.com"],
+      ["Forwarded", "for=\"66.85.48.76\";proto=https"],
+      ["Host", "aj-flask-m3uuizd7iq-uc.a.run.app"],
+      ["Origin", "https://aj-react-m3uuizd7iq-uc.a.run.app"],
+      ["Referer", "https://aj-react-m3uuizd7iq-uc.a.run.app/toolstore"],
+      ["Sentry-Trace", "eaf14e4485864075aec039797501aca2-923dcc257d229405-1"],
+      [
+        "User-Agent",
+        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.1 Safari/605.1.15"
+      ],
+      ["X-Cloud-Trace-Context", "8ae7c9108753b53d5e22f0f3d51efc42/12016370555220220073"],
+      ["X-Forwarded-For", ""],
+      ["X-Forwarded-Proto", "https"]
+    ],
+    "env": {"SERVER_NAME": "0.0.0.0", "SERVER_PORT": "8080"},
+    "inferred_content_type": "application/json"
+  },
+  "sdk": {
+    "name": "sentry.python",
+    "version": "0.19.2",
+    "integrations": [
+      "argv",
+      "atexit",
+      "dedupe",
+      "excepthook",
+      "flask",
+      "logging",
+      "modules",
+      "sqlalchemy",
+      "stdlib",
+      "threading"
+    ],
+    "packages": [{"name": "pypi:sentry-sdk", "version": "0.19.2"}]
+  },
+  "spans": [
+    {
+      "timestamp": 1615837032.413307,
+      "start_timestamp": 1615837032.40376,
+      "op": "db function: get inventory",
+      "span_id": "958d7b57cb3fc89b",
+      "parent_span_id": "9f629125fa90064c",
+      "trace_id": "eaf14e4485864075aec039797501aca2",
+      "same_process_as_parent": true
+    },
+    {
+      "timestamp": 1615837032.403948,
+      "start_timestamp": 1615837032.403814,
+      "op": "connect to db",
+      "span_id": "95245228f8f39673",
+      "parent_span_id": "958d7b57cb3fc89b",
+      "trace_id": "eaf14e4485864075aec039797501aca2",
+      "same_process_as_parent": true
+    },
+    {
+      "timestamp": 1615837032.410861,
+      "start_timestamp": 1615837032.40399,
+      "op": "run query",
+      "span_id": "a567b19a48a61708",
+      "parent_span_id": "958d7b57cb3fc89b",
+      "trace_id": "eaf14e4485864075aec039797501aca2",
+      "same_process_as_parent": true
+    },
+    {
+      "timestamp": 1615837032.410461,
+      "start_timestamp": 1615837032.404122,
+      "description": "SELECT * FROM inventory",
+      "op": "db",
+      "span_id": "b9ef6d1b42cb7ce5",
+      "parent_span_id": "a567b19a48a61708",
+      "trace_id": "eaf14e4485864075aec039797501aca2",
+      "same_process_as_parent": true
+    },
+    {
+      "timestamp": 1615837034.449384,
+      "start_timestamp": 1615837032.413422,
+      "op": "process order",
+      "span_id": "9186b2cdef32d090",
+      "parent_span_id": "9f629125fa90064c",
+      "trace_id": "eaf14e4485864075aec039797501aca2",
+      "tags": {"status": "internal_error"},
+      "same_process_as_parent": true
+    }
+  ],
+  "start_timestamp": 1615837032.402719,
+  "timestamp": 1615837034.460185,
+  "title": "checkout",
+  "transaction": "checkout",
+  "type": "transaction",
+  "user": {"email": "zllm@yahoo.com"},
+  "version": "7"
+}

+ 1041 - 0
src/sentry/demo/data/react_transaction_1.json

@@ -0,0 +1,1041 @@
+{
+  "event_id": "d88832dfdcc44d4fb3e64f1e66877751",
+  "project": 5641783,
+  "release": "52f7c8a62a97cecf029f6a68adb411c1714c4836",
+  "dist": null,
+  "platform": "javascript",
+  "message": "",
+  "datetime": "2021-03-09T20:17:35.548000+00:00",
+  "tags": [
+    ["browser", "Safari 12.1.1"],
+    ["browser.name", "Safari"],
+    ["customerType", "enterprise"],
+    ["device", "Mac"],
+    ["device.family", "Mac"],
+    ["environment", "prod"],
+    ["level", "info"],
+    ["os", "Mac OS X 10.13.6"],
+    ["os.name", "Mac OS X"],
+    ["release", "52f7c8a62a97cecf029f6a68adb411c1714c4836"],
+    ["user", "email:6rp8@yahoo.com"],
+    ["session_id", "_z5x4maawt"],
+    ["transaction", "checkout"],
+    ["url", "https://aj-react-m3uuizd7iq-uc.a.run.app/toolstore"]
+  ],
+  "_meta": {
+    "breadcrumbs": {
+      "values": {
+        "1": {"event_id": {"": {"err": ["invalid_attribute"]}}},
+        "22": {"event_id": {"": {"err": ["invalid_attribute"]}}},
+        "43": {"event_id": {"": {"err": ["invalid_attribute"]}}}
+      }
+    }
+  },
+  "_metrics": {"bytes.ingested.event": 15306, "bytes.stored.event": 17780},
+  "breadcrumbs": {
+    "values": [
+      {
+        "timestamp": 1615321046.449,
+        "type": "info",
+        "category": "redux.action",
+        "level": "info",
+        "data": {"type": "@@redux/INITb.m.o.t.9.8"}
+      },
+      {
+        "timestamp": 1615321046.458,
+        "type": "default",
+        "category": "sentry.transaction",
+        "level": "info",
+        "message": "1342ce7328f047ea8883cc99848d2cb9",
+        "event_id": null
+      },
+      {
+        "timestamp": 1615321046.461,
+        "type": "default",
+        "category": "navigation",
+        "level": "info",
+        "data": {"from": "/", "to": "/toolstore"}
+      },
+      {
+        "timestamp": 1615321046.466,
+        "type": "default",
+        "category": "console",
+        "level": "info",
+        "message": "BACKEND is:  https://aj-flask-m3uuizd7iq-uc.a.run.app",
+        "data": {
+          "arguments": ["BACKEND is: ", "https://aj-flask-m3uuizd7iq-uc.a.run.app"],
+          "logger": "console"
+        }
+      },
+      {
+        "timestamp": 1615321046.468,
+        "type": "default",
+        "category": "console",
+        "level": "info",
+        "message": "BACKEND is:  https://aj-flask-m3uuizd7iq-uc.a.run.app",
+        "data": {
+          "arguments": ["BACKEND is: ", "https://aj-flask-m3uuizd7iq-uc.a.run.app"],
+          "logger": "console"
+        }
+      },
+      {
+        "timestamp": 1615321049.06,
+        "type": "http",
+        "category": "fetch",
+        "level": "info",
+        "data": {
+          "method": "GET",
+          "status_code": 200,
+          "url": "https://aj-flask-m3uuizd7iq-uc.a.run.app/tools"
+        }
+      },
+      {
+        "timestamp": 1615321049.071,
+        "type": "info",
+        "category": "redux.action",
+        "level": "info",
+        "data": {
+          "payload": {
+            "tools": [
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]",
+              "[Object]"
+            ]
+          },
+          "type": "SET_TOOLS"
+        }
+      },
+      {
+        "timestamp": 1615321049.153,
+        "type": "default",
+        "category": "console",
+        "level": "info",
+        "message": "%c prev state color: #9E9E9E; font-weight: bold [object Object]",
+        "data": {
+          "arguments": [
+            "%c prev state",
+            "color: #9E9E9E; font-weight: bold",
+            {"cart": "[Array]", "tools": "[Array]"}
+          ],
+          "logger": "console"
+        }
+      },
+      {
+        "timestamp": 1615321049.153,
+        "type": "default",
+        "category": "console",
+        "level": "info",
+        "message": "%c action     color: #03A9F4; font-weight: bold [object Object]",
+        "data": {
+          "arguments": [
+            "%c action    ",
+            "color: #03A9F4; font-weight: bold",
+            {"payload": "[Object]", "type": "SET_TOOLS"}
+          ],
+          "logger": "console"
+        }
+      },
+      {
+        "timestamp": 1615321049.153,
+        "type": "default",
+        "category": "console",
+        "level": "info",
+        "message": "%c next state color: #4CAF50; font-weight: bold [object Object]",
+        "data": {
+          "arguments": [
+            "%c next state",
+            "color: #4CAF50; font-weight: bold",
+            {"cart": "[Array]", "tools": "[Array]"}
+          ],
+          "logger": "console"
+        }
+      },
+      {
+        "timestamp": 1615321049.522,
+        "type": "default",
+        "category": "ui.click",
+        "level": "info",
+        "message": "tbody > tr > td.item > div.button-wrapper > button"
+      },
+      {
+        "timestamp": 1615321049.523,
+        "type": "info",
+        "category": "redux.action",
+        "level": "info",
+        "data": {
+          "payload": {
+            "tool": {
+              "id": 1,
+              "image": "/static/media/wrench.0371ec11.png",
+              "name": "allen wrench",
+              "price": 5672,
+              "sku": "asyqtzmrhsabqxri",
+              "type": "wrench"
+            }
+          },
+          "type": "ADD_TOOL"
+        }
+      },
+      {
+        "timestamp": 1615321049.524,
+        "type": "default",
+        "category": "console",
+        "level": "info",
+        "message": "%c prev state color: #9E9E9E; font-weight: bold [object Object]",
+        "data": {
+          "arguments": [
+            "%c prev state",
+            "color: #9E9E9E; font-weight: bold",
+            {"cart": "[Array]", "tools": "[Array]"}
+          ],
+          "logger": "console"
+        }
+      },
+      {
+        "timestamp": 1615321049.524,
+        "type": "default",
+        "category": "console",
+        "level": "info",
+        "message": "%c action     color: #03A9F4; font-weight: bold [object Object]",
+        "data": {
+          "arguments": [
+            "%c action    ",
+            "color: #03A9F4; font-weight: bold",
+            {"payload": "[Object]", "type": "ADD_TOOL"}
+          ],
+          "logger": "console"
+        }
+      },
+      {
+        "timestamp": 1615321049.524,
+        "type": "default",
+        "category": "console",
+        "level": "info",
+        "message": "%c next state color: #4CAF50; font-weight: bold [object Object]",
+        "data": {
+          "arguments": [
+            "%c next state",
+            "color: #4CAF50; font-weight: bold",
+            {"cart": "[Array]", "tools": "[Array]"}
+          ],
+          "logger": "console"
+        }
+      },
+      {
+        "timestamp": 1615321049.524,
+        "type": "default",
+        "category": "cart",
+        "level": "info",
+        "message": "User added allen wrench to cart"
+      },
+      {
+        "timestamp": 1615321049.913,
+        "type": "default",
+        "category": "ui.click",
+        "level": "info",
+        "message": "tbody > tr > td.item > div.button-wrapper > button"
+      },
+      {
+        "timestamp": 1615321049.914,
+        "type": "info",
+        "category": "redux.action",
+        "level": "info",
+        "data": {
+          "payload": {
+            "tool": {
+              "id": 1,
+              "image": "/static/media/wrench.0371ec11.png",
+              "name": "allen wrench",
+              "price": 5672,
+              "sku": "asyqtzmrhsabqxri",
+              "type": "wrench"
+            }
+          },
+          "type": "ADD_TOOL"
+        }
+      },
+      {
+        "timestamp": 1615321049.915,
+        "type": "default",
+        "category": "console",
+        "level": "info",
+        "message": "%c prev state color: #9E9E9E; font-weight: bold [object Object]",
+        "data": {
+          "arguments": [
+            "%c prev state",
+            "color: #9E9E9E; font-weight: bold",
+            {"cart": "[Array]", "tools": "[Array]"}
+          ],
+          "logger": "console"
+        }
+      },
+      {
+        "timestamp": 1615321049.915,
+        "type": "default",
+        "category": "console",
+        "level": "info",
+        "message": "%c action     color: #03A9F4; font-weight: bold [object Object]",
+        "data": {
+          "arguments": [
+            "%c action    ",
+            "color: #03A9F4; font-weight: bold",
+            {"payload": "[Object]", "type": "ADD_TOOL"}
+          ],
+          "logger": "console"
+        }
+      },
+      {
+        "timestamp": 1615321049.915,
+        "type": "default",
+        "category": "console",
+        "level": "info",
+        "message": "%c next state color: #4CAF50; font-weight: bold [object Object]",
+        "data": {
+          "arguments": [
+            "%c next state",
+            "color: #4CAF50; font-weight: bold",
+            {"cart": "[Array]", "tools": "[Array]"}
+          ],
+          "logger": "console"
+        }
+      },
+      {
+        "timestamp": 1615321049.915,
+        "type": "default",
+        "category": "cart",
+        "level": "info",
+        "message": "User added allen wrench to cart"
+      },
+      {
+        "timestamp": 1615321050.069,
+        "type": "default",
+        "category": "sentry.transaction",
+        "level": "info",
+        "message": "602eb470c3a44f87a4c5871df40a3ada",
+        "event_id": null
+      },
+      {
+        "timestamp": 1615321050.283,
+        "type": "default",
+        "category": "ui.click",
+        "level": "info",
+        "message": "tbody > tr > td.item > div.button-wrapper > button"
+      },
+      {
+        "timestamp": 1615321050.284,
+        "type": "info",
+        "category": "redux.action",
+        "level": "info",
+        "data": {
+          "payload": {
+            "tool": {
+              "id": 1,
+              "image": "/static/media/wrench.0371ec11.png",
+              "name": "allen wrench",
+              "price": 5672,
+              "sku": "asyqtzmrhsabqxri",
+              "type": "wrench"
+            }
+          },
+          "type": "ADD_TOOL"
+        }
+      },
+      {
+        "timestamp": 1615321050.284,
+        "type": "default",
+        "category": "console",
+        "level": "info",
+        "message": "%c prev state color: #9E9E9E; font-weight: bold [object Object]",
+        "data": {
+          "arguments": [
+            "%c prev state",
+            "color: #9E9E9E; font-weight: bold",
+            {"cart": "[Array]", "tools": "[Array]"}
+          ],
+          "logger": "console"
+        }
+      },
+      {
+        "timestamp": 1615321050.284,
+        "type": "default",
+        "category": "console",
+        "level": "info",
+        "message": "%c action     color: #03A9F4; font-weight: bold [object Object]",
+        "data": {
+          "arguments": [
+            "%c action    ",
+            "color: #03A9F4; font-weight: bold",
+            {"payload": "[Object]", "type": "ADD_TOOL"}
+          ],
+          "logger": "console"
+        }
+      },
+      {
+        "timestamp": 1615321050.284,
+        "type": "default",
+        "category": "console",
+        "level": "info",
+        "message": "%c next state color: #4CAF50; font-weight: bold [object Object]",
+        "data": {
+          "arguments": [
+            "%c next state",
+            "color: #4CAF50; font-weight: bold",
+            {"cart": "[Array]", "tools": "[Array]"}
+          ],
+          "logger": "console"
+        }
+      },
+      {
+        "timestamp": 1615321050.284,
+        "type": "default",
+        "category": "cart",
+        "level": "info",
+        "message": "User added allen wrench to cart"
+      },
+      {
+        "timestamp": 1615321050.638,
+        "type": "default",
+        "category": "ui.click",
+        "level": "info",
+        "message": "tbody > tr > td.item > div.button-wrapper > button"
+      },
+      {
+        "timestamp": 1615321050.638,
+        "type": "info",
+        "category": "redux.action",
+        "level": "info",
+        "data": {
+          "payload": {
+            "tool": {
+              "id": 1,
+              "image": "/static/media/wrench.0371ec11.png",
+              "name": "allen wrench",
+              "price": 5672,
+              "sku": "asyqtzmrhsabqxri",
+              "type": "wrench"
+            }
+          },
+          "type": "ADD_TOOL"
+        }
+      },
+      {
+        "timestamp": 1615321050.639,
+        "type": "default",
+        "category": "console",
+        "level": "info",
+        "message": "%c prev state color: #9E9E9E; font-weight: bold [object Object]",
+        "data": {
+          "arguments": [
+            "%c prev state",
+            "color: #9E9E9E; font-weight: bold",
+            {"cart": "[Array]", "tools": "[Array]"}
+          ],
+          "logger": "console"
+        }
+      },
+      {
+        "timestamp": 1615321050.639,
+        "type": "default",
+        "category": "console",
+        "level": "info",
+        "message": "%c action     color: #03A9F4; font-weight: bold [object Object]",
+        "data": {
+          "arguments": [
+            "%c action    ",
+            "color: #03A9F4; font-weight: bold",
+            {"payload": "[Object]", "type": "ADD_TOOL"}
+          ],
+          "logger": "console"
+        }
+      },
+      {
+        "timestamp": 1615321050.639,
+        "type": "default",
+        "category": "console",
+        "level": "info",
+        "message": "%c next state color: #4CAF50; font-weight: bold [object Object]",
+        "data": {
+          "arguments": [
+            "%c next state",
+            "color: #4CAF50; font-weight: bold",
+            {"cart": "[Array]", "tools": "[Array]"}
+          ],
+          "logger": "console"
+        }
+      },
+      {
+        "timestamp": 1615321050.639,
+        "type": "default",
+        "category": "cart",
+        "level": "info",
+        "message": "User added allen wrench to cart"
+      },
+      {
+        "timestamp": 1615321051.048,
+        "type": "default",
+        "category": "ui.click",
+        "level": "info",
+        "message": "tbody > tr > td.item > div.button-wrapper > button"
+      },
+      {
+        "timestamp": 1615321051.049,
+        "type": "info",
+        "category": "redux.action",
+        "level": "info",
+        "data": {
+          "payload": {
+            "tool": {
+              "id": 1,
+              "image": "/static/media/wrench.0371ec11.png",
+              "name": "allen wrench",
+              "price": 5672,
+              "sku": "asyqtzmrhsabqxri",
+              "type": "wrench"
+            }
+          },
+          "type": "ADD_TOOL"
+        }
+      },
+      {
+        "timestamp": 1615321051.049,
+        "type": "default",
+        "category": "console",
+        "level": "info",
+        "message": "%c prev state color: #9E9E9E; font-weight: bold [object Object]",
+        "data": {
+          "arguments": [
+            "%c prev state",
+            "color: #9E9E9E; font-weight: bold",
+            {"cart": "[Array]", "tools": "[Array]"}
+          ],
+          "logger": "console"
+        }
+      },
+      {
+        "timestamp": 1615321051.049,
+        "type": "default",
+        "category": "console",
+        "level": "info",
+        "message": "%c action     color: #03A9F4; font-weight: bold [object Object]",
+        "data": {
+          "arguments": [
+            "%c action    ",
+            "color: #03A9F4; font-weight: bold",
+            {"payload": "[Object]", "type": "ADD_TOOL"}
+          ],
+          "logger": "console"
+        }
+      },
+      {
+        "timestamp": 1615321051.049,
+        "type": "default",
+        "category": "console",
+        "level": "info",
+        "message": "%c next state color: #4CAF50; font-weight: bold [object Object]",
+        "data": {
+          "arguments": [
+            "%c next state",
+            "color: #4CAF50; font-weight: bold",
+            {"cart": "[Array]", "tools": "[Array]"}
+          ],
+          "logger": "console"
+        }
+      },
+      {
+        "timestamp": 1615321051.049,
+        "type": "default",
+        "category": "cart",
+        "level": "info",
+        "message": "User added allen wrench to cart"
+      },
+      {
+        "timestamp": 1615321051.709,
+        "type": "default",
+        "category": "ui.click",
+        "level": "info",
+        "message": "div#root.root > div > div.App > div.sidebar > button"
+      },
+      {
+        "timestamp": 1615321055.539,
+        "type": "http",
+        "category": "fetch",
+        "level": "info",
+        "data": {
+          "method": "POST",
+          "status_code": 500,
+          "url": "https://aj-flask-m3uuizd7iq-uc.a.run.app/checkout"
+        }
+      },
+      {
+        "timestamp": 1615321055.545,
+        "type": "default",
+        "category": "sentry.event",
+        "level": "error",
+        "message": "Error: 500 - INTERNAL SERVER ERROR",
+        "event_id": null
+      }
+    ]
+  },
+  "contexts": {
+    "browser": {"name": "Safari", "version": "12.1.1", "type": "browser"},
+    "device": {"family": "Mac", "model": "Mac", "brand": "Apple", "type": "device"},
+    "os": {"name": "Mac OS X", "version": "10.13.6", "type": "os"},
+    "redux.state": {
+      "cart": ["[Object]", "[Object]", "[Object]", "[Object]", "[Object]"],
+      "tools": [
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]",
+        "[Object]"
+      ],
+      "type": "redux.state"
+    },
+    "trace": {
+      "trace_id": "39ecb9f463bb4982a6d76e426915d3ab",
+      "span_id": "ac7e3ef6047fd004",
+      "op": "default",
+      "type": "trace"
+    }
+  },
+  "culprit": "checkout",
+  "environment": "prod",
+  "errors": [
+    {"type": "invalid_attribute", "name": "breadcrumbs.values.1.event_id"},
+    {"type": "invalid_attribute", "name": "breadcrumbs.values.22.event_id"},
+    {"type": "invalid_attribute", "name": "breadcrumbs.values.43.event_id"}
+  ],
+  "extra": {
+    "cart": "[{\"sku\":\"asyqtzmrhsabqxri\",\"name\":\"allen wrench\",\"image\":\"/static/media/wrench.0371ec11.png\",\"id\":1,\"type\":\"wrench\",\"price\":5672},{\"sku\":\"asyqtzmrhsabqxri\",\"name\":\"allen wrench\",\"image\":\"/static/media/wrench.0371ec11.png\",\"id\":1,\"type\":\"wrench\",\"price\":5672},{\"sku\":\"asyqtzmrhsabqxri\",\"name\":\"allen wrench\",\"image\":\"/static/media/wrench.0371ec11.png\",\"id\":1,\"type\":\"wrench\",\"price\":5672},{\"sku\":\"asyqtzmrhsabqxri\",\"name\":\"allen wrench\",\"image\":\"/static/media/wrench.0371ec11.png\",\"id\":1,\"type\":\"wrench\",\"price\":5672}]"
+  },
+  "grouping_config": {
+    "enhancements": "eJybzDhxY3J-bm5-npWRgaGlroGxrpHxBABcTQcY",
+    "id": "newstyle:2019-10-29"
+  },
+  "key_id": "1512182",
+  "level": "info",
+  "location": "checkout",
+  "logger": "",
+  "metadata": {"location": "checkout", "title": "checkout"},
+  "received": 1615321043.385451,
+  "request": {
+    "url": "https://aj-react-m3uuizd7iq-uc.a.run.app/toolstore",
+    "headers": [
+      [
+        "User-Agent",
+        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.1 Safari/605.1.15"
+      ]
+    ]
+  },
+  "sdk": {
+    "name": "sentry.javascript.react",
+    "version": "5.27.3",
+    "integrations": [
+      "InboundFilters",
+      "FunctionToString",
+      "TryCatch",
+      "Breadcrumbs",
+      "GlobalHandlers",
+      "LinkedErrors",
+      "UserAgent",
+      "BrowserTracing"
+    ],
+    "packages": [
+      {"name": "npm:@sentry/browser", "version": "5.27.3"},
+      {"name": "npm:@sentry/react", "version": "5.27.3"}
+    ]
+  },
+  "spans": [
+    {
+      "timestamp": 1615321055.54,
+      "start_timestamp": 1615321051.711,
+      "description": "POST https://aj-flask-m3uuizd7iq-uc.a.run.app/checkout",
+      "op": "http",
+      "span_id": "a2254b6b8281d31d",
+      "parent_span_id": "ac7e3ef6047fd004",
+      "trace_id": "39ecb9f463bb4982a6d76e426915d3ab",
+      "status": "internal_error",
+      "tags": {"http.status_code": "500"},
+      "data": {
+        "method": "POST",
+        "type": "fetch",
+        "url": "https://aj-flask-m3uuizd7iq-uc.a.run.app/checkout"
+      }
+    },
+    {
+      "timestamp": 1615321055.548,
+      "start_timestamp": 1615321055.548,
+      "description": "processing shopping cart result",
+      "op": "task",
+      "span_id": "a5fa70f3f5ea8ecc",
+      "parent_span_id": "ac7e3ef6047fd004",
+      "trace_id": "39ecb9f463bb4982a6d76e426915d3ab",
+      "data": {"httpResponseData": "500 - "}
+    }
+  ],
+  "start_timestamp": 1615321051.711,
+  "timestamp": 1615321055.548,
+  "title": "checkout",
+  "transaction": "checkout",
+  "type": "transaction",
+  "user": {
+    "email": "6rp8@yahoo.com",
+    "ip_address": "66.85.48.78",
+    "geo": {"country_code": "US", "city": "Santa Clara", "region": "United States"}
+  },
+  "version": "7"
+}

+ 312 - 48
src/sentry/demo/data_population.py

@@ -1,29 +1,47 @@
+import copy
 import logging
 import functools
 import random
-from typing import (
-    List,
-    Callable,
-)
+import pytz
+import time
 
+from collections import defaultdict
 from datetime import timedelta
+from django.conf import settings
 from django.utils import timezone
+from uuid import uuid4
+from typing import List
+
 from sentry.interfaces.user import User as UserInterface
 from sentry.models import Project
 from sentry.utils import json
-from sentry.utils.samples import random_geo, random_ip, create_sample_event
+from sentry.utils.dates import to_timestamp
+from sentry.utils.samples import (
+    random_geo,
+    random_ip,
+    create_sample_event_basic,
+    random_normal,
+)
 from sentry.utils.snuba import SnubaError
 
 
-MAX_DAYS = 2
-SCALE_FACTOR = 0.5
-BASE_OFFSET = 0.5
-NAME_STEP_SIZE = 20
+MAX_DAYS = settings.DEMO_DATA_GEN_PARAMS["MAX_DAYS"]
+SCALE_FACTOR = settings.DEMO_DATA_GEN_PARAMS["SCALE_FACTOR"]
+BASE_OFFSET = settings.DEMO_DATA_GEN_PARAMS["BASE_OFFSET"]
+NAME_STEP_SIZE = settings.DEMO_DATA_GEN_PARAMS["NAME_STEP_SIZE"]
+BREADCRUMB_LOOKBACK_TIME = settings.DEMO_DATA_GEN_PARAMS["BREADCRUMB_LOOKBACK_TIME"]
+DEFAULT_BACKOFF_TIME = settings.DEMO_DATA_GEN_PARAMS["DEFAULT_BACKOFF_TIME"]
+ERROR_BACKOFF_TIME = settings.DEMO_DATA_GEN_PARAMS["ERROR_BACKOFF_TIME"]
 
 
 logger = logging.getLogger(__name__)
 
 
+def get_event_from_file(file_path):
+    with open(file_path) as f:
+        return clean_event(json.load(f))
+
+
 def distribution_v1(hour: int) -> int:
     if hour > 9 and hour < 12:
         return 8
@@ -87,55 +105,301 @@ def generate_user():
     return get_user_by_id(id_0_offset)
 
 
-def populate_event_on_project(
-    project: Project, file_path: str, dist_function: Callable[[int], int]
-) -> None:
-    with open(file_path) as f:
-        error_json = json.load(f)
+def safe_send_event(data):
+    project = data.pop("project")
+    # TODO: make a batched update version of create_sample_event
+    try:
+        create_sample_event_basic(data, project.id)
+        time.sleep(DEFAULT_BACKOFF_TIME)
+    except SnubaError:
+        # if snuba fails, just back off and continue
+        logger.info("safe_send_event.snuba_error")
+        time.sleep(ERROR_BACKOFF_TIME)
+
 
+def clean_event(event_json):
     # clear out these fields if they exist
-    fields_to_delete = ["datetime", "location", "title", "event_id", "project"]
+    fields_to_delete = [
+        "datetime",
+        "timestamp",
+        "start_timestamp",
+        "location",
+        "title",
+        "event_id",
+        "project",
+    ]
     for field in fields_to_delete:
-        if field in error_json:
-            del error_json[field]
+        if field in event_json:
+            del event_json[field]
+
+        # delete in spans as well
+        for span in event_json.get("spans", []):
+            if field in span:
+                del span[field]
+
+    # delete request header since they have data that won't match
+    # the generated data
+    request = event_json.get("request")
+    if request and "headers" in request:
+        del request["headers"]
+
+    return event_json
+
+
+def fix_timestamps(event_json):
+    """
+    Convert a time zone aware datetime timestamps to a POSIX timestamp
+    for an evnet
+    """
+    event_json["timestamp"] = to_timestamp(event_json["timestamp"])
+    start_timestamp = event_json.get("start_timestamp")
+    if start_timestamp:
+        event_json["start_timestamp"] = to_timestamp(start_timestamp)
+
+
+def fix_error_event(event_json):
+    fix_timestamps(event_json)
+    fix_breadrumbs(event_json)
+
+
+def fix_transaction_event(event_json, old_span_id):
+    fix_timestamps(event_json)
+    fix_spans(event_json, old_span_id)
+    fix_measurements(event_json)
+
+
+def fix_spans(event_json, old_span_id):
+    """
+    This function does the folowing:
+    1. Give spans fresh span_ids & update the parent span ids accordingly
+    2. Update span offsets and durations based on transaction duration and some randomness
+    """
+    trace = event_json["contexts"]["trace"]
+    new_span_id = trace["span_id"]
+    trace_id = trace["trace_id"]
+
+    update_id_map = {old_span_id: new_span_id}
+    spans = event_json.get("spans", [])
+
+    full_duration = event_json["timestamp"] - event_json["start_timestamp"]
+
+    while True:
+        found_any = False
+        for span in spans:
+            new_parent_id = update_id_map.get(span["parent_span_id"])
+            if new_parent_id:
+                # set the new parent
+                span["parent_span_id"] = new_parent_id
+
+                # generate a new id and set the replacement mappping
+                new_id = uuid4().hex[:16]
+                update_id_map[span["span_id"]] = new_id
+
+                # update the spn
+                span["span_id"] = new_id
+
+                found_any = True
+
+        # quit if we didn't make any updates
+        if not found_any:
+            break
+
+    # now update every trace id
+    for span in spans:
+        span["trace_id"] = trace_id
+
+    # create a tree of children and a hashmap of the span by the ID
+    tree = defaultdict(list)
+    id_map = {}
+    for span in spans:
+        tree[span["parent_span_id"]].append(span)
+        id_map[span["span_id"]] = span
+
+    id_list = [new_span_id]
+    while id_list:
+        span_id = id_list.pop()
+        children = tree.get(span_id, [])
+
+        # figure out the offset of the parent span and the end time of the span
+        if span_id == new_span_id:
+            span_offset = 0
+            parent_duration = full_duration
+            end_of_parent_span = full_duration
+        else:
+            parent_span = id_map[span_id]
+            span_offset = parent_span["data"]["offset"]
+            parent_duration = parent_span["data"]["duration"]
+
+        # end time of the parent span is the offset + duration
+        end_of_parent_span = span_offset + parent_duration
+
+        num_children = len(children)
+        avg_span_length = parent_duration / max(num_children, 1)
+
+        # order each span with the same parent sequentially in time
+        for i, span in enumerate(children):
+            if "data" not in span:
+                span["data"] = {}
+
+            span["data"]["offset"] = span_offset
+            remaining_time = end_of_parent_span - span_offset
+            # if we are the last child of a span, then
+            last_index = num_children - 1
+            if i == last_index:
+                duration = remaining_time
+            else:
+                # the max duration should give some breathging room to the remaining spans
+                max_duration = remaining_time - (avg_span_length / 4.0) * (last_index - i)
+                # pick a random length for the span that's at most 2x the average span length
+                duration = min(max_duration, random.uniform(0, 2 * avg_span_length))
+            span["data"]["duration"] = duration
+            span["start_timestamp"] = event_json["start_timestamp"] + span_offset
+            span.setdefault("timestamp", span["start_timestamp"] + duration)
+            # calcualate the next span offset
+            span_offset = duration + span_offset
+            id_list.append(span["span_id"])
+
+
+def fix_measurements(event_json):
+    """
+    Convert measurment data from durations into timestamps
+    """
+    measurements = event_json.get("measurements")
+
+    if measurements:
+        measurement_markers = {}
+        for key, entry in measurements.items():
+            if key in ["fp", "fcp", "lcp", "fid"]:
+                measurement_markers[f"mark.{key}"] = {
+                    "value": round(event_json["start_timestamp"] + entry["value"] / 1000, 3)
+                }
+        measurements.update(measurement_markers)
+
+
+def fix_breadrumbs(event_json):
+    """
+    Fixes the timestamps on breadcrumbs to match the current time
+    Evenly spaces out all breadcrumbs starting at BREADCRUMB_LOOKBACK_TIME ago
+    """
+    breadcrumbs = event_json.get("breadcrumbs", {}).get("values", [])
+    num_breadcrumbs = len(breadcrumbs)
+    breadcrumb_time_step = BREADCRUMB_LOOKBACK_TIME * 1.0 / num_breadcrumbs
+
+    curr_time = event_json["timestamp"] - BREADCRUMB_LOOKBACK_TIME
+    for breadcrumb in breadcrumbs:
+        breadcrumb["timestamp"] = curr_time
+        curr_time += breadcrumb_time_step
+
+
+def populate_connected_event_scenario_1(react_project: Project, python_project: Project):
+    """
+    This function populates a set of four related events with the same trace id:
+    - Front-end transaction
+    - Front-end error
+    - Back-end transaction
+    - Back-end error
+    Occurrance times and durations are randomized
+    """
+    react_transaction = get_event_from_file("src/sentry/demo/data/react_transaction_1.json")
+    react_error = get_event_from_file("src/sentry/demo/data/react_error_1.json")
+    python_transaction = get_event_from_file("src/sentry/demo/data/python_transaction_1.json")
+    python_error = get_event_from_file("src/sentry/demo/data/python_error_1.json")
 
     for day in range(MAX_DAYS):
         for hour in range(24):
-            base = dist_function(hour)
+            base = distribution_v1(hour)
             # determine the number of events we want in this hour
             num_events = int((BASE_OFFSET + SCALE_FACTOR * base) * random.uniform(0.6, 1.0))
             for i in range(num_events):
-                # pick the minutes randomly (which means events will received be out of order)
+                # pick the minutes randomly (which means events will sent be out of order)
                 minute = random.randint(0, 60)
                 timestamp = timezone.now() - timedelta(days=day, hours=hour, minutes=minute)
-                local_error = error_json.copy()
-                local_error.update(
-                    project=project,
-                    platform=project.platform,
+                timestamp = timestamp.replace(tzinfo=pytz.utc)
+                transaction_user = generate_user()
+                trace_id = uuid4().hex
+
+                old_span_id = react_transaction["contexts"]["trace"]["span_id"]
+                frontend_root_span_id = uuid4().hex[:16]
+                frontend_duration = random_normal(2000 - 50 * day, 250, 1000) / 1000.0
+
+                frontend_context = {
+                    "trace": {
+                        "type": "trace",
+                        "trace_id": trace_id,
+                        "span_id": frontend_root_span_id,
+                    }
+                }
+
+                # React transaction
+                local_event = copy.deepcopy(react_transaction)
+                local_event.update(
+                    project=react_project,
+                    platform=react_project.platform,
+                    event_id=uuid4().hex,
+                    user=transaction_user,
+                    timestamp=timestamp,
+                    # start_timestamp decreases based on day so that there's a trend
+                    start_timestamp=timestamp - timedelta(seconds=frontend_duration),
+                    measurements={
+                        "fp": {"value": random_normal(1250 - 50 * day, 200, 500)},
+                        "fcp": {"value": random_normal(1250 - 50 * day, 200, 500)},
+                        "lcp": {"value": random_normal(2800 - 50 * day, 400, 2000)},
+                        "fid": {"value": random_normal(5 - 0.125 * day, 2, 1)},
+                    },
+                    contexts=frontend_context,
+                )
+
+                fix_transaction_event(local_event, old_span_id)
+                safe_send_event(local_event)
+
+                # note picking the 0th span is arbitrary
+                backend_parent_id = local_event["spans"][0]["span_id"]
+
+                # React error
+                local_event = copy.deepcopy(react_error)
+                local_event.update(
+                    project=react_project,
+                    platform=react_project.platform,
+                    timestamp=timestamp,
+                    user=transaction_user,
+                    contexts=frontend_context,
+                )
+                fix_error_event(local_event)
+                safe_send_event(local_event)
+
+                # python transaction
+                old_span_id = python_transaction["contexts"]["trace"]["span_id"]
+                backend_duration = random_normal(1500 + 50 * day, 250, 500)
+
+                backend_context = {
+                    "trace": {
+                        "type": "trace",
+                        "trace_id": trace_id,
+                        "span_id": uuid4().hex[:16],
+                        "parent_span_id": backend_parent_id,
+                    }
+                }
+
+                local_event = copy.deepcopy(python_transaction)
+                local_event.update(
+                    project=python_project,
+                    platform=python_project.platform,
+                    timestamp=timestamp,
+                    start_timestamp=timestamp - timedelta(milliseconds=backend_duration),
+                    user=transaction_user,
+                    contexts=backend_context,
+                )
+                fix_transaction_event(local_event, old_span_id)
+                safe_send_event(local_event)
+
+                # python error
+                local_event = copy.deepcopy(python_error)
+                local_event.update(
+                    project=python_project,
+                    platform=python_project.platform,
                     timestamp=timestamp,
-                    user=generate_user(),
+                    user=transaction_user,
+                    contexts=backend_context,
                 )
-                # snuba might fail but what can we do ¯\_(ツ)_/¯
-                # TODO: make a batched update version of create_sample_event
-                try:
-                    create_sample_event(
-                        **local_error,
-                    )
-                except SnubaError:
-                    logger.info("populate_event_on_project.snuba_error")
-                    pass
-
-
-def populate_python_project(project: Project):
-    populate_event_on_project(
-        project, "src/sentry/demo/data/python_error_1.json", distrubtion_fns[2]
-    )
-
-
-def populate_react_project(project: Project):
-    populate_event_on_project(
-        project, "src/sentry/demo/data/react_error_1.json", distrubtion_fns[0]
-    )
-    populate_event_on_project(
-        project, "src/sentry/demo/data/react_error_2.json", distrubtion_fns[1]
-    )
+                fix_error_event(local_event)
+                safe_send_event(local_event)

+ 11 - 4
src/sentry/demo/demo_org_manager.py

@@ -1,5 +1,6 @@
 from django.conf import settings
 from django.db import transaction
+from django.db.models import F
 from django.template.defaultfilters import slugify
 from typing import Tuple
 
@@ -15,7 +16,9 @@ from sentry.models import (
 )
 from sentry.utils.email import create_fake_email
 
-from .data_population import populate_python_project, populate_react_project
+from .data_population import (
+    populate_connected_event_scenario_1,
+)
 from .utils import NoDemoOrgReady, generate_random_name
 from .models import DemoUser, DemoOrganization, DemoOrgStatus
 
@@ -23,7 +26,6 @@ from .models import DemoUser, DemoOrganization, DemoOrgStatus
 def create_demo_org() -> Organization:
     # wrap the main org setup in transaction
     with transaction.atomic():
-        # TODO: add way to ensure we generate unique petnames
         name = generate_random_name()
 
         slug = slugify(name)
@@ -44,8 +46,13 @@ def create_demo_org() -> Organization:
         # delete all DSNs for the org so people don't send events
         ProjectKey.objects.filter(project__organization=org).delete()
 
-    populate_python_project(python_project)
-    populate_react_project(react_project)
+        Project.objects.filter(organization=org).update(
+            flags=F("flags").bitor(Project.flags.has_transactions)
+        )
+
+    # TODO: delete org if data population fails
+
+    populate_connected_event_scenario_1(react_project, python_project)
 
     return org
 

+ 11 - 1
src/sentry/utils/samples.py

@@ -16,6 +16,13 @@ from sentry.utils.canonical import CanonicalKeyDict
 epoch = datetime.utcfromtimestamp(0)
 
 
+def random_normal(mu, sigma, minimum, maximum=None):
+    random_value = max(random.normalvariate(mu, sigma), minimum)
+    if maximum is not None:
+        random_value = min(random_value, maximum)
+    return random_value
+
+
 def milliseconds_ago(now, milliseconds):
     ago = now - timedelta(milliseconds=milliseconds)
     return (ago - epoch).total_seconds()
@@ -289,7 +296,10 @@ def create_sample_event(
         data["contexts"]["trace"]["parent_span_id"] = kwargs.pop("parent_span_id")
 
     data.update(kwargs)
+    return create_sample_event_basic(data, project.id, raw=raw)
+
 
+def create_sample_event_basic(data, project_id, raw=True):
     manager = EventManager(data)
     manager.normalize()
-    return manager.save(project.id, raw=raw)
+    return manager.save(project_id, raw=raw)