diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 1e3485e..faed3ea 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -15,7 +15,4 @@ jobs:
go-version: 1.23.3
- name: Run tests
- run: |
- go install github.com/a-h/templ/cmd/templ@latest
- templ generate
- go test ./...
+ run: go test ./...
diff --git a/.gitignore b/.gitignore
index 0fbde30..2bf8bb2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,7 +5,6 @@ example
./example-server-bootstrap
example
-*_templ.go
handoff.db
# tmux startup script
diff --git a/internal/html/body_templ.go b/internal/html/body_templ.go
new file mode 100644
index 0000000..fa8a68f
--- /dev/null
+++ b/internal/html/body_templ.go
@@ -0,0 +1,249 @@
+// Code generated by templ@v0.2.364 DO NOT EDIT.
+
+package html
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import "context"
+import "io"
+import "bytes"
+
+func body() templ.Component {
+ return templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
+ templBuffer, templIsBuffer := w.(*bytes.Buffer)
+ if !templIsBuffer {
+ templBuffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templBuffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ var_1 := templ.GetChildren(ctx)
+ if var_1 == nil {
+ var_1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, err = templBuffer.WriteString("
")
+ if err != nil {
+ return err
+ }
+ var_9 := `🤝`
+ _, err = templBuffer.WriteString(var_9)
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("
")
+ if err != nil {
+ return err
+ }
+ var_16 := `🤝`
+ _, err = templBuffer.WriteString(var_16)
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("
")
+ if err != nil {
+ return err
+ }
+ err = var_1.Render(ctx, templBuffer)
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("
")
+ if err != nil {
+ return err
+ }
+ if !templIsBuffer {
+ _, err = templBuffer.WriteTo(w)
+ }
+ return err
+ })
+}
diff --git a/internal/html/component/heading_templ.go b/internal/html/component/heading_templ.go
new file mode 100644
index 0000000..820d2eb
--- /dev/null
+++ b/internal/html/component/heading_templ.go
@@ -0,0 +1,97 @@
+// Code generated by templ@v0.2.364 DO NOT EDIT.
+
+package component
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import "context"
+import "io"
+import "bytes"
+
+func Heading(title string) templ.Component {
+ return templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
+ templBuffer, templIsBuffer := w.(*bytes.Buffer)
+ if !templIsBuffer {
+ templBuffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templBuffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ var_1 := templ.GetChildren(ctx)
+ if var_1 == nil {
+ var_1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, err = templBuffer.WriteString("")
+ if err != nil {
+ return err
+ }
+ var var_6 string = title
+ _, err = templBuffer.WriteString(templ.EscapeString(var_6))
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("
")
+ if err != nil {
+ return err
+ }
+ if !templIsBuffer {
+ _, err = templBuffer.WriteTo(w)
+ }
+ return err
+ })
+}
diff --git a/internal/html/component/stats_templ.go b/internal/html/component/stats_templ.go
new file mode 100644
index 0000000..2aa98b4
--- /dev/null
+++ b/internal/html/component/stats_templ.go
@@ -0,0 +1,88 @@
+// Code generated by templ@v0.2.364 DO NOT EDIT.
+
+package component
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import "context"
+import "io"
+import "bytes"
+
+func Stats() templ.Component {
+ return templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
+ templBuffer, templIsBuffer := w.(*bytes.Buffer)
+ if !templIsBuffer {
+ templBuffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templBuffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ var_1 := templ.GetChildren(ctx)
+ if var_1 == nil {
+ var_1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, err = templBuffer.WriteString("- ")
+ if err != nil {
+ return err
+ }
+ var_2 := `Total Subscribers`
+ _, err = templBuffer.WriteString(var_2)
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("
- ")
+ if err != nil {
+ return err
+ }
+ var_3 := `71,897`
+ _, err = templBuffer.WriteString(var_3)
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("
- ")
+ if err != nil {
+ return err
+ }
+ var_4 := `Avg. Open Rate`
+ _, err = templBuffer.WriteString(var_4)
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("
- ")
+ if err != nil {
+ return err
+ }
+ var_5 := `58.16%`
+ _, err = templBuffer.WriteString(var_5)
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("
- ")
+ if err != nil {
+ return err
+ }
+ var_6 := `Avg. Click Rate`
+ _, err = templBuffer.WriteString(var_6)
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("
- ")
+ if err != nil {
+ return err
+ }
+ var_7 := `24.57%`
+ _, err = templBuffer.WriteString(var_7)
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("
")
+ if err != nil {
+ return err
+ }
+ if !templIsBuffer {
+ _, err = templBuffer.WriteTo(w)
+ }
+ return err
+ })
+}
diff --git a/internal/html/component/test_run_table_templ.go b/internal/html/component/test_run_table_templ.go
new file mode 100644
index 0000000..96fd18f
--- /dev/null
+++ b/internal/html/component/test_run_table_templ.go
@@ -0,0 +1,119 @@
+// Code generated by templ@v0.2.364 DO NOT EDIT.
+
+package component
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import "context"
+import "io"
+import "bytes"
+
+import (
+ "fmt"
+
+ "github.com/raphi011/handoff/internal/model"
+)
+
+func TestRunTable(tsr model.TestSuiteRun) templ.Component {
+ return templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
+ templBuffer, templIsBuffer := w.(*bytes.Buffer)
+ if !templIsBuffer {
+ templBuffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templBuffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ var_1 := templ.GetChildren(ctx)
+ if var_1 == nil {
+ var_1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, err = templBuffer.WriteString("")
+ if err != nil {
+ return err
+ }
+ var_2 := `Name`
+ _, err = templBuffer.WriteString(var_2)
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString(" | ")
+ if err != nil {
+ return err
+ }
+ var_3 := `Duration`
+ _, err = templBuffer.WriteString(var_3)
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString(" |
")
+ if err != nil {
+ return err
+ }
+ for _, tr := range tsr.TestResults {
+ _, err = templBuffer.WriteString("")
+ if err != nil {
+ return err
+ }
+ if tr.Result == "passed" {
+ _, err = templBuffer.WriteString("")
+ if err != nil {
+ return err
+ }
+ } else {
+ _, err = templBuffer.WriteString("")
+ if err != nil {
+ return err
+ }
+ }
+ _, err = templBuffer.WriteString("")
+ if err != nil {
+ return err
+ }
+ var var_5 string = tr.Name
+ _, err = templBuffer.WriteString(templ.EscapeString(var_5))
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString(" | ")
+ if err != nil {
+ return err
+ }
+ var var_6 string = fmt.Sprintf("%dms", tr.DurationInMS)
+ _, err = templBuffer.WriteString(templ.EscapeString(var_6))
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString(" | ")
+ if err != nil {
+ return err
+ }
+ var var_7 string = fmt.Sprintf("%v", tr.Spans)
+ _, err = templBuffer.WriteString(templ.EscapeString(var_7))
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString(" |
")
+ if err != nil {
+ return err
+ }
+ }
+ _, err = templBuffer.WriteString("
")
+ if err != nil {
+ return err
+ }
+ if !templIsBuffer {
+ _, err = templBuffer.WriteTo(w)
+ }
+ return err
+ })
+}
diff --git a/internal/html/test-run_templ.go b/internal/html/test-run_templ.go
new file mode 100644
index 0000000..4714106
--- /dev/null
+++ b/internal/html/test-run_templ.go
@@ -0,0 +1,441 @@
+// Code generated by templ@v0.2.364 DO NOT EDIT.
+
+package html
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import "context"
+import "io"
+import "bytes"
+
+import (
+ "fmt"
+
+ "github.com/raphi011/handoff/internal/html/component"
+ "github.com/raphi011/handoff/internal/model"
+)
+
+func RenderTestRun(tr model.TestRun) templ.Component {
+ return templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
+ templBuffer, templIsBuffer := w.(*bytes.Buffer)
+ if !templIsBuffer {
+ templBuffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templBuffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ var_1 := templ.GetChildren(ctx)
+ if var_1 == nil {
+ var_1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ var_2 := templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
+ templBuffer, templIsBuffer := w.(*bytes.Buffer)
+ if !templIsBuffer {
+ templBuffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templBuffer)
+ }
+ _, err = templBuffer.WriteString("")
+ if err != nil {
+ return err
+ }
+ var var_3 string = tr.Name
+ _, err = templBuffer.WriteString(templ.EscapeString(var_3))
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("
")
+ if err != nil {
+ return err
+ }
+ var_4 := `Logs`
+ _, err = templBuffer.WriteString(var_4)
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("
")
+ if err != nil {
+ return err
+ }
+ var var_5 string = tr.Logs
+ _, err = templBuffer.WriteString(templ.EscapeString(var_5))
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("
")
+ if err != nil {
+ return err
+ }
+ if !templIsBuffer {
+ _, err = io.Copy(w, templBuffer)
+ }
+ return err
+ })
+ err = body().Render(templ.WithChildren(ctx, var_2), templBuffer)
+ if err != nil {
+ return err
+ }
+ if !templIsBuffer {
+ _, err = templBuffer.WriteTo(w)
+ }
+ return err
+ })
+}
+
+func RenderSchedules(schedules []model.ScheduledRun) templ.Component {
+ return templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
+ templBuffer, templIsBuffer := w.(*bytes.Buffer)
+ if !templIsBuffer {
+ templBuffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templBuffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ var_6 := templ.GetChildren(ctx)
+ if var_6 == nil {
+ var_6 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ var_7 := templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
+ templBuffer, templIsBuffer := w.(*bytes.Buffer)
+ if !templIsBuffer {
+ templBuffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templBuffer)
+ }
+ _, err = templBuffer.WriteString("")
+ if err != nil {
+ return err
+ }
+ var_8 := `Scheduled runs`
+ _, err = templBuffer.WriteString(var_8)
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("
")
+ if err != nil {
+ return err
+ }
+ for _, s := range schedules {
+ _, err = templBuffer.WriteString("- ")
+ if err != nil {
+ return err
+ }
+ var var_9 string = s.Name
+ _, err = templBuffer.WriteString(templ.EscapeString(var_9))
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("
")
+ if err != nil {
+ return err
+ }
+ }
+ _, err = templBuffer.WriteString("
")
+ if err != nil {
+ return err
+ }
+ if !templIsBuffer {
+ _, err = io.Copy(w, templBuffer)
+ }
+ return err
+ })
+ err = body().Render(templ.WithChildren(ctx, var_7), templBuffer)
+ if err != nil {
+ return err
+ }
+ if !templIsBuffer {
+ _, err = templBuffer.WriteTo(w)
+ }
+ return err
+ })
+}
+
+func RenderTestSuiteRun(tsr model.TestSuiteRun) templ.Component {
+ return templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
+ templBuffer, templIsBuffer := w.(*bytes.Buffer)
+ if !templIsBuffer {
+ templBuffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templBuffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ var_10 := templ.GetChildren(ctx)
+ if var_10 == nil {
+ var_10 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ var_11 := templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
+ templBuffer, templIsBuffer := w.(*bytes.Buffer)
+ if !templIsBuffer {
+ templBuffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templBuffer)
+ }
+ err = component.Heading(tsr.SuiteName).Render(ctx, templBuffer)
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString(" ")
+ if err != nil {
+ return err
+ }
+ var_12 := `Started at `
+ _, err = templBuffer.WriteString(var_12)
+ if err != nil {
+ return err
+ }
+ var var_13 string = tsr.Start.Format("02.01 15:04:05")
+ _, err = templBuffer.WriteString(templ.EscapeString(var_13))
+ if err != nil {
+ return err
+ }
+ var_14 := `, took `
+ _, err = templBuffer.WriteString(var_14)
+ if err != nil {
+ return err
+ }
+ var var_15 string = fmt.Sprintf("%d", tsr.DurationInMS)
+ _, err = templBuffer.WriteString(templ.EscapeString(var_15))
+ if err != nil {
+ return err
+ }
+ var_16 := `ms to finish.`
+ _, err = templBuffer.WriteString(var_16)
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("
")
+ if err != nil {
+ return err
+ }
+ var_17 := `Is flaky: `
+ _, err = templBuffer.WriteString(var_17)
+ if err != nil {
+ return err
+ }
+ var var_18 string = fmt.Sprintf("%t", tsr.Flaky)
+ _, err = templBuffer.WriteString(templ.EscapeString(var_18))
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("
")
+ if err != nil {
+ return err
+ }
+ err = component.Stats().Render(ctx, templBuffer)
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString(" ")
+ if err != nil {
+ return err
+ }
+ var_19 := `Tests`
+ _, err = templBuffer.WriteString(var_19)
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("
")
+ if err != nil {
+ return err
+ }
+ err = component.TestRunTable(tsr).Render(ctx, templBuffer)
+ if err != nil {
+ return err
+ }
+ if !templIsBuffer {
+ _, err = io.Copy(w, templBuffer)
+ }
+ return err
+ })
+ err = body().Render(templ.WithChildren(ctx, var_11), templBuffer)
+ if err != nil {
+ return err
+ }
+ if !templIsBuffer {
+ _, err = templBuffer.WriteTo(w)
+ }
+ return err
+ })
+}
+
+func RenderTestSuiteRuns(description string, runs []model.TestSuiteRun) templ.Component {
+ return templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
+ templBuffer, templIsBuffer := w.(*bytes.Buffer)
+ if !templIsBuffer {
+ templBuffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templBuffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ var_20 := templ.GetChildren(ctx)
+ if var_20 == nil {
+ var_20 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ var_21 := templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
+ templBuffer, templIsBuffer := w.(*bytes.Buffer)
+ if !templIsBuffer {
+ templBuffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templBuffer)
+ }
+ _, err = templBuffer.WriteString("")
+ if err != nil {
+ return err
+ }
+ var_22 := `Testruns`
+ _, err = templBuffer.WriteString(var_22)
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("
")
+ if err != nil {
+ return err
+ }
+ err = templ.Raw(description).Render(ctx, templBuffer)
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("
")
+ if err != nil {
+ return err
+ }
+ for _, tsr := range runs {
+ _, err = templBuffer.WriteString("- ")
+ if err != nil {
+ return err
+ }
+ var var_24 string = tsr.SuiteName
+ _, err = templBuffer.WriteString(templ.EscapeString(var_24))
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString(" ")
+ if err != nil {
+ return err
+ }
+ var_25 := `(#`
+ _, err = templBuffer.WriteString(var_25)
+ if err != nil {
+ return err
+ }
+ var var_26 string = fmt.Sprintf("%d", tsr.ID)
+ _, err = templBuffer.WriteString(templ.EscapeString(var_26))
+ if err != nil {
+ return err
+ }
+ var_27 := `): `
+ _, err = templBuffer.WriteString(var_27)
+ if err != nil {
+ return err
+ }
+ var var_28 string = string(tsr.Result)
+ _, err = templBuffer.WriteString(templ.EscapeString(var_28))
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("
")
+ if err != nil {
+ return err
+ }
+ }
+ _, err = templBuffer.WriteString("
")
+ if err != nil {
+ return err
+ }
+ if !templIsBuffer {
+ _, err = io.Copy(w, templBuffer)
+ }
+ return err
+ })
+ err = body().Render(templ.WithChildren(ctx, var_21), templBuffer)
+ if err != nil {
+ return err
+ }
+ if !templIsBuffer {
+ _, err = templBuffer.WriteTo(w)
+ }
+ return err
+ })
+}
+
+func RenderTestSuites(suites []model.TestSuite) templ.Component {
+ return templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
+ templBuffer, templIsBuffer := w.(*bytes.Buffer)
+ if !templIsBuffer {
+ templBuffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templBuffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ var_29 := templ.GetChildren(ctx)
+ if var_29 == nil {
+ var_29 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ var_30 := templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
+ templBuffer, templIsBuffer := w.(*bytes.Buffer)
+ if !templIsBuffer {
+ templBuffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templBuffer)
+ }
+ _, err = templBuffer.WriteString("")
+ if err != nil {
+ return err
+ }
+ var_31 := `Test suites`
+ _, err = templBuffer.WriteString(var_31)
+ if err != nil {
+ return err
+ }
+ _, err = templBuffer.WriteString("
")
+ if err != nil {
+ return err
+ }
+ if !templIsBuffer {
+ _, err = io.Copy(w, templBuffer)
+ }
+ return err
+ })
+ err = body().Render(templ.WithChildren(ctx, var_30), templBuffer)
+ if err != nil {
+ return err
+ }
+ if !templIsBuffer {
+ _, err = templBuffer.WriteTo(w)
+ }
+ return err
+ })
+}