github.com/covergates/covergates@v0.2.2-0.20201009050117-42ef8a19fb95/modules/charts/card.go (about)

     1  package charts
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  
     7  	svg "github.com/ajstarks/svgo"
     8  	"github.com/covergates/covergates/core"
     9  	"github.com/covergates/covergates/modules/charts/icons"
    10  	"github.com/dustin/go-humanize"
    11  	"golang.org/x/text/language"
    12  	"golang.org/x/text/message"
    13  )
    14  
    15  const (
    16  	fileIcon = "files.svg"
    17  	listIcon = "list.svg"
    18  	gitIcon  = "git.svg"
    19  	logoIcon = "logo.svg"
    20  	cssStyle = `
    21  	<style>
    22  	.score-circle-ring {
    23  		stroke: #3f8167;
    24  		stroke-width: 6;
    25  		fill: none;
    26  		opacity: 0.2;
    27  	}
    28  	.score-circle {
    29  		stroke: #3f8167;
    30  		stroke-width: 6;
    31  		fill: none;
    32  		stroke-linecap: round;
    33  		stroke-dasharray: 283;
    34  	}
    35  	</style>
    36  	`
    37  )
    38  
    39  // RepoCard render svg repo status card
    40  type RepoCard struct {
    41  	core.Chart
    42  	repo   *core.Repo
    43  	report *core.Report
    44  	canvas *svg.SVG
    45  }
    46  
    47  // NewRepoCard render
    48  func NewRepoCard(repo *core.Repo, report *core.Report) *RepoCard {
    49  	return &RepoCard{
    50  		repo:   repo,
    51  		report: report,
    52  	}
    53  }
    54  
    55  func (c *RepoCard) start(w io.Writer) {
    56  	c.canvas = svg.New(w)
    57  	c.canvas.Start(495, 195)
    58  	c.canvas.Writer.Write([]byte(cssStyle))
    59  	c.canvas.ClipPath(`id="a1"`)
    60  	c.canvas.Rect(0, 0, 500, 170)
    61  	c.canvas.ClipEnd()
    62  	c.canvas.Roundrect(1, 1, 493, 193, 5, 5, `fill="#3b726b"`)
    63  	c.canvas.Roundrect(1, 1, 493, 193, 5, 5, `fill="#333333"`, `clip-path="url(#a1)"`)
    64  }
    65  
    66  func (c *RepoCard) end() {
    67  	c.canvas.End()
    68  }
    69  
    70  func (c *RepoCard) title() {
    71  	c.canvas.Text(15, 32, c.repo.FullName(), `font: 600 24px 'Segoe UI', sans-serif`, `fill="#80b9a2"`)
    72  }
    73  
    74  func (c *RepoCard) files() {
    75  	c.canvas.Translate(15, 75)
    76  	c.canvas.Group(`transform="scale(0.7) translate(0, -22)"`)
    77  	c.canvas.Writer.Write(icons.MustAsset(fileIcon))
    78  	c.canvas.Gend()
    79  	c.canvas.Text(32, 0, "Total Files", `font: 600 18px 'Segoe UI', sans-serif`, `fill="#e6e6e6"`)
    80  	p := message.NewPrinter(language.English)
    81  	c.canvas.Text(200, 0, p.Sprintf("%d", len(c.report.Files)), `font: 600 18px 'Segoe UI', sans-serif`, `fill="#e6e6e6"`)
    82  	c.canvas.Gend()
    83  }
    84  
    85  func (c *RepoCard) hits() {
    86  	c.canvas.Translate(15, 115)
    87  	c.canvas.Translate(-1, -16)
    88  	c.canvas.Gtransform("scale(0.18)")
    89  	c.canvas.Writer.Write(icons.MustAsset(listIcon))
    90  	c.canvas.Gend()
    91  	c.canvas.Gend()
    92  	c.canvas.Text(32, 0, "Hit Lines", `font: 600 18px 'Segoe UI', sans-serif`, `fill="#e6e6e6"`)
    93  	p := message.NewPrinter(language.English)
    94  	c.canvas.Text(200, 0, p.Sprintf("%d", c.hitLines()), `font: 600 18px 'Segoe UI', sans-serif`, `fill="#e6e6e6"`)
    95  	c.canvas.Gend()
    96  }
    97  
    98  func (c *RepoCard) hitLines() int {
    99  	hits := 0
   100  	for _, coverage := range c.report.Coverages {
   101  		for _, file := range coverage.Files {
   102  			for _, line := range file.StatementHits {
   103  				if line.Hits > 0 {
   104  					hits++
   105  				}
   106  			}
   107  		}
   108  	}
   109  	return hits
   110  }
   111  
   112  func (c *RepoCard) build() {
   113  	c.canvas.Translate(15, 155)
   114  	c.canvas.Gtransform("scale(0.11) translate(-140, -160)")
   115  	c.canvas.Writer.Write(icons.MustAsset(gitIcon))
   116  	c.canvas.Gend()
   117  	c.canvas.Text(32, 0, "Recent Build", `font: 600 18px 'Segoe UI', sans-serif`, `fill="#e6e6e6"`)
   118  	c.canvas.Text(200, 0, fmt.Sprintf("%s, %s", c.repo.Branch, humanize.Time(c.report.CreatedAt)), `font: 600 18px 'Segoe UI', sans-serif`, `fill="#e6e6e6"`)
   119  	c.canvas.Gend()
   120  }
   121  
   122  func (c *RepoCard) brand() {
   123  	c.canvas.Translate(180, 185)
   124  	c.canvas.Gtransform("scale(0.08) translate(-5, -180)")
   125  	c.canvas.Writer.Write(icons.MustAsset(logoIcon))
   126  	c.canvas.Gend()
   127  	c.canvas.Text(30, 0, "covergates©2020", "font-size:12px")
   128  	c.canvas.Gend()
   129  }
   130  
   131  func (c *RepoCard) score() {
   132  	c.canvas.Translate(420, 75)
   133  	c.canvas.Circle(0, 0, 45, `class="score-circle-ring"`)
   134  	c.canvas.Circle(
   135  		0, 0, 45,
   136  		`class="score-circle"`,
   137  		fmt.Sprintf("stroke-dashoffset: %d", int(45*2*3.14*(1.0-c.report.StatementCoverage()))),
   138  	)
   139  	c.canvas.Translate(-35, 12)
   140  	score := int(c.report.StatementCoverage() * 100)
   141  	pad := 0
   142  	if score < 100 {
   143  		pad = 10
   144  	}
   145  	c.canvas.Text(pad, 0, fmt.Sprintf("%d", score), `font: 600 36px 'Segoe UI', sans-serif`, `fill="#3f8167"`)
   146  	c.canvas.Text(55, 0, "%", `font: 800 14px 'Segoe UI', sans-serif`, `fill="#3f8167"`)
   147  	c.canvas.Gend()
   148  	c.canvas.Gend()
   149  }
   150  
   151  // Render card to writer
   152  func (c *RepoCard) Render(w io.Writer) error {
   153  	c.start(w)
   154  	c.title()
   155  	c.files()
   156  	c.hits()
   157  	c.build()
   158  	c.brand()
   159  	c.score()
   160  	c.end()
   161  	return nil
   162  }