package render
import (
"fmt"
"time"
"html/template"
"github.com/pkg/errors"
"github.com/strongdm/comply/internal/config"
"github.com/strongdm/comply/internal/model"
"github.com/russross/blackfriday/v2"
)
type project struct {
OrganizationName string
Name string
}
type stats struct {
ControlsTotal int
CriteriaTotal int
CriteriaSatisfied int
ProcedureTotal int
ProcedureOpen int
ProcedureOldestDays int
AuditOpen int
AuditClosed int
AuditTotal int
}
type renderData struct {
// duplicates Project.OrganizationName
Name string
Project *project
Stats *stats
Narratives []*model.Document
Policies []*model.Document
Controls []*model.Control
Procedures []*model.Procedure
Frameworks []*model.Framework
Tickets []*model.Ticket
Links *model.TicketLinks
}
type criterion struct {
Framework string
CriteriaKey string
Name string
Description string
Satisfied bool
SatisfiedBy []string
}
func load() (*model.Data, *renderData, error) {
modelData, err := model.ReadData()
if err != nil {
return nil, nil, err
}
cfg := config.Config()
project := &project{
OrganizationName: cfg.Name,
Name: fmt.Sprintf("%s Compliance Program", cfg.Name),
}
satisfied := model.CriteriaSatisfied(modelData)
for _, framework := range modelData.Frameworks {
for key, c := range framework.Criteria{
satisfactions, ok := satisfied[key]
satisfied := ok && len(satisfactions) > 0
c.Satisfied = satisfied
c.SatisfiedBy = satisfactions
framework.Criteria[key] = c
}
}
rd := &renderData{}
rd.Narratives = modelData.Narratives
rd.Policies = modelData.Policies
rd.Controls = modelData.Controls
rd.Procedures = modelData.Procedures
rd.Frameworks = modelData.Frameworks
rd.Tickets = modelData.Tickets
rd.Links = &model.TicketLinks{}
rd.Project = project
rd.Name = project.OrganizationName
ts, err := config.Config().TicketSystem()
if err != nil {
return nil, nil, errors.Wrap(err, "error in ticket system configuration")
}
tp := model.GetPlugin(model.TicketSystem(ts))
if tp.Configured() {
links := tp.Links()
rd.Links = &links
}
return modelData, rd, nil
}
func loadWithStats() (*model.Data, *renderData, error) {
modelData, renderData, err := load()
if err != nil {
return nil, nil, err
}
// Convert the markdown body of each control to HTML
for _, n := range modelData.Controls {
b := []byte(n.Body)
n.BodyHTML = template.HTML(blackfriday.Run(b))
}
addStats(modelData, renderData)
return modelData, renderData, nil
}
func addStats(modelData *model.Data, renderData *renderData) {
stats := &stats{}
satisfied := model.CriteriaSatisfied(modelData)
stats.ControlsTotal += len(renderData.Controls)
for _, std := range renderData.Frameworks {
stats.CriteriaTotal += len(std.Criteria)
for criteriaKey := range std.Criteria{
if _, ok := satisfied[criteriaKey]; ok {
stats.CriteriaSatisfied++
}
}
}
for _, t := range renderData.Tickets {
if t.Bool("audit") {
stats.AuditTotal++
}
if t.State == model.Open {
if t.Bool("comply-procedure") {
stats.ProcedureOpen++
if t.CreatedAt != nil {
age := int(time.Since(*t.CreatedAt).Hours() / float64(24))
if stats.ProcedureOldestDays < age {
stats.ProcedureOldestDays = age
}
}
}
if t.Bool("audit") {
stats.AuditOpen++
}
}
}
renderData.Stats = stats
}