Skip to content

Commit c5fbcad

Browse files
h4rikrisravisuhag
andauthored
feat: base Stencil UI (#135)
Co-authored-by: Ravi Suhag <suhag.ravi@gmail.com>
1 parent 6ea8a49 commit c5fbcad

28 files changed

Lines changed: 9299 additions & 11 deletions

.golangci.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ run:
66
- clients/js
77
- docs
88
- scripts
9+
- ui
910
output:
1011
format: line-number
1112
linters:

Makefile

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@ NAME="github.com/odpf/stencil"
22
VERSION=$(shell git describe --always --tags 2>/dev/null)
33
PROTON_COMMIT := "a6c7056fa80128145d00d5ee72f216c28578ec43"
44

5-
.PHONY: all build test clean dist vet proto install
5+
.PHONY: all build test clean dist vet proto install ui
66

77
all: build
88

9-
build: ## Build the stencil binary
9+
build: ui ## Build the stencil binary
1010
go build -ldflags "-X config.Version=${VERSION}" ${NAME}
1111

12-
test: ## Run the tests
12+
test: ui ## Run the tests
1313
go test ./... -coverprofile=coverage.out
1414

15-
coverage: ## Print code coverage
15+
coverage: ui ## Print code coverage
1616
go test -race -coverprofile coverage.txt -covermode=atomic ./... & go tool cover -html=coverage.out
1717

1818
vet: ## Run the go vet tool
@@ -28,7 +28,11 @@ proto: ## Generate the protobuf files
2828
@echo " > protobuf compilation finished"
2929

3030
clean: ## Clean the build artifacts
31-
rm -rf stencil dist/
31+
rm -rf stencil dist/ ui/build/
32+
33+
ui:
34+
@echo " > generating ui build"
35+
@cd ui && $(MAKE) dep && $(MAKE) dist
3236

3337
help: ## Display this help message
3438
@cat $(MAKEFILE_LIST) | grep -e "^[a-zA-Z_\-]*: *.*## *" | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ require (
1111
github.com/georgysavva/scany v0.2.9
1212
github.com/golang-migrate/migrate/v4 v4.15.2
1313
github.com/google/uuid v1.3.0
14+
github.com/gorilla/mux v1.8.0 // indirect
1415
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
1516
github.com/grpc-ecosystem/grpc-gateway/v2 v2.6.0
1617
github.com/hamba/avro v1.6.2

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,7 @@ github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/
664664
github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
665665
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
666666
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
667+
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
667668
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
668669
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
669670
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=

internal/server/server.go

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,20 @@ package server
33
import (
44
"context"
55
"fmt"
6+
"io/fs"
67
"log"
78
"net/http"
9+
"os"
10+
"path/filepath"
811
"strings"
912

1013
"github.com/dgraph-io/ristretto"
14+
"github.com/gorilla/mux"
1115
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
1216
"github.com/newrelic/go-agent/v3/integrations/nrgrpc"
1317
"github.com/odpf/stencil/config"
1418
"github.com/odpf/stencil/internal/store/postgres"
19+
"github.com/odpf/stencil/ui"
1520

1621
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
1722
grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap"
@@ -58,7 +63,7 @@ func Start(cfg config.Config) {
5863

5964
port := fmt.Sprintf(":%s", cfg.Port)
6065
nr := getNewRelic(&cfg)
61-
mux := runtime.NewServeMux()
66+
gatewayMux := runtime.NewServeMux()
6267

6368
// init grpc server
6469
opts := []grpc.ServerOption{
@@ -83,13 +88,16 @@ func Start(cfg config.Config) {
8388
if err != nil {
8489
log.Fatalln("Failed to dial server:", err)
8590
}
86-
api.RegisterSchemaHandlers(mux, nr)
91+
api.RegisterSchemaHandlers(gatewayMux, nr)
8792

88-
if err = stencilv1beta1.RegisterStencilServiceHandler(ctx, mux, conn); err != nil {
93+
if err = stencilv1beta1.RegisterStencilServiceHandler(ctx, gatewayMux, conn); err != nil {
8994
log.Fatalln("Failed to register stencil service handler:", err)
9095
}
9196

92-
runWithGracefulShutdown(&cfg, grpcHandlerFunc(s, mux), func() {
97+
rtr := mux.NewRouter()
98+
rtr.PathPrefix("/ui").Handler(http.StripPrefix("/ui", newSpaHandler()))
99+
100+
runWithGracefulShutdown(&cfg, grpcHandlerFunc(s, gatewayMux, rtr), func() {
93101
conn.Close()
94102
s.GracefulStop()
95103
db.Close()
@@ -98,13 +106,70 @@ func Start(cfg config.Config) {
98106

99107
// grpcHandlerFunc returns an http.Handler that delegates to grpcServer on incoming gRPC
100108
// connections or otherHandler otherwise.
101-
func grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Handler {
109+
func grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler, uiHandler http.Handler) http.Handler {
102110
return h2c.NewHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
103111
// This is a partial recreation of gRPC's internal checks https://github.com/grpc/grpc-go/pull/514/files#diff-95e9a25b738459a2d3030e1e6fa2a718R61
104112
if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") {
105113
grpcServer.ServeHTTP(w, r)
106114
} else {
107-
otherHandler.ServeHTTP(w, r)
115+
if strings.HasPrefix(r.URL.Path, "/ui") {
116+
uiHandler.ServeHTTP(w, r)
117+
} else {
118+
otherHandler.ServeHTTP(w, r)
119+
}
108120
}
109121
}), &http2.Server{})
110122
}
123+
124+
func newSpaHandler() spaHandler {
125+
fsys, err := fs.Sub(ui.Assets, "build")
126+
if err != nil {
127+
panic(err)
128+
}
129+
return spaHandler{
130+
staticPath: "./",
131+
indexFile: "index.html",
132+
buildFs: fsys,
133+
}
134+
}
135+
136+
type spaHandler struct {
137+
staticPath string
138+
indexFile string
139+
buildFs fs.FS
140+
}
141+
142+
// ServeHTTP inspects the URL path to locate a file within the static dir
143+
// on the SPA handler. If a file is found, it will be served. If not, the
144+
// file located at the index path on the SPA handler will be served. This
145+
// is suitable behavior for serving an SPA (single page application).
146+
func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
147+
log.Println("ui path getting called")
148+
// get the absolute path to prevent directory traversal
149+
path := r.URL.Path
150+
151+
path = filepath.Join(h.staticPath, path)
152+
153+
// check whether a file exists at the given path
154+
_, err := fs.Stat(h.buildFs, path)
155+
156+
log.Println(err, os.IsNotExist(err))
157+
if os.IsNotExist(err) {
158+
// file does not exist, serve index.html
159+
data, err := fs.ReadFile(h.buildFs, filepath.Join(h.staticPath, h.indexFile))
160+
if err != nil {
161+
http.Error(w, err.Error(), http.StatusNotFound)
162+
return
163+
}
164+
w.Write(data)
165+
return
166+
} else if err != nil {
167+
// if we got an error (that wasn't that the file doesn't exist) stating the
168+
// file, return a 500 internal server error and stop
169+
http.Error(w, err.Error(), http.StatusInternalServerError)
170+
return
171+
}
172+
173+
// otherwise, use http.FileServer to serve the static dir
174+
http.FileServer(http.FS(h.buildFs)).ServeHTTP(w, r)
175+
}

ui/.gitignore

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# testing
9+
/coverage
10+
11+
# production
12+
/build
13+
14+
# misc
15+
.DS_Store
16+
.env.local
17+
.env.development.local
18+
.env.test.local
19+
.env.production.local
20+
21+
npm-debug.log*
22+
yarn-debug.log*
23+
yarn-error.log*

ui/Makefile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.PHONY: dist test
2+
3+
dist:
4+
@yarn run build
5+
6+
test:
7+
@yarn run test
8+
9+
dep:
10+
@yarn install

ui/README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Getting Started with Create React App
2+
3+
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4+
5+
## Available Scripts
6+
7+
In the project directory, you can run:
8+
9+
### `npm start`
10+
11+
Runs the app in the development mode.\
12+
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
13+
14+
The page will reload if you make edits.\
15+
You will also see any lint errors in the console.
16+
17+
### `npm test`
18+
19+
Launches the test runner in the interactive watch mode.\
20+
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21+
22+
### `npm run build`
23+
24+
Builds the app for production to the `build` folder.\
25+
It correctly bundles React in production mode and optimizes the build for the best performance.
26+
27+
The build is minified and the filenames include the hashes.\
28+
Your app is ready to be deployed!
29+
30+
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31+
32+
### `npm run eject`
33+
34+
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
35+
36+
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37+
38+
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
39+
40+
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
41+
42+
## Learn More
43+
44+
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45+
46+
To learn React, check out the [React documentation](https://reactjs.org/).

ui/embed.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package ui
2+
3+
import "embed"
4+
5+
// Assets embed build folder
6+
//
7+
//go:embed build/*
8+
var Assets embed.FS

ui/package.json

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"name": "ui",
3+
"version": "0.1.0",
4+
"private": true,
5+
"homepage": "/ui",
6+
"dependencies": {
7+
"@testing-library/jest-dom": "^5.16.4",
8+
"@testing-library/react": "^13.1.1",
9+
"@testing-library/user-event": "^13.5.0",
10+
"@types/jest": "^27.4.1",
11+
"@types/node": "^16.11.31",
12+
"@types/react": "^18.0.8",
13+
"@types/react-dom": "^18.0.0",
14+
"react": "^18.1.0",
15+
"react-dom": "^18.1.0",
16+
"react-scripts": "5.0.1",
17+
"styled-components": "^5.2.1",
18+
"typescript": "^4.6.3",
19+
"web-vitals": "^2.1.4"
20+
},
21+
"scripts": {
22+
"start": "react-scripts start",
23+
"build": "react-scripts build",
24+
"test": "react-scripts test",
25+
"eject": "react-scripts eject"
26+
},
27+
"eslintConfig": {
28+
"extends": [
29+
"react-app",
30+
"react-app/jest"
31+
]
32+
},
33+
"browserslist": {
34+
"production": [
35+
">0.2%",
36+
"not dead",
37+
"not op_mini all"
38+
],
39+
"development": [
40+
"last 1 chrome version",
41+
"last 1 firefox version",
42+
"last 1 safari version"
43+
]
44+
}
45+
}

0 commit comments

Comments
 (0)