Skip to content

Commit

Permalink
Add project level markdown renderer (#256)
Browse files Browse the repository at this point in the history
  • Loading branch information
Aayyush authored Jun 1, 2022
1 parent 6e7a766 commit 81c96ea
Show file tree
Hide file tree
Showing 4 changed files with 292 additions and 21 deletions.
8 changes: 3 additions & 5 deletions server/events/output_updater.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,18 +77,16 @@ func (c *ChecksOutputUpdater) UpdateOutput(ctx *command.Context, cmd PullCommand
})

// Description is a required field
var description string
description := fmt.Sprintf("**Project**: `%s` **Dir**: `%s` **Workspace**: `%s`", projectResult.ProjectName, projectResult.RepoRelDir, projectResult.Workspace)

var state models.CommitStatus
if projectResult.Error != nil || projectResult.Failure != "" {
description = fmt.Sprintf("%s failed for %s", strings.Title(projectResult.Command.String()), projectResult.ProjectName)
state = models.FailedCommitStatus
} else {
description = fmt.Sprintf("%s succeeded for %s", strings.Title(projectResult.Command.String()), projectResult.ProjectName)
state = models.SuccessCommitStatus
}

// TODO: Make the mark down rendered project specific
output := c.MarkdownRenderer.Render(res, cmd.CommandName(), ctx.Pull.BaseRepo)
output := c.MarkdownRenderer.RenderProject(projectResult, cmd.CommandName(), ctx.Pull.BaseRepo)
updateStatusReq := types.UpdateStatusRequest{
Repo: ctx.HeadRepo,
Ref: ctx.Pull.HeadCommit,
Expand Down
25 changes: 18 additions & 7 deletions server/vcs/markdown/markdown_renderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ type projectResultTmplData struct {
Rendered string
}

// Render formats the data into a markdown string.
// Render formats the data into a markdown string for a command.
// nolint: interfacer
func (m *Renderer) Render(res command.Result, cmdName command.Name, baseRepo models.Repo) string {
commandStr := strings.Title(strings.Replace(cmdName.String(), "_", " ", -1))
commandStr := strings.Title(strings.ReplaceAll(cmdName.String(), "_", " "))
common := commonData{
Command: commandStr,
DisableApplyAll: m.DisableApplyAll || m.DisableApply,
Expand All @@ -83,20 +83,31 @@ func (m *Renderer) Render(res command.Result, cmdName command.Name, baseRepo mod
if res.Failure != "" {
return m.renderTemplate(template.Must(template.New("").Parse(failureWithLogTmpl)), failureData{res.Failure, common})
}
return m.renderProjectResults(res.ProjectResults, common, baseRepo)

return m.renderProjectResults(res.ProjectResults, common, cmdName, baseRepo)
}

// RenderProject formats the data into a markdown string for a project
func (m *Renderer) RenderProject(prjRes command.ProjectResult, cmdName command.Name, baseRepo models.Repo) string {
commandStr := strings.Title(strings.ReplaceAll(cmdName.String(), "_", " "))
common := commonData{
Command: commandStr,
DisableApply: m.DisableApply,
EnableDiffMarkdownFormat: m.EnableDiffMarkdownFormat,
}
template, templateData := m.TemplateResolver.ResolveProject(prjRes, baseRepo, common)
return m.renderTemplate(template, templateData)
}

func (m *Renderer) renderProjectResults(results []command.ProjectResult, common commonData, baseRepo models.Repo) string {
func (m *Renderer) renderProjectResults(results []command.ProjectResult, common commonData, cmdName command.Name, baseRepo models.Repo) string {
// render project results
var prjResultTmplData []projectResultTmplData
for _, result := range results {
template, templateData := m.TemplateResolver.ResolveProject(result, baseRepo, common)
renderedOutput := m.renderTemplate(template, templateData)
prjResultTmplData = append(prjResultTmplData, projectResultTmplData{
Workspace: result.Workspace,
RepoRelDir: result.RepoRelDir,
ProjectName: result.ProjectName,
Rendered: renderedOutput,
Rendered: m.RenderProject(result, cmdName, baseRepo),
})
}

Expand Down
262 changes: 262 additions & 0 deletions server/vcs/markdown/markdown_renderer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1942,3 +1942,265 @@ Plan: 1 to add, 1 to change, 1 to destroy.
})
}
}

func TestRenderProjectCustomTemplate(t *testing.T) {
cases := []struct {
Description string
Command command.Name
ProjectResult command.ProjectResult
VCSHost models.VCSHostType
Expected string
TemplateOverrides map[string]string
}{
{
"Default Plan",
command.Plan,
command.ProjectResult{
PlanSuccess: &models.PlanSuccess{},
},
models.Github,
`$$$diff
$$$
* :arrow_forward: To **apply** this plan, comment:
* $$
* :put_litter_in_its_place: To **delete** this plan click [here]()
* :repeat: To **plan** this project again, comment:
* $$
`,
map[string]string{},
},
{
"Plan Override",
command.Plan,
command.ProjectResult{
PlanSuccess: &models.PlanSuccess{},
},
models.Github,
"Custom Template",
map[string]string{"project_plan_success": "testdata/custom_template.tmpl"},
},
{
"Default Apply",
command.Plan,
command.ProjectResult{
ApplySuccess: "Apply Output",
},
models.Github,
`$$$diff
Apply Output
$$$`,
map[string]string{},
},
{
"Apply Override",
command.Apply,
command.ProjectResult{
ApplySuccess: "Apply Output",
},
models.Github,
"Custom Template",
map[string]string{"project_apply_success": "testdata/custom_template.tmpl"},
},
}
for _, c := range cases {

templateResolver := TemplateResolver{
GlobalCfg: valid.GlobalCfg{
Repos: []valid.Repo{
{
ID: testRepo.ID(),
TemplateOverrides: c.TemplateOverrides,
},
},
},
}
r := Renderer{
TemplateResolver: templateResolver,
}
expWithBackticks := strings.Replace(c.Expected, "$", "`", -1)
t.Run(fmt.Sprintf("%s_%t", c.Description, false), func(t *testing.T) {
s := r.RenderProject(c.ProjectResult, c.Command, testRepo)
fmt.Println(s)
Equals(t, expWithBackticks, s)
})
}

}

func TestRenderProjectRenderErrorf(t *testing.T) {
err := errors.New("err")
cases := []struct {
Description string
Command command.Name
Error error
Expected string
}{
{
"plan error",
command.Plan,
err,
"**Plan Error**\n```\nerr\n```",
},
{
"apply error",
command.Apply,
err,
"**Apply Error**\n```\nerr\n```",
},
{
"policy check error",
command.PolicyCheck,
err,
"**Policy Check Error**\n```\nerr\n```",
},
}
r := Renderer{}
for _, c := range cases {
res := command.ProjectResult{
Error: c.Error,
}
t.Run(c.Description, func(t *testing.T) {
s := r.RenderProject(res, c.Command, testRepo)
Equals(t, c.Expected, s)
})
}
}

func TestRenderProjectFailure(t *testing.T) {
cases := []struct {
Description string
Command command.Name
Failure string
Expected string
}{
{
"apply failure",
command.Apply,
"failure",
"**Apply Failed**: failure",
},
{
"plan failure",
command.Plan,
"failure",
"**Plan Failed**: failure",
},
{
"policy check failure",
command.PolicyCheck,
"failure",
"**Policy Check Failed**\n```\nfailure\n```" +
"\n* :heavy_check_mark: To **approve** failing policies either request an approval from approvers or address the failure by modifying the codebase.\n",
},
}
r := Renderer{}
for _, c := range cases {
res := command.ProjectResult{
Failure: c.Failure,
}
t.Run(c.Description, func(t *testing.T) {
s := r.RenderProject(res, c.Command, testRepo)
Equals(t, c.Expected, s)
})
}
}

func TestRenderProjectErrAndFailure(t *testing.T) {
r := Renderer{}
res := command.ProjectResult{
Error: errors.New("error"),
Failure: "failure",
}
s := r.RenderProject(res, command.Plan, testRepo)
Equals(t, "**Plan Error**\n```\nerror\n```", s)
}

func TestRenderProjectDisableApply(t *testing.T) {
cases := []struct {
Description string
Command command.Name
ProjectResult command.ProjectResult
VCSHost models.VCSHostType
Expected string
DisableApply bool
}{
{
"successful plan with disable apply not set",
command.Plan,
command.ProjectResult{
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "terraform-output",
LockURL: "lock-url",
RePlanCmd: "atlantis plan -d path -w workspace",
ApplyCmd: "atlantis apply -d path -w workspace",
},
Workspace: "workspace",
RepoRelDir: "path",
},
models.Github,
`$$$diff
terraform-output
$$$
* :arrow_forward: To **apply** this plan, comment:
* $atlantis apply -d path -w workspace$
* :put_litter_in_its_place: To **delete** this plan click [here](lock-url)
* :repeat: To **plan** this project again, comment:
* $atlantis plan -d path -w workspace$
`,
false,
},
{
"successful plan with disable apply set",
command.Plan,
command.ProjectResult{
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "terraform-output",
LockURL: "lock-url",
RePlanCmd: "atlantis plan -d path -w workspace",
ApplyCmd: "atlantis apply -d path -w workspace",
},
Workspace: "workspace",
RepoRelDir: "path",
},
models.Github,
`$$$diff
terraform-output
$$$
* :put_litter_in_its_place: To **delete** this plan click [here](lock-url)
* :repeat: To **plan** this project again, comment:
* $atlantis plan -d path -w workspace$
`,
true,
},
}
for _, c := range cases {
r := Renderer{
DisableApply: c.DisableApply,
}
t.Run(c.Description, func(t *testing.T) {
s := r.RenderProject(c.ProjectResult, c.Command, testRepo)
fmt.Print(s)
expWithBackticks := strings.Replace(c.Expected, "$", "`", -1)
Equals(t, expWithBackticks, s)
})
}
}

func TestRenderProjectFolding(t *testing.T) {
mr := Renderer{
TemplateResolver: TemplateResolver{
DisableMarkdownFolding: true,
},
}

rendered := mr.RenderProject(command.ProjectResult{
RepoRelDir: ".",
Workspace: "default",
Error: errors.New(strings.Repeat("line\n", 13)),
}, command.Plan, testRepo)
Equals(t, false, strings.Contains(rendered, "<details>"))
}
18 changes: 9 additions & 9 deletions server/vcs/markdown/template_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,15 @@ func (t *TemplateResolver) ResolveProject(result command.ProjectResult, baseRepo
var templateData interface{}

switch {
case result.Error != nil:
tmpl = t.buildTemplate(Error, baseRepo.VCSHost.Type, wrappedErrTmpl, unwrappedErrTmpl, result.Error.Error(), templateOverrides)
templateData = struct {
Command string
Error string
}{
Command: common.Command,
Error: result.Error.Error(),
}
case result.Failure != "":
// use template override if specified
if val, ok := templateOverrides["project_failure"]; ok {
Expand All @@ -128,15 +137,6 @@ func (t *TemplateResolver) ResolveProject(result command.ProjectResult, baseRepo
Command: common.Command,
Failure: result.Failure,
}
case result.Error != nil:
tmpl = t.buildTemplate(Error, baseRepo.VCSHost.Type, wrappedErrTmpl, unwrappedErrTmpl, result.Error.Error(), templateOverrides)
templateData = struct {
Command string
Error string
}{
Command: common.Command,
Error: result.Error.Error(),
}
case result.PlanSuccess != nil:
tmpl = t.buildTemplate(PlanSuccess, baseRepo.VCSHost.Type, planSuccessWrappedTmpl, planSuccessUnwrappedTmpl, result.PlanSuccess.TerraformOutput, templateOverrides)
templateData = planSuccessData{
Expand Down

0 comments on commit 81c96ea

Please sign in to comment.