mike boone commited on
Commit
ef5b239
Β·
1 Parent(s): 7a5c5fe

fix: identify e2e runs from final model URL

Browse files
Files changed (1) hide show
  1. tests/e2e_quality.py +69 -90
tests/e2e_quality.py CHANGED
@@ -437,8 +437,8 @@ def submit_job(page: Page, test_case: dict):
437
  url_el.fill(test_case["company_url"])
438
  page.wait_for_timeout(300)
439
 
440
- # Generate a unique tag for this test case β€” logged by the app at session init
441
- # so fetch_run_diagnostics can find this session without any guessing
442
  test_tag = f"TR-{uuid.uuid4().hex[:8].upper()}"
443
  test_case["_test_tag"] = test_tag # store so run_single_test can pass it to diagnostics
444
 
@@ -504,17 +504,28 @@ def pipeline_finished(stages: dict) -> bool:
504
  def extract_run_context(page: Page) -> dict:
505
  """
506
  After completion, find model and liveboard URLs in the page.
507
- Uses full HTML content so GUIDs in href attributes are also matched.
 
508
  """
509
- body = page.content() # full HTML β€” catches GUIDs in href attrs too
 
 
 
 
510
 
511
  model_match = re.search(r'(https://[^\s"\'<>#]+)/#/data/tables/([a-f0-9-]{36})', body)
512
  lb_match = re.search(r'(https://[^\s"\'<>#]+)/#/pinboard/([a-f0-9-]{36})', body)
 
 
 
 
 
513
 
514
  return {
515
- "ts_base_url": model_match.group(1) if model_match else None,
516
  "model_guid": model_match.group(2) if model_match else None,
517
  "liveboard_guid": lb_match.group(2) if lb_match else None,
 
518
  }
519
 
520
 
@@ -610,6 +621,26 @@ def extract_db_schema(model_tml_str: str) -> tuple:
610
  return "", ""
611
 
612
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
613
  def get_snowflake_sample(db: str, schema: str) -> str:
614
  try:
615
  from snowflake_auth import get_snowflake_connection
@@ -804,7 +835,7 @@ def run_ai_grading(run_context: dict, company: str, vertical: str, line: str, fu
804
  db, schema = extract_db_schema(model_tml)
805
  if (not db or not schema) and schema_override:
806
  db, schema = "DEMOBUILD", schema_override
807
- print(f" ℹ️ Using schema from session_logs: {schema_override}")
808
  sample = get_snowflake_sample(db, schema) if db and schema else "Could not determine db/schema"
809
  print(" πŸ€– Grading data quality...")
810
  dg = grade_data_quality(company, vertical, line, function, model_tml, sample)
@@ -1140,8 +1171,8 @@ def compute_grade(score: float, config: dict) -> str:
1140
  def fetch_run_diagnostics(start_time: float, company: str = "", test_tag: str = "") -> dict:
1141
  """
1142
  Query session_logs for entries by testrunner after start_time.
1143
- If test_tag is provided (preferred), matches the session by exact tag lookup.
1144
- Falls back to company prefix matching when no tag is available.
1145
  """
1146
  try:
1147
  from supabase_client import SupabaseSettings
@@ -1169,57 +1200,16 @@ def fetch_run_diagnostics(start_time: float, company: str = "", test_tag: str =
1169
  sid = log["session_id"]
1170
  sessions.setdefault(sid, []).append(log)
1171
 
1172
- # Derive expected 3-letter company prefix from company name/URL
1173
- # e.g. "delta.com" β†’ "DEL", "Wells Fargo" β†’ "WEL"
1174
- company_prefix = ""
1175
- if company:
1176
- name = company.lower().replace(".com", "").replace(".", "").replace(" ", "")
1177
- company_prefix = name[:3].upper()
1178
-
1179
- def schema_for_session(session_entries):
1180
- for l in session_entries:
1181
- schema = (l.get("meta") or {}).get("schema", "")
1182
- if schema:
1183
- return schema
1184
- return ""
1185
-
1186
- # 1. Exact tag match β€” most reliable, no guessing
1187
  if test_tag:
1188
  tag_matching = {
1189
  sid: entries for sid, entries in sessions.items()
1190
  if any((l.get("meta") or {}).get("test_tag") == test_tag for l in entries)
1191
  }
1192
- if tag_matching:
1193
- session_logs = max(tag_matching.values(), key=len)
1194
- else:
1195
- # Tag not found yet (pipeline may still be starting) β€” fall through to prefix
1196
- if company_prefix:
1197
- matching = {
1198
- sid: entries for sid, entries in sessions.items()
1199
- if company_prefix in schema_for_session(entries).upper()
1200
- }
1201
- if matching:
1202
- session_logs = max(matching.values(), key=len)
1203
- else:
1204
- session_logs = max(sessions.values(), key=len)
1205
- schema = schema_for_session(session_logs)
1206
- print(f" ⚠️ Tag '{test_tag}' not found, prefix '{company_prefix}' no match β€” using best guess (schema: {schema})")
1207
- else:
1208
- session_logs = max(sessions.values(), key=len)
1209
- # 2. Company prefix match β€” fallback when no tag
1210
- elif company_prefix:
1211
- matching = {
1212
- sid: entries for sid, entries in sessions.items()
1213
- if company_prefix in schema_for_session(entries).upper()
1214
- }
1215
- if matching:
1216
- session_logs = max(matching.values(), key=len)
1217
- else:
1218
- session_logs = max(sessions.values(), key=len)
1219
- schema = schema_for_session(session_logs)
1220
- print(f" ⚠️ No session matched company prefix '{company_prefix}' β€” using best guess (schema: {schema})")
1221
  else:
1222
- session_logs = max(sessions.values(), key=len)
1223
 
1224
  # Summarise
1225
  completed = [l["stage"] for l in session_logs if "completed" in (l.get("event") or "")]
@@ -1277,29 +1267,18 @@ def fetch_run_diagnostics(start_time: float, company: str = "", test_tag: str =
1277
  def check_snowflake_schema(company: str, start_time: float, schema_override: str = None) -> dict:
1278
  """
1279
  Get row counts for the Snowflake schema created during this test run.
1280
- Uses schema_override (from session_logs) if available β€” no guessing needed.
1281
- Falls back to prefix+date matching only if session_logs didn't record the schema.
1282
  """
1283
  try:
1284
  from snowflake_auth import get_snowflake_connection
1285
- from datetime import datetime
1286
 
 
 
1287
  conn = get_snowflake_connection()
1288
  cursor = conn.cursor()
1289
 
1290
- if schema_override:
1291
- schema = schema_override
1292
- else:
1293
- # Fall back: guess from company prefix + date
1294
- prefix = company[:3].upper()
1295
- date_str = datetime.fromtimestamp(start_time).strftime("%m%d")
1296
- cursor.execute("SHOW SCHEMAS IN DATABASE DEMOBUILD")
1297
- all_schemas = [row[1] for row in cursor.fetchall()]
1298
- matching = [s for s in all_schemas if s.startswith(prefix) and date_str in s]
1299
- if not matching:
1300
- cursor.close(); conn.close()
1301
- return {"found": False, "prefix": prefix, "date": date_str}
1302
- schema = sorted(matching)[-1] # most recent
1303
  cursor.execute(f'SHOW TABLES IN SCHEMA DEMOBUILD."{schema}"')
1304
  tables = cursor.fetchall()
1305
 
@@ -1344,7 +1323,7 @@ def print_diagnostics(diag: dict, sf: dict):
1344
  for t in sf["tables"]:
1345
  print(f" {t['table']}: {t['rows']} rows")
1346
  elif sf:
1347
- print(f" Snowflake: no schema found for prefix={sf.get('prefix')} date={sf.get('date')}")
1348
  print(" ─────────────────────────────────────────────────────")
1349
 
1350
 
@@ -1432,30 +1411,29 @@ def run_single_test(page: Page, test_case: dict, config: dict) -> dict:
1432
 
1433
  result["duration_seconds"] = round(time.time() - start)
1434
 
1435
- # Always fetch session_logs β€” primary source for GUIDs, schema, and errors
 
 
 
 
 
 
 
 
 
 
1436
  diag = fetch_run_diagnostics(start, company=result.get("company", ""),
1437
  test_tag=test_case.get("_test_tag", ""))
1438
  result["diagnostics"] = diag
 
 
1439
 
1440
- # Build run_context from session_logs (authoritative); fall back to page scrape only if missing
1441
- log_ctx = {
1442
- "ts_base_url": diag.get("ts_base_url"),
1443
- "model_guid": diag.get("model_guid"),
1444
- "liveboard_guid": diag.get("liveboard_guid"),
1445
- }
1446
- if log_ctx["model_guid"]:
1447
- result["run_context"] = log_ctx
1448
- print(f" πŸ”‘ GUIDs from session_logs: model={log_ctx['model_guid'][:8]}… lb={log_ctx['liveboard_guid'] and log_ctx['liveboard_guid'][:8] or 'none'}…")
1449
  else:
1450
- page_ctx = extract_run_context(page)
1451
- result["run_context"] = page_ctx
1452
- if page_ctx.get("model_guid"):
1453
- print(f" πŸ”‘ GUIDs from page HTML (log miss): model={page_ctx['model_guid'][:8]}…")
1454
- else:
1455
- print(" ⚠️ No model GUID found in logs or page β€” AI grading will be skipped")
1456
-
1457
- # Use the real schema name from session_logs; fall back to prefix-guessing only if missing
1458
- known_schema = diag.get("snowflake_schema") if diag.get("found") else None
1459
  sf = check_snowflake_schema(result["company"], start, schema_override=known_schema)
1460
  result["snowflake_check"] = sf
1461
  if sf.get("found"):
@@ -1463,7 +1441,7 @@ def run_single_test(page: Page, test_case: dict, config: dict) -> dict:
1463
  for t in sf["tables"]:
1464
  print(f" {t['table']}: {t['rows']} rows")
1465
  else:
1466
- print(f" πŸ“¦ Snowflake: schema not found (logged as: {known_schema or 'not in logs'})")
1467
 
1468
  # Reconcile stages with session_logs β€” catches cases where the monitor bailed early
1469
  # but the pipeline actually completed (e.g. slow DDL that took >20 min)
@@ -1488,6 +1466,7 @@ def run_single_test(page: Page, test_case: dict, config: dict) -> dict:
1488
  ag = run_ai_grading(
1489
  result["run_context"],
1490
  result["company"], result["vertical"], result["line"], result["function"],
 
1491
  )
1492
  else:
1493
  ag["grading_errors"].append("Skipped β€” no model GUID (pipeline did not complete)")
 
437
  url_el.fill(test_case["company_url"])
438
  page.wait_for_timeout(300)
439
 
440
+ # Generate a unique tag for this test case. Logs are diagnostic-only; the
441
+ # completed page's model/liveboard URLs identify the run under test.
442
  test_tag = f"TR-{uuid.uuid4().hex[:8].upper()}"
443
  test_case["_test_tag"] = test_tag # store so run_single_test can pass it to diagnostics
444
 
 
504
  def extract_run_context(page: Page) -> dict:
505
  """
506
  After completion, find model and liveboard URLs in the page.
507
+ Uses visible text plus full HTML so GUIDs in href attributes are also
508
+ matched. This is the source of truth for the run under test.
509
  """
510
+ try:
511
+ visible = page.inner_text("body", timeout=5000)
512
+ except Exception:
513
+ visible = ""
514
+ body = f"{visible}\n{page.content()}" # full HTML catches GUIDs in href attrs too
515
 
516
  model_match = re.search(r'(https://[^\s"\'<>#]+)/#/data/tables/([a-f0-9-]{36})', body)
517
  lb_match = re.search(r'(https://[^\s"\'<>#]+)/#/pinboard/([a-f0-9-]{36})', body)
518
+ ts_base = None
519
+ if model_match:
520
+ ts_base = model_match.group(1)
521
+ elif lb_match:
522
+ ts_base = lb_match.group(1)
523
 
524
  return {
525
+ "ts_base_url": ts_base,
526
  "model_guid": model_match.group(2) if model_match else None,
527
  "liveboard_guid": lb_match.group(2) if lb_match else None,
528
+ "source": "page",
529
  }
530
 
531
 
 
621
  return "", ""
622
 
623
 
624
+ def resolve_schema_from_model(run_context: dict) -> dict:
625
+ """
626
+ Resolve the Snowflake schema from the exact ThoughtSpot model printed by
627
+ the app. No prefix/date guessing.
628
+ """
629
+ ts_base = run_context.get("ts_base_url")
630
+ model_guid = run_context.get("model_guid")
631
+ if not ts_base or not model_guid:
632
+ return {"found": False, "reason": "missing model URL"}
633
+ try:
634
+ session = ts_authenticate(ts_base)
635
+ model_tml = export_model_tml(ts_base, session, model_guid)
636
+ db, schema = extract_db_schema(model_tml)
637
+ if not schema:
638
+ return {"found": False, "reason": "model TML did not expose schema"}
639
+ return {"found": True, "database": db or "DEMOBUILD", "schema": schema}
640
+ except Exception as e:
641
+ return {"found": False, "reason": str(e)}
642
+
643
+
644
  def get_snowflake_sample(db: str, schema: str) -> str:
645
  try:
646
  from snowflake_auth import get_snowflake_connection
 
835
  db, schema = extract_db_schema(model_tml)
836
  if (not db or not schema) and schema_override:
837
  db, schema = "DEMOBUILD", schema_override
838
+ print(f" ℹ️ Using schema resolved from model: {schema_override}")
839
  sample = get_snowflake_sample(db, schema) if db and schema else "Could not determine db/schema"
840
  print(" πŸ€– Grading data quality...")
841
  dg = grade_data_quality(company, vertical, line, function, model_tml, sample)
 
1171
  def fetch_run_diagnostics(start_time: float, company: str = "", test_tag: str = "") -> dict:
1172
  """
1173
  Query session_logs for entries by testrunner after start_time.
1174
+ Matches only by exact test tag. This is intentionally not used to identify
1175
+ the model/schema under test because concurrent runs can contaminate logs.
1176
  """
1177
  try:
1178
  from supabase_client import SupabaseSettings
 
1200
  sid = log["session_id"]
1201
  sessions.setdefault(sid, []).append(log)
1202
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1203
  if test_tag:
1204
  tag_matching = {
1205
  sid: entries for sid, entries in sessions.items()
1206
  if any((l.get("meta") or {}).get("test_tag") == test_tag for l in entries)
1207
  }
1208
+ if not tag_matching:
1209
+ return {"found": False, "reason": f"Exact test tag not found in session_logs: {test_tag}"}
1210
+ session_logs = max(tag_matching.values(), key=len)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1211
  else:
1212
+ return {"found": False, "reason": "No test tag provided; refusing to guess session"}
1213
 
1214
  # Summarise
1215
  completed = [l["stage"] for l in session_logs if "completed" in (l.get("event") or "")]
 
1267
  def check_snowflake_schema(company: str, start_time: float, schema_override: str = None) -> dict:
1268
  """
1269
  Get row counts for the Snowflake schema created during this test run.
1270
+ Requires an explicit schema resolved from the exact ThoughtSpot model.
1271
+ Never guesses by company/date prefix.
1272
  """
1273
  try:
1274
  from snowflake_auth import get_snowflake_connection
 
1275
 
1276
+ if not schema_override:
1277
+ return {"found": False, "reason": "No explicit schema provided; refusing to guess"}
1278
  conn = get_snowflake_connection()
1279
  cursor = conn.cursor()
1280
 
1281
+ schema = schema_override
 
 
 
 
 
 
 
 
 
 
 
 
1282
  cursor.execute(f'SHOW TABLES IN SCHEMA DEMOBUILD."{schema}"')
1283
  tables = cursor.fetchall()
1284
 
 
1323
  for t in sf["tables"]:
1324
  print(f" {t['table']}: {t['rows']} rows")
1325
  elif sf:
1326
+ print(f" Snowflake: {sf.get('reason') or sf.get('error') or 'schema not found'}")
1327
  print(" ─────────────────────────────────────────────────────")
1328
 
1329
 
 
1411
 
1412
  result["duration_seconds"] = round(time.time() - start)
1413
 
1414
+ # The completed page prints the exact model/liveboard URLs for this run.
1415
+ # Treat that as authoritative; session_logs are diagnostics only.
1416
+ page_ctx = extract_run_context(page)
1417
+ result["run_context"] = page_ctx
1418
+ if page_ctx.get("model_guid"):
1419
+ lb_note = page_ctx.get("liveboard_guid", "")
1420
+ print(f" πŸ”‘ GUIDs from page: model={page_ctx['model_guid'][:8]}… lb={lb_note[:8] if lb_note else 'none'}…")
1421
+ else:
1422
+ print(" ⚠️ No model GUID found on final page β€” AI grading will be skipped")
1423
+
1424
+ # Fetch exact-tag diagnostics only for supplemental errors/stage reconciliation.
1425
  diag = fetch_run_diagnostics(start, company=result.get("company", ""),
1426
  test_tag=test_case.get("_test_tag", ""))
1427
  result["diagnostics"] = diag
1428
+ if not diag.get("found"):
1429
+ print(f" ℹ️ Session logs not used for identity: {diag.get('reason')}")
1430
 
1431
+ schema_resolution = resolve_schema_from_model(result["run_context"])
1432
+ known_schema = schema_resolution.get("schema") if schema_resolution.get("found") else None
1433
+ if known_schema:
1434
+ print(f" 🧭 Schema from ThoughtSpot model: {known_schema}")
 
 
 
 
 
1435
  else:
1436
+ print(f" ⚠️ Could not resolve schema from model: {schema_resolution.get('reason')}")
 
 
 
 
 
 
 
 
1437
  sf = check_snowflake_schema(result["company"], start, schema_override=known_schema)
1438
  result["snowflake_check"] = sf
1439
  if sf.get("found"):
 
1441
  for t in sf["tables"]:
1442
  print(f" {t['table']}: {t['rows']} rows")
1443
  else:
1444
+ print(f" πŸ“¦ Snowflake: schema not checked ({sf.get('reason') or sf.get('error') or 'unknown'})")
1445
 
1446
  # Reconcile stages with session_logs β€” catches cases where the monitor bailed early
1447
  # but the pipeline actually completed (e.g. slow DDL that took >20 min)
 
1466
  ag = run_ai_grading(
1467
  result["run_context"],
1468
  result["company"], result["vertical"], result["line"], result["function"],
1469
+ schema_override=known_schema,
1470
  )
1471
  else:
1472
  ag["grading_errors"].append("Skipped β€” no model GUID (pipeline did not complete)")