99 _ "embed"
1010 "fmt"
1111 "html/template"
12+ "slices"
1213 "sort"
1314 "strings"
1415
@@ -19,14 +20,15 @@ import (
1920)
2021
2122type templateHeatmapRow struct {
22- Items []* templateHeatmapRow
23- Name string
24- Coverage []int64
25- IsDir bool
26- Depth int
27- LastDayInstrumented int64
28- Tooltips []string
29- FileCoverageLink []string
23+ Items []* templateHeatmapRow
24+ Name string
25+ Coverage []int64 // in percents
26+ Covered []int64 // in lines count
27+ IsDir bool
28+ Depth int
29+ Summary int64 // right column, may be negative to show drops
30+ Tooltips []string
31+ FileCoverageLink []string
3032
3133 builder map [string ]* templateHeatmapRow
3234 instrumented map [coveragedb.TimePeriod ]int64
@@ -41,6 +43,43 @@ type templateHeatmap struct {
4143 Managers []string
4244}
4345
46+ func (th * templateHeatmap ) Filter (pred func (* templateHeatmapRow ) bool ) {
47+ th .Root .filter (pred )
48+ }
49+
50+ func (th * templateHeatmap ) Transform (f func (* templateHeatmapRow )) {
51+ th .Root .transform (f )
52+ }
53+
54+ func (th * templateHeatmap ) Sort (pred func (* templateHeatmapRow , * templateHeatmapRow ) int ) {
55+ th .Root .sort (pred )
56+ }
57+
58+ func (thm * templateHeatmapRow ) transform (f func (* templateHeatmapRow )) {
59+ for _ , item := range thm .Items {
60+ item .transform (f )
61+ }
62+ f (thm )
63+ }
64+
65+ func (thm * templateHeatmapRow ) filter (pred func (* templateHeatmapRow ) bool ) {
66+ var filteredItems []* templateHeatmapRow
67+ for _ , item := range thm .Items {
68+ item .filter (pred )
69+ if pred (item ) {
70+ filteredItems = append (filteredItems , item )
71+ }
72+ }
73+ thm .Items = filteredItems
74+ }
75+
76+ func (thm * templateHeatmapRow ) sort (pred func (* templateHeatmapRow , * templateHeatmapRow ) int ) {
77+ for _ , item := range thm .Items {
78+ item .sort (pred )
79+ }
80+ slices .SortFunc (thm .Items , pred )
81+ }
82+
4483func (thm * templateHeatmapRow ) addParts (depth int , pathLeft []string , filePath string , instrumented , covered int64 ,
4584 timePeriod coveragedb.TimePeriod ) {
4685 thm .instrumented [timePeriod ] += instrumented
@@ -68,18 +107,9 @@ func (thm *templateHeatmapRow) addParts(depth int, pathLeft []string, filePath s
68107 thm .builder [nextElement ].addParts (depth + 1 , pathLeft [1 :], filePath , instrumented , covered , timePeriod )
69108}
70109
71- func (thm * templateHeatmapRow ) prepareDataFor (pageColumns []pageColumnTarget , skipEmpty bool ) {
110+ func (thm * templateHeatmapRow ) prepareDataFor (pageColumns []pageColumnTarget ) {
72111 for _ , item := range thm .builder {
73- if ! skipEmpty {
74- thm .Items = append (thm .Items , item )
75- continue
76- }
77- for _ , hitCount := range item .covered {
78- if hitCount > 0 {
79- thm .Items = append (thm .Items , item )
80- break
81- }
82- }
112+ thm .Items = append (thm .Items , item )
83113 }
84114 sort .Slice (thm .Items , func (i , j int ) bool {
85115 if thm .Items [i ].IsDir != thm .Items [j ].IsDir {
@@ -94,6 +124,7 @@ func (thm *templateHeatmapRow) prepareDataFor(pageColumns []pageColumnTarget, sk
94124 dateCoverage = percent (thm .covered [tp ], thm .instrumented [tp ])
95125 }
96126 thm .Coverage = append (thm .Coverage , dateCoverage )
127+ thm .Covered = append (thm .Covered , thm .covered [tp ])
97128 thm .Tooltips = append (thm .Tooltips , fmt .Sprintf ("Instrumented:\t %d blocks\n Covered:\t %d blocks" ,
98129 thm .instrumented [tp ], thm .covered [tp ]))
99130 if ! thm .IsDir {
@@ -107,10 +138,10 @@ func (thm *templateHeatmapRow) prepareDataFor(pageColumns []pageColumnTarget, sk
107138 }
108139 if len (pageColumns ) > 0 {
109140 lastDate := pageColumns [len (pageColumns )- 1 ].TimePeriod
110- thm .LastDayInstrumented = thm .instrumented [lastDate ]
141+ thm .Summary = thm .instrumented [lastDate ]
111142 }
112143 for _ , item := range thm .builder {
113- item .prepareDataFor (pageColumns , skipEmpty )
144+ item .prepareDataFor (pageColumns )
114145 }
115146}
116147
@@ -119,7 +150,7 @@ type pageColumnTarget struct {
119150 Commit string
120151}
121152
122- func filesCoverageToTemplateData (fCov []* coveragedb.FileCoverageWithDetails , hideEmpty bool ) * templateHeatmap {
153+ func filesCoverageToTemplateData (fCov []* coveragedb.FileCoverageWithDetails ) * templateHeatmap {
123154 res := templateHeatmap {
124155 Root : & templateHeatmapRow {
125156 IsDir : true ,
@@ -152,7 +183,7 @@ func filesCoverageToTemplateData(fCov []*coveragedb.FileCoverageWithDetails, hid
152183 res .Periods = append (res .Periods , fmt .Sprintf ("%s(%d)" , tp .DateTo .String (), tp .Days ))
153184 }
154185
155- res .Root .prepareDataFor (targetDateAndCommits , hideEmpty )
186+ res .Root .prepareDataFor (targetDateAndCommits )
156187 return & res
157188}
158189
@@ -179,22 +210,30 @@ func stylesBodyJSTemplate(templData *templateHeatmap,
179210 template .HTML (js .Bytes ()), nil
180211}
181212
213+ type Format struct {
214+ FilterMinCoveredLinesDrop int
215+ OrderByCoveredLinesDrop bool
216+ DropCoveredLines0 bool
217+ }
218+
182219func DoHeatMapStyleBodyJS (
183220 ctx context.Context , client spannerclient.SpannerClient , scope * coveragedb.SelectScope , onlyUnique bool ,
184- sss , managers []string ) (template.CSS , template.HTML , template.HTML , error ) {
221+ sss , managers []string , dataFilters Format ) (template.CSS , template.HTML , template.HTML , error ) {
185222 covAndDates , err := coveragedb .FilesCoverageWithDetails (ctx , client , scope , onlyUnique )
186223 if err != nil {
187224 return "" , "" , "" , fmt .Errorf ("failed to FilesCoverageWithDetails: %w" , err )
188225 }
189- templData := filesCoverageToTemplateData (covAndDates , onlyUnique )
226+ templData := filesCoverageToTemplateData (covAndDates )
190227 templData .Subsystems = sss
191228 templData .Managers = managers
229+ FormatResult (templData , dataFilters )
230+
192231 return stylesBodyJSTemplate (templData )
193232}
194233
195234func DoSubsystemsHeatMapStyleBodyJS (
196235 ctx context.Context , client spannerclient.SpannerClient , scope * coveragedb.SelectScope , onlyUnique bool ,
197- sss , managers []string ) (template.CSS , template.HTML , template.HTML , error ) {
236+ sss , managers []string , format Format ) (template.CSS , template.HTML , template.HTML , error ) {
198237 covWithDetails , err := coveragedb .FilesCoverageWithDetails (ctx , client , scope , onlyUnique )
199238 if err != nil {
200239 panic (err )
@@ -213,20 +252,54 @@ func DoSubsystemsHeatMapStyleBodyJS(
213252 ssCovAndDates = append (ssCovAndDates , & newRecord )
214253 }
215254 }
216- templData := filesCoverageToTemplateData (ssCovAndDates , onlyUnique )
255+ templData := filesCoverageToTemplateData (ssCovAndDates )
217256 templData .Managers = managers
257+ FormatResult (templData , format )
218258 return stylesBodyJSTemplate (templData )
219259}
220260
261+ func FormatResult (thm * templateHeatmap , format Format ) {
262+ thm .Filter (func (row * templateHeatmapRow ) bool {
263+ if row .IsDir && len (row .Items ) > 0 {
264+ return true
265+ }
266+ return slices .Max (row .Covered )- row .Covered [len (row .Covered )- 1 ] >= int64 (format .FilterMinCoveredLinesDrop )
267+ })
268+ if format .DropCoveredLines0 {
269+ thm .Filter (func (row * templateHeatmapRow ) bool {
270+ return slices .Max (row .Covered ) > 0
271+ })
272+ }
273+ // The files are sorted lexicographically by default.
274+ if format .OrderByCoveredLinesDrop {
275+ thm .Sort (func (row1 * templateHeatmapRow , row2 * templateHeatmapRow ) int {
276+ row1CoveredDrop := slices .Max (row1 .Covered ) - row1 .Covered [len (row1 .Covered )- 1 ]
277+ row2CoveredDrop := slices .Max (row2 .Covered ) - row2 .Covered [len (row2 .Covered )- 1 ]
278+ return int (row2CoveredDrop - row1CoveredDrop )
279+ })
280+ // We want to show the coverage drop numbers instead of total instrumented blocks.
281+ thm .Transform (func (row * templateHeatmapRow ) {
282+ row .Summary = - 1 * (slices .Max (row .Covered ) - row .Covered [len (row .Covered )- 1 ])
283+ })
284+ }
285+ }
286+
221287func approximateInstrumented (points int64 ) string {
222288 dim := "_"
223- if points > 10000 {
289+ if abs ( points ) > 10000 {
224290 dim = "K"
225291 points /= 1000
226292 }
227293 return fmt .Sprintf ("%d%s" , points , dim )
228294}
229295
296+ func abs (a int64 ) int64 {
297+ if a < 0 {
298+ return - a
299+ }
300+ return a
301+ }
302+
230303//go:embed templates/heatmap.html
231304var templatesHeatmap string
232305var templateHeatmapFuncs = template.FuncMap {
0 commit comments