github.com/covergates/covergates@v0.2.2-0.20201009050117-42ef8a19fb95/models/report.go (about) 1 package models 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 8 "github.com/covergates/covergates/core" 9 log "github.com/sirupsen/logrus" 10 "gorm.io/gorm" 11 ) 12 13 var errReportFields = errors.New("Error Report Fields") 14 15 type reportList []*Report 16 17 // Report holds the report 18 type Report struct { 19 gorm.Model 20 FileData []byte 21 ReportID string `gorm:"size:256;uniqueIndex:report_record"` 22 Coverages []*Coverage 23 References []*Reference `gorm:"many2many:report_reference"` 24 Commit string `gorm:"size:256;uniqueIndex:report_record"` 25 } 26 27 // Coverage defines test coverage report 28 type Coverage struct { 29 gorm.Model 30 Data []byte 31 Type string 32 ReportID uint 33 } 34 35 // Reference of Report, such as branch or tag name 36 type Reference struct { 37 gorm.Model 38 ReportID string `gorm:"size:256;uniqueIndex:reference_record"` 39 Name string `gorm:"size:256;uniqueIndex:reference_record"` 40 Reports []*Report `gorm:"many2many:report_reference"` 41 } 42 43 // ReportComment defines summary report comment in the pull request 44 type ReportComment struct { 45 gorm.Model 46 ReportID string `gorm:"size:256;uniqueIndex:report_comment_number"` 47 // Number is the PR number 48 Number int `gorm:"uniqueIndex:report_comment_number"` 49 Comment int 50 } 51 52 // ReportStore reports in storage 53 type ReportStore struct { 54 DB core.DatabaseService 55 } 56 57 // Upload create a report to database 58 // If the report id and commit is already existed in the table, 59 // the report will be updated instead. 60 func (store *ReportStore) Upload(r *core.Report) error { 61 if r.ReportID == "" || r.Commit == "" { 62 return errReportFields 63 } 64 session := store.DB.Session() 65 if r.Reference != "" { 66 session = session.Preload("References", "name=?", r.Reference) 67 } 68 report := &Report{} 69 session.Preload("Coverages").FirstOrCreate(report, &Report{ 70 ReportID: r.ReportID, 71 Commit: r.Commit, 72 }) 73 if len(report.References) <= 0 && r.Reference != "" { 74 if err := store.appendReference(report, r.Reference); err != nil { 75 return err 76 } 77 } 78 for _, coverage := range r.Coverages { 79 if err := store.updateCoverage(report, coverage); err != nil { 80 return err 81 } 82 } 83 copyReport(report, r) 84 return session.Save(report).Error 85 } 86 87 // Find report with the input seed. No-empty filed will use as where condition 88 func (store *ReportStore) Find(r *core.Report) (*core.Report, error) { 89 session := store.DB.Session() 90 target := &Report{} 91 if r.Reference == "" { 92 session = session.Preload("Coverages").Order("created_at desc").First(target, query(r)) 93 if err := session.Error; err != nil { 94 return nil, err 95 } 96 } else { 97 if r.ReportID == "" { 98 log.Warning("report id should not be empty when search with reference") 99 return nil, gorm.ErrRecordNotFound 100 } 101 ref := &Reference{ReportID: r.ReportID, Name: r.Reference} 102 session = session.Preload("Reports", func(db *gorm.DB) *gorm.DB { 103 return db.Where(query(r)).Order("created_at desc").Limit(50) 104 }).Preload("Reports.Coverages").First(ref, ref) 105 if err := session.Error; err != nil { 106 return nil, err 107 } 108 if len(ref.Reports) <= 0 { 109 return nil, gorm.ErrRecordNotFound 110 } 111 target = ref.Reports[0] 112 } 113 report := target.ToCoreReport() 114 report.Reference = r.Reference 115 return report, nil 116 } 117 118 // Finds all report with given seed 119 func (store *ReportStore) Finds(r *core.Report) ([]*core.Report, error) { 120 session := store.DB.Session() 121 var rst []*Report 122 123 if r.Reference == "" { 124 if err := session.Preload( 125 "Coverages", 126 ).Where(query(r)).Order( 127 "created_at desc", 128 ).Limit(100).Find(&rst).Error; err != nil { 129 return nil, err 130 } 131 } else { 132 if r.ReportID == "" { 133 log.Warning("report id should not be empty when search with reference") 134 return nil, gorm.ErrRecordNotFound 135 } 136 ref := &Reference{ReportID: r.ReportID, Name: r.Reference} 137 session = session.Preload("Reports", func(db *gorm.DB) *gorm.DB { 138 return db.Where(query(r)).Order("created_at desc").Limit(100) 139 }).Preload("Reports.Coverages").First(ref, ref) 140 if err := session.Error; err != nil { 141 return nil, err 142 } 143 if len(ref.Reports) <= 0 { 144 return nil, gorm.ErrRecordNotFound 145 } 146 rst = ref.Reports 147 } 148 reports := make([]*core.Report, len(rst)) 149 for i, report := range rst { 150 reports[i] = report.ToCoreReport() 151 reports[i].Reference = r.Reference 152 } 153 return reports, nil 154 } 155 156 // List reports with reference 157 // 158 // reference (ref) could be commit SHA, branch or tag name. 159 // The files and data field will be remove from result to reduce memory usage. 160 func (store *ReportStore) List(reportID, ref string) ([]*core.Report, error) { 161 session := store.DB.Session() 162 var reports reportList 163 condition := &Report{ReportID: reportID, Commit: ref} 164 err := session.Preload("Coverages").Where(condition).Order( 165 "created_at desc", 166 ).Limit(100).Find(&reports).Error 167 if err == nil && len(reports) > 0 { 168 return reports.ToCoreReports(""), nil 169 } 170 reference := &Reference{ReportID: reportID, Name: ref} 171 session = store.DB.Session().Preload("Reports", func(db *gorm.DB) *gorm.DB { 172 return db.Order( 173 "created_at desc", 174 ).Limit(200) 175 }).Preload("Reports.Coverages").First(reference, reference) 176 if err := session.Error; err != nil { 177 return nil, err 178 } 179 if len(reference.Reports) <= 0 { 180 return nil, gorm.ErrRecordNotFound 181 } 182 return reportList(reference.Reports).ToCoreReports(ref), nil 183 } 184 185 // CreateComment of the report summary 186 func (store *ReportStore) CreateComment(r *core.Report, comment *core.ReportComment) error { 187 if comment.Comment <= 0 || comment.Number <= 0 { 188 return fmt.Errorf("invalid comment") 189 } 190 session := store.DB.Session() 191 condition := &ReportComment{ReportID: r.ReportID, Number: comment.Number} 192 c := &ReportComment{} 193 if err := session.Where(condition).FirstOrCreate(c).Error; err != nil { 194 return err 195 } 196 c.Comment = comment.Comment 197 return session.Save(c).Error 198 } 199 200 // FindComment summary of given PR number 201 func (store *ReportStore) FindComment(r *core.Report, number int) (*core.ReportComment, error) { 202 session := store.DB.Session() 203 condition := &ReportComment{ReportID: r.ReportID, Number: number} 204 comment := &ReportComment{} 205 if err := session.First(comment, condition).Error; err != nil { 206 return nil, err 207 } 208 return &core.ReportComment{ 209 Comment: comment.Comment, 210 Number: comment.Number, 211 }, nil 212 } 213 214 func (store *ReportStore) updateCoverage(r *Report, cov *core.CoverageReport) error { 215 c, ok := r.find(cov.Type) 216 if err := copyCoverage(c, cov); err != nil { 217 return err 218 } 219 if !ok { 220 r.Coverages = append(r.Coverages, c) 221 } else if c.ID > 0 { 222 store.DB.Session().Save(c) 223 } 224 return nil 225 } 226 227 func (store *ReportStore) appendReference(r *Report, name string) error { 228 if r.ReportID == "" { 229 return errReportFields 230 } 231 session := store.DB.Session() 232 ref := &Reference{Name: name, ReportID: r.ReportID} 233 if err := session.FirstOrCreate(ref, ref).Error; err != nil { 234 return err 235 } 236 return session.Model(r).Association("References").Append(ref) 237 } 238 239 // Files of the report 240 func (r *Report) Files() ([]string, error) { 241 var files []string 242 err := json.Unmarshal(r.FileData, &files) 243 return files, err 244 } 245 246 // ToCoreReport object 247 func (r *Report) ToCoreReport() *core.Report { 248 coverages := make([]*core.CoverageReport, len(r.Coverages)) 249 for i, coverage := range r.Coverages { 250 c, _ := coverage.ToCoreCoverage() 251 coverages[i] = c 252 } 253 files, err := r.Files() 254 if err != nil { 255 files = []string{} 256 } 257 report := &core.Report{ 258 Commit: r.Commit, 259 ReportID: r.ReportID, 260 Files: files, 261 Coverages: coverages, 262 CreatedAt: r.CreatedAt, 263 } 264 return report 265 } 266 267 func (r *Report) find(t core.ReportType) (*Coverage, bool) { 268 for _, coverage := range r.Coverages { 269 if coverage.Type == string(t) { 270 return coverage, true 271 } 272 } 273 return &Coverage{}, false 274 } 275 276 // ToCoreCoverage unmarshal Coverage Data 277 func (c *Coverage) ToCoreCoverage() (*core.CoverageReport, error) { 278 cover := &core.CoverageReport{} 279 cover.Type = core.ReportType(c.Type) 280 if err := json.Unmarshal(c.Data, cover); err != nil { 281 return cover, err 282 } 283 cover.StatementCoverage = cover.ComputeStatementCoverage() 284 return cover, nil 285 } 286 287 func (r reportList) ToCoreReports(ref string) []*core.Report { 288 result := make([]*core.Report, len(r)) 289 for i, report := range r { 290 report.FileData = nil 291 coreReport := report.ToCoreReport() 292 for _, coverage := range coreReport.Coverages { 293 coverage.Files = nil 294 } 295 coreReport.Reference = ref 296 result[i] = coreReport 297 } 298 return result 299 } 300 301 func query(r *core.Report) *Report { 302 return &Report{ 303 Commit: r.Commit, 304 ReportID: r.ReportID, 305 } 306 } 307 308 func copyReport(dst *Report, src *core.Report) { 309 files, err := json.Marshal(src.Files) 310 if err != nil { 311 files = []byte{} 312 } 313 dst.Commit = src.Commit 314 dst.ReportID = src.ReportID 315 dst.FileData = files 316 } 317 318 func copyCoverage(dst *Coverage, src *core.CoverageReport) error { 319 cov, err := json.Marshal(src) 320 if err != nil { 321 return err 322 } 323 dst.Data = cov 324 dst.Type = string(src.Type) 325 return nil 326 }