Skip to content

Commit

Permalink
Separate web frontend out into its own service
Browse files Browse the repository at this point in the history
This will make ghviz easier to dockerize as well as opens up a path
towards server side rendering.
  • Loading branch information
ksheedlo committed Apr 10, 2016
1 parent fb3d00c commit fded37f
Show file tree
Hide file tree
Showing 10 changed files with 129 additions and 64 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ services/web/web

npm-debug.log

dashboard/dist/
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ all: js go
dashboard/bundle.min.js: dashboard/index.js dashboard/api-client.js dashboard/cache.js dashboard/helpers.js dashboard/components/*.js
cd dashboard; NODE_ENV=$(NODE_ENV) ./node_modules/.bin/webpack

js: dashboard/bundle.min.js
dashboard/dist/server.js: dashboard/server.js
cd dashboard; ./node_modules/.bin/webpack --config webpack-server.config.js

js: dashboard/bundle.min.js dashboard/dist/server.js

services/web/web: errors/*.go github/*.go interfaces/*.go middleware/*.go models/*.go routes/*.go services/web/*.go simulate/*.go
cd services/web; go build
Expand All @@ -17,7 +20,7 @@ services/prewarm/prewarm: errors/*.go github/*.go interfaces/*.go prewarm/*.go s
go: services/prewarm/prewarm services/web/web

clean:
rm dashboard/bundle.min.js dashboard/*.js.map services/prewarm/prewarm services/web/web
rm dashboard/bundle.min.js dashboard/*.js.map dashboard/dist/server.js services/prewarm/prewarm services/web/web

jsclean:
rm dashboard/bundle.min.js dashboard/*.js.map
Expand Down
9 changes: 5 additions & 4 deletions index.tpl.html → dashboard/index.tpl.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="https://github.com/{{.Owner}}/{{.Repo}}" target="_blank">
{{.Owner}}/{{.Repo}}
<a class="navbar-brand" target="_blank"
href="https://github.com/{{owner}}/{{repo}}">
{{owner}}/{{repo}}
</a>
</div>
</div>
Expand Down Expand Up @@ -67,8 +68,8 @@
</div>
<script type="text/javascript">
window.GLOBALS = {
owner: "{{.Owner}}",
repo: "{{.Repo}}"
owner: "{{owner}}",
repo: "{{repo}}"
};
</script>
<script type="text/javascript" src="/dashboard/bundle.min.js"></script>
Expand Down
6 changes: 3 additions & 3 deletions dashboard/karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ module.exports = function(config) {
files: [
'third_party/sinon/1.17.3/sinon-1.17.3.js',

'test/**/*.js'
'test/**/*.js',
],
logLevel: config.LOG_INFO,
port: 9876,
preprocessors: {
'test/**/*.js': ['webpack']
'test/**/*.js': ['webpack'],
},
reporters: ['progress'],
singleRun: false,
webpack: webpackConfig,
webpackMiddleware: { noInfo: true }
webpackMiddleware: { noInfo: true },
});
};
7 changes: 6 additions & 1 deletion dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "",
"main": "index.js",
"scripts": {
"lint": "eslint api-client.js cache.js helpers.js index.js webpack.config.js components/*.js test/*.js",
"lint": "eslint '!(bundle)*.js' components/*.js test/*.js",
"pretest": "npm run lint",
"test": "karma start --single-run"
},
Expand All @@ -14,9 +14,14 @@
"bluebird": "3.3.3",
"bootstrap": "3.3.6",
"d3": "3.5.16",
"express": "4.13.4",
"hogan.js": "3.0.2",
"http-proxy": "1.13.2",
"lodash": "4.9.0",
"lodash.constant": "3.0.0",
"lodash.flatten": "4.1.0",
"lodash.map": "4.2.1",
"morgan": "1.7.0",
"react": "0.14.7",
"react-dom": "0.14.7",
"whatwg-fetch": "0.11.0"
Expand Down
70 changes: 70 additions & 0 deletions dashboard/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import express, { Router } from 'express';
import fs from 'fs';
import hogan from 'hogan.js';
import httpProxy from 'http-proxy';
import { includes } from 'lodash';
import morgan from 'morgan';
import { parse } from 'url';
import process from 'process';

const app = express();
const router = Router();
const indexTpl = hogan.compile(fs.readFileSync('./index.tpl.html', 'utf-8'));
const proxy = httpProxy.createProxyServer({
target: `${process.env.GHVIZ_API_URL}/`,
});

proxy.on('proxyReq', (proxyReq, req) => {
proxyReq.path = parse(req.url).path.replace(/^\/gh/, '');
});

router.all('/gh/*', (req, res) => {
proxy.web(req, res, {});
});

router.get('/', (req, res) => {
res.send(indexTpl.render({
owner: process.env.GHVIZ_OWNER,
repo: process.env.GHVIZ_REPO,
}));
});

function getConfiguredPort() {
if (process.env.GHVIZ_WEB_PORT) {
const integerPort = (process.env.GHVIZ_WEB_PORT|0);
if (0 < integerPort && integerPort < 65536) {
return integerPort;
}
}
return 4001;
}

const ALLOWED_STATIC_FILES = [
'/bundle.js',
'/bundle.js.map',
'/bundle.min.js',
'/bundle.min.js.map',
'/main.css',
'/node_modules/bootstrap/dist/css/bootstrap.min.css',
'/node_modules/bootstrap/dist/css/bootstrap.min.css.map',
'/third_party/octicons/octicons.css',
'/third_party/octicons/octicons.ttf',
'/third_party/octicons/octicons.woff',
];

function restrictDashboardStaticFiles(req, res, next) {
if (!includes(ALLOWED_STATIC_FILES, req.path)) {
return res.sendStatus(404);
}
next();
}

app.use(morgan('short'));
app.use(router);
app.use('/dashboard', restrictDashboardStaticFiles, express.static('.'));
const configuredPort = getConfiguredPort();
app.listen(configuredPort, function () {
/*eslint-disable no-console*/
console.log(`${process.argv[1]} listening on port ${configuredPort}`);
/*eslint-enable no-console*/
});
34 changes: 34 additions & 0 deletions dashboard/webpack-server.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use strict';

var path = require('path');
var fs = require('fs');
var _ = require('lodash');

/*global __dirname*/

exports.externals = _.fromPairs(_.map(
_.filter(fs.readdirSync('node_modules'), function (dir) {
return ['.bin'].indexOf(dir) === -1;
}),
function (module) {
return [module, 'commonjs ' + module];
})
);

exports.entry = './server.js';

exports.target = 'node';

exports.output = {
path: path.join(__dirname, 'dist'),
filename: 'server.js',
};

exports.module = {
loaders: [{
exclude: /node_modules/,
loader: 'babel',
query: { presets: ['es2015', 'react', 'stage-2'] },
test: /\.js$/,
}],
};
13 changes: 0 additions & 13 deletions routes/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"net/http"
"sort"
"strconv"
"text/template"
"time"

"github.com/gorilla/context"
Expand Down Expand Up @@ -93,18 +92,6 @@ func TopPrs(gh github.ListTopPrser) http.HandlerFunc {
}
}

type IndexParams struct {
Owner string
Repo string
}

func ServeIndex(params *IndexParams, tpl *template.Template) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
tpl.Execute(w, params)
}
}

func HighScores(redis interfaces.Rediser) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
logger := context.Get(r, middleware.CtxLog).(*log.Logger)
Expand Down
22 changes: 0 additions & 22 deletions routes/routes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"net/http/httptest"
"strconv"
"testing"
"text/template"
"time"

"github.com/ksheedlo/ghviz/errors"
Expand Down Expand Up @@ -347,27 +346,6 @@ func TestTopPrsError(t *testing.T) {
assert.Equal(t, "Github API Error\n", w.Body.String())
}

func TestServeIndex(t *testing.T) {
t.Parallel()

r := mux.NewRouter()
logger := mocks.DummyLogger(t)
tpl, err := template.New("index").Parse("<Test>{{.Owner}}|{{.Repo}}</Test>")
assert.NoError(t, err)
r.HandleFunc("/", ServeIndex(&IndexParams{
Owner: "tester1",
Repo: "coolrepo",
}, tpl))
req := mocks.NewHttpRequest(t, "GET", "http://example.com/", nil)
context.Set(req, middleware.CtxLog, logger)

w := httptest.NewRecorder()
r.ServeHTTP(w, req)

assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "<Test>tester1|coolrepo</Test>", w.Body.String())
}

func TestHighScoresBadYear(t *testing.T) {
t.Parallel()

Expand Down
24 changes: 5 additions & 19 deletions services/web/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import (
"fmt"
"net/http"
"os"
"path"
"text/template"

"github.com/gorilla/mux"
"gopkg.in/redis.v3"
Expand All @@ -16,13 +14,6 @@ import (
"github.com/ksheedlo/ghviz/routes"
)

var indexTpl *template.Template = template.Must(template.ParseFiles("index.tpl.html"))

func ServeStaticFile(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
http.ServeFile(w, r, path.Join("dashboard", vars["path"]))
}

func withDefaultStr(config, default_ string) string {
if config == "" {
return default_
Expand Down Expand Up @@ -54,23 +45,18 @@ func main() {
middleware.LogRequest,
middleware.Gzip,
)
r.HandleFunc("/", withMiddleware(routes.ServeIndex(&routes.IndexParams{
Owner: os.Getenv("GHVIZ_OWNER"),
Repo: os.Getenv("GHVIZ_REPO"),
}, indexTpl)))
r.HandleFunc("/dashboard/{path:.*}", withMiddleware(ServeStaticFile))
r.HandleFunc(
"/gh/{owner}/{repo}/star_counts",
"/{owner}/{repo}/star_counts",
withMiddleware(routes.ListStarCounts(gh)),
)
r.HandleFunc(
"/gh/{owner}/{repo}/issue_counts",
"/{owner}/{repo}/issue_counts",
withMiddleware(routes.ListOpenIssuesAndPrs(gh)),
)
r.HandleFunc("/gh/{owner}/{repo}/top_issues", withMiddleware(routes.TopIssues(gh)))
r.HandleFunc("/gh/{owner}/{repo}/top_prs", withMiddleware(routes.TopPrs(gh)))
r.HandleFunc("/{owner}/{repo}/top_issues", withMiddleware(routes.TopIssues(gh)))
r.HandleFunc("/{owner}/{repo}/top_prs", withMiddleware(routes.TopPrs(gh)))
r.HandleFunc(
"/gh/{owner}/{repo}/highscores/{year:[0-9]+}/{month:(0[1-9]|1[012])}",
"/{owner}/{repo}/highscores/{year:[0-9]+}/{month:(0[1-9]|1[012])}",
withMiddleware(routes.HighScores(redisClient)),
)
http.ListenAndServe(":4000", r)
Expand Down

0 comments on commit fded37f

Please sign in to comment.