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  }