func (web *Web) AnalysisHandler(w http.ResponseWriter, r *http.Request) {
	analysisID, err := strconv.ParseInt(chi.URLParam(r, "analysisID"), 10, 32)
	if err != nil {
		web.errorHandler(w, r, http.StatusBadRequest, "Invalid analysis ID")
		return
	}
	logger := web.logger.With("analysisID", analysisID)
	analysis, err := web.db.GetAnalysis(int(analysisID))
	if err != nil {
		logger.With("error", err).Error("cannot get analysis")
		web.errorHandler(w, r, http.StatusInternalServerError, "Could not get analysis")
		return
	}
	if analysis == nil {
		web.NotFoundHandler(w, r)
		return
	}
	outputs, err := web.db.AnalysisOutputs(analysis.ID)
	if err != nil {
		logger.With("error", err).Error("cannot get analysis output")
		web.errorHandler(w, r, http.StatusInternalServerError, "Could not get analysis output")
		return
	}
	vcs, err := NewVCS(web.gh, analysis)
	if err != nil {
		logger.With("error", err).Error("cannot get analysis VCS")
		web.errorHandler(w, r, http.StatusInternalServerError, "Could not get VCS")
		return
	}
	var patches []Patch
	if len(analysis.Issues()) > 0 {
		diffReader, err := vcs.Diff(r.Context(), analysis.RepositoryID, analysis.CommitFrom, analysis.CommitTo, analysis.RequestNumber)
		switch {
		case err != nil:
			logger.With("error", err).Error("cannot get diff from VCS")
		case diffReader != nil:
			defer diffReader.Close()
			patches, err = DiffIssues(r.Context(), diffReader, analysis.Issues())
			if err != nil {
				logger.With("error", err).Error("cannot diff issues from VCS diff")
				web.errorHandler(w, r, http.StatusInternalServerError, "Could not read VCS")
				return
			}
		}
	}
	var page = struct {
		Title       string
		Analysis    *db.Analysis
		Patches     []Patch
		Outputs     []db.Output
		TotalIssues int
	}{
		Title:       "Analysis",
		Analysis:    analysis,
		Patches:     patches,
		Outputs:     outputs,
		TotalIssues: len(analysis.Issues()),
	}
	if err := web.templates.ExecuteTemplate(w, "analysis.tmpl", page); err != nil {
		logger.With("error", err).Error("cannot parse analysis template")
	}
}
AnalysisHandler displays a single analysis.
package web
import (
	"fmt"
	"html/template"
	"net/http"
	"strconv"
	"github.com/bradleyfalzon/gopherci/internal/db"
	"github.com/bradleyfalzon/gopherci/internal/github"
	"github.com/bradleyfalzon/gopherci/internal/logger"
	"github.com/go-chi/chi"
)
type Web struct {
	logger    logger.Logger
	db        db.DB
	gh        *github.GitHub
	templates *template.Template
}
func NewWeb(logger logger.Logger, db db.DB, gh *github.GitHub) (*Web, error) {
	templates, err := template.ParseGlob("internal/web/templates/*.tmpl")
	if err != nil {
		return nil, err
	}
	web := &Web{
		logger:    logger,
		db:        db,
		gh:        gh,
		templates: templates,
	}
	return web, nil
}
func (web *Web) NotFoundHandler(w http.ResponseWriter, r *http.Request) {
	web.errorHandler(w, r, http.StatusNotFound, fmt.Sprintf("%q not found", r.URL))
}
func (web *Web) errorHandler(w http.ResponseWriter, r *http.Request, code int, desc string) {
	page := struct {
		Title  string
		Code   string  
		Status string  
		Desc   string  
	}{fmt.Sprintf("%d - %s", code, http.StatusText(code)), strconv.Itoa(code), http.StatusText(code), desc}
	if page.Desc == "" {
		page.Desc = http.StatusText(code)
	}
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.WriteHeader(code)
	if err := web.templates.ExecuteTemplate(w, "error.tmpl", page); err != nil {
		web.logger.With("error", err).Error("cannot parse error template")
	}
}