Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

initial web version #89

Merged
merged 1 commit into from
Oct 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions chi_util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package main

import (
"net/http"
"strings"

"github.com/go-chi/chi"
"github.com/go-chi/render"
)

// taken from
// - https://github.com/go-chi/chi/blob/cca4135d8dddff765463feaf1118047a9e506b4a/_examples/fileserver/main.go
// - https://github.com/go-chi/chi/blob/cca4135d8dddff765463feaf1118047a9e506b4a/_examples/rest/main.go

func (e *ErrResponse) Render(w http.ResponseWriter, r *http.Request) error {
render.Status(r, e.HTTPStatusCode)
return nil
}

type ErrResponse struct {
Err error `json:"-"` // low-level runtime error
HTTPStatusCode int `json:"-"` // http response status code

StatusText string `json:"status"` // user-level status message
AppCode int64 `json:"code,omitempty"` // application-specific error code
ErrorText string `json:"error,omitempty"` // application-level error message, for debugging
}

func ErrRender(err error) render.Renderer {
return &ErrResponse{
Err: err,
HTTPStatusCode: 422,
StatusText: "Error rendering response.",
ErrorText: err.Error(),
}
}

func FileServer(r chi.Router, path string, root http.FileSystem) {
if strings.ContainsAny(path, "{}*") {
panic("FileServer does not permit URL parameters.")
}

fs := http.StripPrefix(path, http.FileServer(root))

if path != "/" && path[len(path)-1] != '/' {
r.Get(path, http.RedirectHandler(path+"/", 301).ServeHTTP)
path += "/"
}
path += "*"

r.Get(path, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fs.ServeHTTP(w, r)
}))
}
3 changes: 0 additions & 3 deletions cmd_db.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ import (
)

type dbOptions struct {
Path string `mapstructure:"dbpath"`
Verbose bool
}

func (opts dbOptions) String() string {
Expand All @@ -22,7 +20,6 @@ func (opts dbOptions) String() string {
}

func dbSetupFlags(flags *pflag.FlagSet, opts *dbOptions) {

viper.BindPFlags(flags)
}

Expand Down
48 changes: 23 additions & 25 deletions cmd_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,13 @@ type runOptions struct {
ReposToFetch []string

// db
DBOpts dbOptions
DBOpts dbOptions
GraphOpts graphOptions

// run
ShowClosed bool `mapstructure:"show-closed"`
ShowOrphans bool
AdditionalPulls []string
EpicLabel string
Destination string
DebugGraph bool
NoCompress bool
DarkTheme bool

Targets []string
//Preview bool
}

Expand All @@ -43,12 +37,6 @@ func (opts runOptions) String() string {

func runSetupFlags(flags *pflag.FlagSet, opts *runOptions) {
flags.BoolVarP(&opts.NoPull, "no-pull", "f", false, "do not pull new issues before runing")
flags.BoolVarP(&opts.ShowClosed, "show-closed", "", false, "show closed issues")
flags.BoolVarP(&opts.DebugGraph, "debug-graph", "", false, "debug graph")
flags.BoolVarP(&opts.ShowOrphans, "show-orphans", "", false, "show issues not linked to an epic")
flags.BoolVarP(&opts.NoCompress, "no-compress", "", false, "do not compress graph (no overlap)")
flags.BoolVarP(&opts.DarkTheme, "dark-theme", "", false, "dark theme")
flags.StringVarP(&opts.EpicLabel, "epic-label", "", "epic", "label used for epics (empty means issues with dependencies but without dependants)")
flags.StringVarP(&opts.Destination, "destination", "", "-", "destination ('-' for stdout)")
flags.StringSliceVarP(&opts.AdditionalPulls, "additional-pull", "", []string{}, "additional pull that won't necessarily be displayed on the graph")
//flags.BoolVarP(&opts.Preview, "preview", "p", false, "preview result")
Expand All @@ -69,9 +57,12 @@ func newRunCommand() *cobra.Command {
if err := viper.Unmarshal(&opts.DBOpts); err != nil {
return err
}
if err := viper.Unmarshal(&opts.GraphOpts); err != nil {
return err
}
opts.PullOpts.DBOpts = opts.DBOpts
opts.PullOpts.Targets = append(args, opts.AdditionalPulls...)
opts.Targets = args
opts.GraphOpts.Targets = args
return run(opts)
},
}
Expand All @@ -81,21 +72,17 @@ func newRunCommand() *cobra.Command {
return cmd
}

func run(opts *runOptions) error {
logger().Debug("run", zap.Stringer("opts", *opts))
if !opts.NoPull {
if err := pull(&opts.PullOpts); err != nil {
return err
}
func graphviz(opts *graphOptions) (string, error) {
if opts.Targets == nil || len(opts.Targets) < 1 || opts.Targets[0] == "" {
return "", fmt.Errorf("you need to specify at least one target")
}

issues, err := loadIssues(db, nil)
if err != nil {
return errors.Wrap(err, "failed to load issues")
return "", errors.Wrap(err, "failed to load issues")
}

if err := issues.prepare(); err != nil {
return errors.Wrap(err, "failed to prepare issues")
return "", errors.Wrap(err, "failed to prepare issues")
}

if !opts.ShowClosed {
Expand All @@ -106,7 +93,18 @@ func run(opts *runOptions) error {
logger().Warn("--show-orphans is deprecated and will be removed")
}

out, err := graphviz(issues, opts)
return graphvizRender(issues, opts)
}

func run(opts *runOptions) error {
logger().Debug("run", zap.Stringer("opts", *opts))
if !opts.NoPull {
if err := pull(&opts.PullOpts); err != nil {
return err
}
}

out, err := graphviz(&opts.GraphOpts)
if err != nil {
return err
}
Expand Down
153 changes: 153 additions & 0 deletions cmd_web.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package main

import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
"time"

"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"github.com/go-chi/docgen"
"github.com/go-chi/render"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)

type webOptions struct {
// web specific
Bind string
ShowRoutes bool

// db
DBOpts dbOptions
}

func (opts webOptions) String() string {
out, _ := json.Marshal(opts)
return string(out)
}

func webSetupFlags(flags *pflag.FlagSet, opts *webOptions) {
flags.StringVarP(&opts.Bind, "bind", "b", ":2020", "web server bind address")
flags.BoolVarP(&opts.ShowRoutes, "show-routes", "", false, "display available routes and quit")
viper.BindPFlags(flags)
}

func newWebCommand() *cobra.Command {
opts := &webOptions{}
cmd := &cobra.Command{
Use: "web",
RunE: func(cmd *cobra.Command, args []string) error {
if err := viper.Unmarshal(opts); err != nil {
return err
}
if err := viper.Unmarshal(&opts.DBOpts); err != nil {
return err
}
return web(opts)
},
}
webSetupFlags(cmd.Flags(), opts)
dbSetupFlags(cmd.Flags(), &opts.DBOpts)
return cmd
}

func (i *Issue) Render(w http.ResponseWriter, r *http.Request) error {
return nil
}

func webListIssues(w http.ResponseWriter, r *http.Request) {
issues, err := loadIssues(db, nil)
if err != nil {
render.Render(w, r, ErrRender(err))
return
}

list := []render.Renderer{}
for _, issue := range issues {
list = append(list, issue)
}

if err := render.RenderList(w, r, list); err != nil {
render.Render(w, r, ErrRender(err))
return
}
}

func webGraphviz(r *http.Request) (string, error) {
opts := &graphOptions{
Targets: strings.Split(r.URL.Query().Get("targets"), ","),
ShowClosed: r.URL.Query().Get("show-closed") == "1",
}
return graphviz(opts)
}

func webDotIssues(w http.ResponseWriter, r *http.Request) {
out, err := webGraphviz(r)
if err != nil {
render.Render(w, r, ErrRender(err))
return
}

w.Write([]byte(out))
}

func webImageIssues(w http.ResponseWriter, r *http.Request) {
out, err := webGraphviz(r)
if err != nil {
render.Render(w, r, ErrRender(err))
return
}

cmd := exec.Command("dot", "-Tsvg")
cmd.Stdin = bytes.NewBuffer([]byte(out))
cmd.Stdout = w

if err := cmd.Run(); err != nil {
render.Render(w, r, ErrRender(err))
return
}
}

func web(opts *webOptions) error {
r := chi.NewRouter()

//r.Use(middleware.RequestID)
//r.Use(middleware.RealIP)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
//r.Use(middleware.URLFormat)
r.Use(middleware.Timeout(5 * time.Second))

r.Route("/api", func(r chi.Router) {
r.Route("/", func(r chi.Router) {
r.Use(render.SetContentType(render.ContentTypeJSON))
r.Get("/issues.json", webListIssues)
})
r.Get("/graph/dot", webDotIssues)
r.Get("/graph/image", webImageIssues)
})

workDir, _ := os.Getwd()
filesDir := filepath.Join(workDir, "web")
FileServer(r, "/", http.Dir(filesDir))

if opts.ShowRoutes {
fmt.Println(docgen.MarkdownRoutesDoc(r, docgen.MarkdownOpts{
ProjectPath: "moul.io/depviz",
Intro: "Welcome to depviz generated docs.",
}))
return nil
}

log.Printf("Listening on %s", opts.Bind)
return http.ListenAndServe(opts.Bind, r)
}
15 changes: 15 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
module moul.io/depviz

require (
cloud.google.com/go v0.29.0 // indirect
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/awalterschulze/gographviz v0.0.0-20180927133620-e69668a01397
github.com/denisenkom/go-mssqldb v0.0.0-20180901172138-1eb28afdf9b6 // indirect
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect
github.com/go-chi/chi v3.3.3+incompatible
github.com/go-chi/docgen v1.0.2 // indirect
github.com/go-chi/render v1.0.1
github.com/go-sql-driver/mysql v1.4.0 // indirect
github.com/google/go-cmp v0.2.0 // indirect
github.com/google/go-github v17.0.0+incompatible
github.com/google/go-querystring v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jinzhu/gorm v1.9.1
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a // indirect
github.com/jinzhu/now v0.0.0-20180511015916-ed742868f2ae // indirect
github.com/lib/pq v1.0.0 // indirect
github.com/mattn/go-sqlite3 v1.9.0
github.com/pkg/errors v0.8.0
github.com/spf13/cobra v0.0.3
github.com/spf13/pflag v1.0.3
github.com/spf13/viper v1.2.1
github.com/xanzy/go-gitlab v0.11.1
go.uber.org/zap v1.9.1
golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4 // indirect
golang.org/x/net v0.0.0-20181003013248-f5e5bdd77824 // indirect
golang.org/x/oauth2 v0.0.0-20181003184128-c57b0facaced
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect
google.golang.org/appengine v1.2.0 // indirect
moul.io/zapgorm v0.0.0-20181003053625-c808c1c4adc6
)
Loading