11import logging
22from dataclasses import dataclass
33from hashlib import sha256
4- from typing import List , Sequence
4+ from typing import Sequence
55
66from shared .plan .constants import FREE_PLAN_REPRESENTATIONS , TEAM_PLAN_REPRESENTATIONS
77from shared .yaml import UserYaml
8- from sqlalchemy import desc
8+ from sqlalchemy import desc , distinct , func
9+ from sqlalchemy .orm import joinedload
10+ from sqlalchemy .orm .session import Session
911
1012from database .enums import ReportType
1113from database .models import (
2123from services .license import requires_license
2224from services .processing .types import UploadArguments
2325from services .report import BaseReportService
24- from services .repository import get_repo_provider_service
2526from services .urls import get_members_url , get_test_analytics_url
2627from services .yaml import read_yaml_field
2728
@@ -121,7 +122,7 @@ def generate_test_id(repoid, testsuite, name, flags_hash):
121122class TestResultsNotificationFailure :
122123 failure_message : str
123124 display_name : str
124- envs : List [str ]
125+ envs : list [str ]
125126 test_id : str
126127 duration_seconds : float
127128 build_url : str | None = None
@@ -138,7 +139,7 @@ class TestResultsNotificationPayload:
138139 failed : int
139140 passed : int
140141 skipped : int
141- failures : List [TestResultsNotificationFailure ]
142+ failures : list [TestResultsNotificationFailure ]
142143 flaky_tests : dict [str , FlakeInfo ]
143144
144145
@@ -149,10 +150,7 @@ def wrap_in_details(summary: str, content: str):
149150
150151def make_quoted (content : str ) -> str :
151152 lines = content .splitlines ()
152-
153- result = ["> " + line for line in lines ]
154-
155- result = "\n " .join (result )
153+ result = "\n " .join ("> " + line for line in lines )
156154 return f"\n { result } \n "
157155
158156
@@ -246,9 +244,7 @@ def build_message(self) -> str:
246244 if self .payload is None :
247245 raise ValueError ("Payload passed to notifier is None, cannot build message" )
248246
249- message = []
250-
251- message .append (f"### :x: { self .payload .failed } Tests Failed:" )
247+ message = [f"### :x: { self .payload .failed } Tests Failed:" ]
252248
253249 completed = self .payload .failed + self .payload .passed
254250
@@ -259,13 +255,13 @@ def build_message(self) -> str:
259255 ]
260256
261257 failures = sorted (
262- filter (
263- lambda x : x .test_id not in self .payload .flaky_tests ,
264- self .payload .failures ,
258+ (
259+ failure
260+ for failure in self .payload .failures
261+ if failure .test_id not in self .payload .flaky_tests
265262 ),
266263 key = lambda x : (x .duration_seconds , x .display_name ),
267264 )
268-
269265 if failures :
270266 failure_content = [f"{ messagify_failure (failure )} " for failure in failures ]
271267
@@ -276,19 +272,17 @@ def build_message(self) -> str:
276272
277273 message .append (top_3_failed_section )
278274
279- flaky_failures = list (
280- filter (
281- lambda x : x .test_id in self .payload .flaky_tests ,
282- self .payload .failures ,
283- ),
284- )
285-
286- flake_content = [
287- f"{ messagify_flake (flaky_failure , self .payload .flaky_tests [flaky_failure .test_id ])} "
288- for flaky_failure in flaky_failures
275+ flaky_failures = [
276+ failure
277+ for failure in self .payload .failures
278+ if failure .test_id in self .payload .flaky_tests
289279 ]
280+ if flaky_failures :
281+ flake_content = [
282+ f"{ messagify_flake (flaky_failure , self .payload .flaky_tests [flaky_failure .test_id ])} "
283+ for flaky_failure in flaky_failures
284+ ]
290285
291- if flake_content :
292286 flaky_section = wrap_in_details (
293287 f"View the full list of { len (flaky_failures )} :snowflake: flaky tests" ,
294288 "\n " .join (flake_content ),
@@ -300,15 +294,11 @@ def build_message(self) -> str:
300294 return "\n " .join (message )
301295
302296 def error_comment (self ):
303- self ._repo_service = get_repo_provider_service (self .commit .repository )
304-
305297 pull = self .get_pull ()
306298 if pull is None :
307299 log .info (
308300 "Not notifying since there is no pull request associated with this commit" ,
309- extra = dict (
310- commitid = self .commit .commitid ,
311- ),
301+ extra = dict (commitid = self .commit .commitid ),
312302 )
313303 return False , "no_pull"
314304
@@ -321,16 +311,11 @@ def error_comment(self):
321311 return (True , "comment_posted" )
322312
323313 def upgrade_comment (self ):
324- if self ._repo_service is None :
325- self ._repo_service = get_repo_provider_service (self .commit .repository )
326-
327314 pull = self .get_pull ()
328315 if pull is None :
329316 log .info (
330317 "Not notifying since there is no pull request associated with this commit" ,
331- extra = dict (
332- commitid = self .commit .commitid ,
333- ),
318+ extra = dict (commitid = self .commit .commitid ),
334319 )
335320 return False , "no_pull"
336321
@@ -369,34 +354,53 @@ def upgrade_comment(self):
369354 return (True , "comment_posted" )
370355
371356
372- def latest_test_instances_for_a_given_commit (db_session , commit_id ):
357+ def latest_failures_for_commit (
358+ db_session : Session , repo_id : int , commit_sha : str
359+ ) -> list [TestInstance ]:
373360 """
374361 This will result in a SQL query that looks something like this:
375362
376- SELECT DISTINCT ON (rt.test_id) rt.id, rt.external_id, rt.created_at, rt.updated_at, rt.test_id, rt.duration_seconds, rt.outcome, rt.upload_id, rt.failure_message
377- FROM reports_testinstance rt JOIN reports_upload ru ON ru.id = rt.upload_id JOIN reports_commitreport rc ON rc.id = ru.report_id
378- WHERE rc.commit_id = <commit_id> ORDER BY rt.test_id, ru.created_at desc
363+ SELECT DISTINCT ON (rti.test_id) rti.id, ...
364+ FROM reports_testinstance rti
365+ JOIN reports_upload ru ON ru.id = rti.upload_id
366+ LEFT OUTER JOIN reports_test rt ON rt.id = rti.test_id
367+ WHERE ...
368+ ORDER BY rti.test_id, ru.created_at DESC
379369
380- The goal of this query is to return: "the latest test instance for each unique test based on upload creation time"
370+ The goal of this query is to return:
371+ > the latest test instance failure for each unique test based on upload creation time
381372
382- The `DISTINCT ON` test_id with the order by test_id, enforces that we are only fetching one test instance for each test
373+ The `DISTINCT ON` test_id with the order by test_id, enforces that we are only fetching one test instance for each test.
383374
384- The ordering of the upload.create_at desc enforces that we get the latest test instance for that unique test
375+ The ordering by ` upload.create_at DESC` enforces that we get the latest test instance for that unique test.
385376 """
377+
386378 return (
387379 db_session .query (TestInstance )
388- .join (Upload )
389- .join (CommitReport )
390- .filter (
391- CommitReport .commit_id == commit_id ,
392- )
380+ .join (TestInstance .upload )
381+ .options (joinedload (TestInstance .test ))
382+ .filter (TestInstance .repoid == repo_id , TestInstance .commitid == commit_sha )
383+ .filter (TestInstance .outcome .in_ (["failure" , "error" ]))
393384 .order_by (TestInstance .test_id )
394385 .order_by (desc (Upload .created_at ))
395386 .distinct (TestInstance .test_id )
396387 .all ()
397388 )
398389
399390
391+ def get_test_summary_for_commit (
392+ db_session : Session , repo_id : int , commit_sha : str
393+ ) -> dict [str , int ]:
394+ return dict (
395+ db_session .query (
396+ TestInstance .outcome , func .count (distinct (TestInstance .test_id ))
397+ )
398+ .filter (TestInstance .repoid == repo_id , TestInstance .commitid == commit_sha )
399+ .group_by (TestInstance .outcome )
400+ .all ()
401+ )
402+
403+
400404def not_private_and_free_or_team (repo : Repository ):
401405 return not (
402406 repo .private
0 commit comments