Skip to content

Commit

Permalink
Merge pull request #5 from LuBingtan/add-dashboard
Browse files Browse the repository at this point in the history
Add event list on dashboard
  • Loading branch information
kuilei-bot[bot] committed Jan 17, 2023
2 parents 8b2b174 + c4bcea9 commit db20bd6
Show file tree
Hide file tree
Showing 19 changed files with 992 additions and 244 deletions.
21 changes: 18 additions & 3 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@ import (
"github.com/google/go-github/v48/github"
"github.com/shurcooL/githubv4"
"github.com/spf13/pflag"

"github.com/airconduct/go-probot/web"
)

func NewGithubAPP() App[GithubClient] {
return &githubApp{
handlers: make(map[string]Handler),
clients: make(map[int64]*github.Client),
graphqlClients: make(map[int64]*githubv4.Client),
serveMux: http.NewServeMux(),
metrics: new(eventMetrics),
}
}

Expand All @@ -36,6 +40,9 @@ type githubApp struct {
graphqlURL string
uploadURL string
serverOpts ServerOptions
serveMux *http.ServeMux

metrics *eventMetrics

dataMutex sync.RWMutex
hmacToken []byte
Expand Down Expand Up @@ -82,6 +89,7 @@ func (app *githubApp) On(events ...WebhookEvent) handlerLoader {
return fmt.Errorf("event type %s already exists", key)
}
app.handlers[key] = h
app.metrics.add(key, event.Type())
}
return nil
})
Expand All @@ -92,17 +100,21 @@ func (app *githubApp) Run(ctx context.Context) error {
return err
}

mux := http.NewServeMux()
if app.serverOpts.Path == "" {
app.serverOpts.Path = "/"
}
mux.HandleFunc(app.serverOpts.Path, app.handle)
server := &http.Server{Addr: fmt.Sprintf("%s:%d", app.serverOpts.Address, app.serverOpts.Port), Handler: mux}
app.serveMux.HandleFunc(app.serverOpts.Path, app.handle)
web.RegisterHandler(app.serveMux, app.metrics)
server := &http.Server{Addr: fmt.Sprintf("%s:%d", app.serverOpts.Address, app.serverOpts.Port), Handler: app.serveMux}
server.RegisterOnShutdown(app.shutdown)
app.logger.Info("Kuilei hook is serving", "addr", server.Addr)
return server.ListenAndServe()
}

func (app *githubApp) ServeMux() *http.ServeMux {
return app.serveMux
}

func (app *githubApp) shutdown() {}

func (app *githubApp) initialize() error {
Expand Down Expand Up @@ -151,6 +163,9 @@ func (app *githubApp) handle(w http.ResponseWriter, r *http.Request) {
return
}
app.logger.Info("Handle event", "event", event)
defer func() {
app.metrics.inc(handlerKey, event)
}()

switch event {
case "branch_protection_rule":
Expand Down
14 changes: 14 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ go 1.19

require (
github.com/bradleyfalzon/ghinstallation v1.1.1
github.com/emicklei/go-restful-openapi/v2 v2.9.1
github.com/emicklei/go-restful/v3 v3.10.1
github.com/go-logr/logr v1.2.3
github.com/go-logr/zapr v1.2.3
github.com/go-openapi/spec v0.20.7
github.com/google/go-github/v48 v48.2.0
github.com/h2non/gock v1.2.0
github.com/onsi/ginkgo/v2 v2.7.0
Expand All @@ -17,16 +20,27 @@ require (

require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-github/v29 v29.0.2 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
golang.org/x/net v0.4.0 // indirect
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect
golang.org/x/sys v0.3.0 // indirect
golang.org/x/text v0.5.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
396 changes: 391 additions & 5 deletions go.sum

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package probot

import (
"context"
"net/http"

"github.com/go-logr/logr"
"github.com/spf13/pflag"
Expand All @@ -11,6 +12,8 @@ type App[GT GitClientType] interface {
AddFlags(flags *pflag.FlagSet)
On(events ...WebhookEvent) handlerLoader
Run(ctx context.Context) error

ServeMux() *http.ServeMux
}

type ProbotContext[GT GitClientType, PT gitEventType] interface {
Expand Down
60 changes: 60 additions & 0 deletions metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package probot

import (
"context"
"sort"
"strings"
"sync"

"github.com/airconduct/go-probot/web/backend"
)

type eventMetrics struct {
events sync.Map
}

var _ backend.EventSource = &eventMetrics{}

func (m *eventMetrics) add(name, tp string) {
m.events.Store(name, backend.Event{
Name: name, Type: backend.EventType(tp),
Metrics: backend.EventMetrics{},
})
}

func (m *eventMetrics) inc(name, tp string) {
e, _ := m.events.LoadOrStore(name, backend.Event{
Name: name, Type: backend.EventType(tp),
Metrics: backend.EventMetrics{},
})
event := e.(backend.Event)
event.Metrics.Count++
m.events.Store(name, event)
}

func (m *eventMetrics) ListEvent(ctx context.Context, opts backend.ListOptions) (backend.EventList, error) {
out := backend.EventList{}
m.events.Range(func(key, value any) bool {
out.Items = append(out.Items, value.(backend.Event))
return true
})
sort.Slice(out.Items, func(i, j int) bool {
return strings.Compare(out.Items[i].Name, out.Items[j].Name) < 0
})
if opts.Limit > 0 {
start := opts.Offset * opts.Limit
end := opts.Offset*opts.Limit + opts.Limit
if start < 0 {
start = 0
} else if start > int64(len(out.Items)) {
start = int64(len(out.Items))
}
if end < 0 {
end = 0
} else if end > int64(len(out.Items)) {
end = int64(len(out.Items))
}
out.Items = out.Items[start:end]
}
return out, nil
}
36 changes: 36 additions & 0 deletions web/backend/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package backend

import (
"strconv"

"github.com/emicklei/go-restful/v3"
)

func WithListOptionsParam(builder *restful.RouteBuilder) *restful.RouteBuilder {
return builder.Param(restful.QueryParameter("page_size", "The maximum number of return items")).
Param(restful.QueryParameter("page_num", "The first (page_size * page_num) items will be skipped.")).
Param(restful.QueryParameter("filter", "The returned items will be filtered according to the filter."))
}

func ListOptionsFromRequest(req *restful.Request) (opts ListOptions, err error) {
if raw := req.QueryParameter("page_size"); raw != "" {
opts.Limit, err = strconv.ParseInt(req.QueryParameter("page_size"), 10, 64)
if err != nil {
return
}
}
if raw := req.QueryParameter("page_num"); raw != "" {
opts.Offset, err = strconv.ParseInt(req.QueryParameter("page_num"), 10, 64)
if err != nil {
return
}
}
opts.Filter = req.QueryParameter("filter")
return
}

type ListOptions struct {
Limit int64
Offset int64
Filter string
}
67 changes: 67 additions & 0 deletions web/backend/event.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package backend

import (
"context"
"net/http"

restfulspec "github.com/emicklei/go-restful-openapi/v2"
"github.com/emicklei/go-restful/v3"
)

func AddEventService(ws *restful.WebService, es EventSource) {
handler := &eventHandler{es: es}
tags := []string{"events"}
ws.Route(WithListOptionsParam(
ws.GET("/events").To(handler.ListEvent).
Metadata(restfulspec.KeyOpenAPITags, tags).
Doc("get all events").
Returns(200, "OK", EventList{}),
))
}

type eventHandler struct {
es EventSource
}

func (h *eventHandler) ListEvent(req *restful.Request, resp *restful.Response) {
ctx, cancel := context.WithCancel(req.Request.Context())
defer cancel()
opts, err := ListOptionsFromRequest(req)
if err != nil {
resp.WriteError(http.StatusBadRequest, err)
return
}

events, err := h.es.ListEvent(ctx, opts)
if err != nil {
resp.WriteError(http.StatusBadRequest, err)
return
}
if err := resp.WriteEntity(events); err != nil {
resp.WriteError(http.StatusBadRequest, err)
return
}
}

type EventSource interface {
ListEvent(ctx context.Context, opts ListOptions) (EventList, error)
}

type EventList struct {
Items []Event `json:"items"`
}

type Event struct {
// Name of event, it should be shown on dashboard
Name string `json:"name"`
// Type of event. All events can be grouped by the type.
Type EventType `json:"type"`
// Metrics is the metrical data about this event
Metrics EventMetrics `json:"metrics"`
}

type EventType string

type EventMetrics struct {
Count int64 `json:"count"`
}
23 changes: 23 additions & 0 deletions web/backend/event_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package backend

import (
"encoding/json"
"fmt"
"testing"
)

func TestEvent(t *testing.T) {
events := EventList{Items: []Event{
{Name: "foo", Type: EventType("foo"), Metrics: EventMetrics{
Count: 1,
}},
{Name: "bar", Type: EventType("bar"), Metrics: EventMetrics{
Count: 1,
}},
}}
raw, err := json.Marshal(events)
if err != nil {
fmt.Println(err)
}
fmt.Println(string(raw))
}
15 changes: 15 additions & 0 deletions web/backend/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package backend

import (
restful "github.com/emicklei/go-restful/v3"
)

func WebService(event EventSource) *restful.WebService {
ws := new(restful.WebService)
ws.Path("/api")
ws.Consumes(restful.MIME_JSON)
ws.Produces(restful.MIME_JSON) // you can specify this per route as well

AddEventService(ws, event)
return ws
}
1 change: 1 addition & 0 deletions web/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"private": true,
"version": "0.0.0",
"type": "module",
"proxy": "http://localhost:7771",
"scripts": {
"dev": "vite",
"build": "tsc && vite build --outDir=../dist --emptyOutDir",
Expand Down
27 changes: 3 additions & 24 deletions web/dashboard/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ import './App.css'

import { Routes, Route, Outlet, Link } from "react-router-dom";

import { Menu, Card, Collapse, Space } from "antd";
import { Menu } from "antd";
import {
DashboardOutlined, PullRequestOutlined, InfoCircleOutlined,
DashboardOutlined,
CopyrightOutlined
} from '@ant-design/icons';
import 'antd/dist/reset.css';

const { Panel } = Collapse;
import Welcome from './Welcome'
import ErrorPage from './error-page'
import Dashboard from './Dashboard'

export default function App() {
return <div id='app'>
Expand Down Expand Up @@ -58,27 +58,6 @@ function About() {
</div>
}

function Dashboard() {
const onChange = (key: string | string[]) => {
console.log(key);
};
return <div>
<h1>Event Listener</h1>
<Collapse onChange={onChange} accordion>
<Panel key="2" header={
<Space><InfoCircleOutlined /><div>issue</div></Space>
}>
FOO
</Panel>
<Panel key="1" header={
<Space><PullRequestOutlined /><div>pulls</div></Space>
}>
BAR
</Panel>
</Collapse>
</div>
}

function NoMatch() {
return (
// <ErrorPage></ErrorPage>
Expand Down
Loading

0 comments on commit db20bd6

Please sign in to comment.