mirror of
https://github.com/simon987/task_tracker.git
synced 2025-04-19 18:16:45 +00:00
added optional task unique field
This commit is contained in:
parent
f250a2180c
commit
64152bfc08
@ -72,7 +72,8 @@ func (api *WebAPI) ReceiveGitWebHook(r *Request) {
|
|||||||
version := getVersion(payload)
|
version := getVersion(payload)
|
||||||
|
|
||||||
project.Version = version
|
project.Version = version
|
||||||
api.Database.UpdateProject(project)
|
err := api.Database.UpdateProject(project)
|
||||||
|
handleErr(err, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func signatureValid(r *Request) (matches bool) {
|
func signatureValid(r *Request) (matches bool) {
|
||||||
|
16
api/main.go
16
api/main.go
@ -1,6 +1,7 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/buaazp/fasthttprouter"
|
"github.com/buaazp/fasthttprouter"
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
@ -48,6 +49,7 @@ func New() *WebAPI {
|
|||||||
api.router.POST("/log/error", LogRequestMiddleware(LogError))
|
api.router.POST("/log/error", LogRequestMiddleware(LogError))
|
||||||
|
|
||||||
api.router.POST("/worker/create", LogRequestMiddleware(api.WorkerCreate))
|
api.router.POST("/worker/create", LogRequestMiddleware(api.WorkerCreate))
|
||||||
|
api.router.POST("/worker/update", LogRequestMiddleware(api.WorkerUpdate))
|
||||||
api.router.GET("/worker/get/:id", LogRequestMiddleware(api.WorkerGet))
|
api.router.GET("/worker/get/:id", LogRequestMiddleware(api.WorkerGet))
|
||||||
|
|
||||||
api.router.POST("/access/grant", LogRequestMiddleware(api.WorkerGrantAccess))
|
api.router.POST("/access/grant", LogRequestMiddleware(api.WorkerGrantAccess))
|
||||||
@ -55,6 +57,7 @@ func New() *WebAPI {
|
|||||||
|
|
||||||
api.router.POST("/project/create", LogRequestMiddleware(api.ProjectCreate))
|
api.router.POST("/project/create", LogRequestMiddleware(api.ProjectCreate))
|
||||||
api.router.GET("/project/get/:id", LogRequestMiddleware(api.ProjectGet))
|
api.router.GET("/project/get/:id", LogRequestMiddleware(api.ProjectGet))
|
||||||
|
api.router.POST("/project/update/:id", LogRequestMiddleware(api.ProjectUpdate))
|
||||||
api.router.GET("/project/stats/:id", LogRequestMiddleware(api.ProjectGetStats))
|
api.router.GET("/project/stats/:id", LogRequestMiddleware(api.ProjectGetStats))
|
||||||
api.router.GET("/project/stats", LogRequestMiddleware(api.ProjectGetAllStats))
|
api.router.GET("/project/stats", LogRequestMiddleware(api.ProjectGetAllStats))
|
||||||
|
|
||||||
@ -67,6 +70,19 @@ func New() *WebAPI {
|
|||||||
|
|
||||||
api.router.POST("/logs", LogRequestMiddleware(api.GetLog))
|
api.router.POST("/logs", LogRequestMiddleware(api.GetLog))
|
||||||
|
|
||||||
|
api.router.NotFound = func(ctx *fasthttp.RequestCtx) {
|
||||||
|
|
||||||
|
if ctx.Request.Header.IsOptions() {
|
||||||
|
ctx.Response.Header.Add("Access-Control-Allow-Headers", "Content-Type")
|
||||||
|
ctx.Response.Header.Add("Access-Control-Allow-Methods", "GET, POST, OPTION")
|
||||||
|
ctx.Response.Header.Add("Access-Control-Allow-Origin", "*")
|
||||||
|
} else {
|
||||||
|
ctx.SetStatusCode(404)
|
||||||
|
_, _ = fmt.Fprintf(ctx, "Not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return api
|
return api
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,20 @@ type CreateProjectRequest struct {
|
|||||||
Public bool `json:"public"`
|
Public bool `json:"public"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpdateProjectRequest struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
CloneUrl string `json:"clone_url"`
|
||||||
|
GitRepo string `json:"git_repo"`
|
||||||
|
Priority int64 `json:"priority"`
|
||||||
|
Motd string `json:"motd"`
|
||||||
|
Public bool `json:"public"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateProjectResponse struct {
|
||||||
|
Ok bool `json:"ok"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type CreateProjectResponse struct {
|
type CreateProjectResponse struct {
|
||||||
Ok bool `json:"ok"`
|
Ok bool `json:"ok"`
|
||||||
Id int64 `json:"id,omitempty"`
|
Id int64 `json:"id,omitempty"`
|
||||||
@ -86,10 +100,66 @@ func (api *WebAPI) ProjectCreate(r *Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (api *WebAPI) ProjectUpdate(r *Request) {
|
||||||
|
|
||||||
|
id, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64)
|
||||||
|
handleErr(err, r) //todo handle invalid id
|
||||||
|
|
||||||
|
updateReq := &UpdateProjectRequest{}
|
||||||
|
if r.GetJson(updateReq) {
|
||||||
|
|
||||||
|
project := &storage.Project{
|
||||||
|
Id: id,
|
||||||
|
Name: updateReq.Name,
|
||||||
|
CloneUrl: updateReq.CloneUrl,
|
||||||
|
GitRepo: updateReq.GitRepo,
|
||||||
|
Priority: updateReq.Priority,
|
||||||
|
Motd: updateReq.Motd,
|
||||||
|
Public: updateReq.Public,
|
||||||
|
}
|
||||||
|
|
||||||
|
if isValidProject(project) {
|
||||||
|
err := api.Database.UpdateProject(project)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
r.Json(CreateProjectResponse{
|
||||||
|
Ok: false,
|
||||||
|
Message: err.Error(),
|
||||||
|
}, 500)
|
||||||
|
|
||||||
|
logrus.WithError(err).WithFields(logrus.Fields{
|
||||||
|
"project": project,
|
||||||
|
}).Warn("Error during project update")
|
||||||
|
} else {
|
||||||
|
r.OkJson(UpdateProjectResponse{
|
||||||
|
Ok: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"project": project,
|
||||||
|
}).Debug("Updated project")
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"project": project,
|
||||||
|
}).Warn("Invalid project")
|
||||||
|
|
||||||
|
r.Json(CreateProjectResponse{
|
||||||
|
Ok: false,
|
||||||
|
Message: "Invalid project",
|
||||||
|
}, 400)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func isValidProject(project *storage.Project) bool {
|
func isValidProject(project *storage.Project) bool {
|
||||||
if len(project.Name) <= 0 {
|
if len(project.Name) <= 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if project.Priority < 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -97,7 +167,7 @@ func isValidProject(project *storage.Project) bool {
|
|||||||
func (api *WebAPI) ProjectGet(r *Request) {
|
func (api *WebAPI) ProjectGet(r *Request) {
|
||||||
|
|
||||||
id, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64)
|
id, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64)
|
||||||
handleErr(err, r)
|
handleErr(err, r) //todo handle invalid id
|
||||||
|
|
||||||
project := api.Database.GetProject(id)
|
project := api.Database.GetProject(id)
|
||||||
|
|
||||||
|
73
api/task.go
73
api/task.go
@ -1,8 +1,13 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
"crypto/hmac"
|
||||||
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/dchest/siphash"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"src/task_tracker/storage"
|
"src/task_tracker/storage"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -14,12 +19,13 @@ type CreateTaskRequest struct {
|
|||||||
Recipe string `json:"recipe"`
|
Recipe string `json:"recipe"`
|
||||||
Priority int64 `json:"priority"`
|
Priority int64 `json:"priority"`
|
||||||
MaxAssignTime int64 `json:"max_assign_time"`
|
MaxAssignTime int64 `json:"max_assign_time"`
|
||||||
|
Hash64 int64 `json:"hash_u64"`
|
||||||
|
UniqueString string `json:"unique_string"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReleaseTaskRequest struct {
|
type ReleaseTaskRequest struct {
|
||||||
TaskId int64 `json:"task_id"`
|
TaskId int64 `json:"task_id"`
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
WorkerId *uuid.UUID `json:"worker_id"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReleaseTaskResponse struct {
|
type ReleaseTaskResponse struct {
|
||||||
@ -51,8 +57,14 @@ func (api *WebAPI) TaskCreate(r *Request) {
|
|||||||
MaxAssignTime: createReq.MaxAssignTime,
|
MaxAssignTime: createReq.MaxAssignTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
if isTaskValid(task) {
|
if createReq.IsValid() && isTaskValid(task) {
|
||||||
err := api.Database.SaveTask(task, createReq.Project)
|
|
||||||
|
if createReq.UniqueString != "" {
|
||||||
|
//TODO: Load key from config
|
||||||
|
createReq.Hash64 = int64(siphash.Hash(1, 2, []byte(createReq.UniqueString)))
|
||||||
|
}
|
||||||
|
|
||||||
|
err := api.Database.SaveTask(task, createReq.Project, createReq.Hash64)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.Json(CreateTaskResponse{
|
r.Json(CreateTaskResponse{
|
||||||
@ -76,6 +88,10 @@ func (api *WebAPI) TaskCreate(r *Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (req *CreateTaskRequest) IsValid() bool {
|
||||||
|
return req.Hash64 == 0 || req.UniqueString == ""
|
||||||
|
}
|
||||||
|
|
||||||
func isTaskValid(task *storage.Task) bool {
|
func isTaskValid(task *storage.Task) bool {
|
||||||
if task.MaxRetries < 0 {
|
if task.MaxRetries < 0 {
|
||||||
return false
|
return false
|
||||||
@ -89,7 +105,7 @@ func isTaskValid(task *storage.Task) bool {
|
|||||||
|
|
||||||
func (api *WebAPI) TaskGetFromProject(r *Request) {
|
func (api *WebAPI) TaskGetFromProject(r *Request) {
|
||||||
|
|
||||||
worker, err := api.workerFromQueryArgs(r)
|
worker, err := api.validateSignature(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.Json(GetTaskResponse{
|
r.Json(GetTaskResponse{
|
||||||
Ok: false,
|
Ok: false,
|
||||||
@ -121,7 +137,7 @@ func (api *WebAPI) TaskGetFromProject(r *Request) {
|
|||||||
|
|
||||||
func (api *WebAPI) TaskGet(r *Request) {
|
func (api *WebAPI) TaskGet(r *Request) {
|
||||||
|
|
||||||
worker, err := api.workerFromQueryArgs(r)
|
worker, err := api.validateSignature(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.Json(GetTaskResponse{
|
r.Json(GetTaskResponse{
|
||||||
Ok: false,
|
Ok: false,
|
||||||
@ -138,9 +154,11 @@ func (api *WebAPI) TaskGet(r *Request) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api WebAPI) workerFromQueryArgs(r *Request) (*storage.Worker, error) {
|
func (api WebAPI) validateSignature(r *Request) (*storage.Worker, error) {
|
||||||
|
|
||||||
|
widStr := string(r.Ctx.Request.Header.Peek("X-Worker-Id"))
|
||||||
|
signature := r.Ctx.Request.Header.Peek("X-Signature")
|
||||||
|
|
||||||
widStr := string(r.Ctx.QueryArgs().Peek("wid"))
|
|
||||||
wid, err := uuid.Parse(widStr)
|
wid, err := uuid.Parse(widStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithError(err).WithFields(logrus.Fields{
|
logrus.WithError(err).WithFields(logrus.Fields{
|
||||||
@ -155,20 +173,53 @@ func (api WebAPI) workerFromQueryArgs(r *Request) (*storage.Worker, error) {
|
|||||||
if worker == nil {
|
if worker == nil {
|
||||||
logrus.WithError(err).WithFields(logrus.Fields{
|
logrus.WithError(err).WithFields(logrus.Fields{
|
||||||
"wid": widStr,
|
"wid": widStr,
|
||||||
}).Warn("Can't parse wid")
|
}).Warn("Worker id does not match any valid worker")
|
||||||
|
|
||||||
return nil, errors.New("worker id does not match any valid worker")
|
return nil, errors.New("worker id does not match any valid worker")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var body []byte
|
||||||
|
if r.Ctx.Request.Header.IsGet() {
|
||||||
|
body = r.Ctx.Request.RequestURI()
|
||||||
|
} else {
|
||||||
|
body = r.Ctx.Request.Body()
|
||||||
|
}
|
||||||
|
|
||||||
|
mac := hmac.New(crypto.SHA256.New, worker.Secret)
|
||||||
|
mac.Write(body)
|
||||||
|
|
||||||
|
expectedMac := make([]byte, 64)
|
||||||
|
hex.Encode(expectedMac, mac.Sum(nil))
|
||||||
|
matches := bytes.Compare(expectedMac, signature) == 0
|
||||||
|
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"expected": string(expectedMac),
|
||||||
|
"signature": string(signature),
|
||||||
|
"matches": matches,
|
||||||
|
}).Trace("Validating Worker signature")
|
||||||
|
|
||||||
|
if !matches {
|
||||||
|
return nil, errors.New("invalid signature")
|
||||||
|
}
|
||||||
|
|
||||||
return worker, nil
|
return worker, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *WebAPI) TaskRelease(r *Request) {
|
func (api *WebAPI) TaskRelease(r *Request) {
|
||||||
|
|
||||||
req := ReleaseTaskRequest{}
|
worker, err := api.validateSignature(r)
|
||||||
if r.GetJson(req) {
|
if err != nil {
|
||||||
|
r.Json(GetTaskResponse{
|
||||||
|
Ok: false,
|
||||||
|
Message: err.Error(),
|
||||||
|
}, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
res := api.Database.ReleaseTask(req.TaskId, req.WorkerId, req.Success)
|
var req ReleaseTaskRequest
|
||||||
|
if r.GetJson(&req) {
|
||||||
|
|
||||||
|
res := api.Database.ReleaseTask(req.TaskId, &worker.Id, req.Success)
|
||||||
|
|
||||||
response := ReleaseTaskResponse{
|
response := ReleaseTaskResponse{
|
||||||
Ok: res,
|
Ok: res,
|
||||||
|
@ -3,17 +3,28 @@ package api
|
|||||||
import (
|
import (
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"math/rand"
|
||||||
"src/task_tracker/storage"
|
"src/task_tracker/storage"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CreateWorkerRequest struct {
|
type CreateWorkerRequest struct {
|
||||||
|
Alias string `json:"alias"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateWorkerRequest struct {
|
||||||
|
Alias string `json:"alias"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateWorkerResponse struct {
|
||||||
|
Ok bool `json:"ok"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateWorkerResponse struct {
|
type CreateWorkerResponse struct {
|
||||||
Ok bool `json:"ok"`
|
Ok bool `json:"ok"`
|
||||||
Message string `json:"message,omitempty"`
|
Message string `json:"message,omitempty"`
|
||||||
WorkerId uuid.UUID `json:"id,omitempty"`
|
Worker *storage.Worker `json:"worker,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetWorkerResponse struct {
|
type GetWorkerResponse struct {
|
||||||
@ -55,13 +66,13 @@ func (api *WebAPI) WorkerCreate(r *Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := api.workerCreate(workerReq, getIdentity(r))
|
worker, err := api.workerCreate(workerReq, getIdentity(r))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleErr(err, r)
|
handleErr(err, r)
|
||||||
} else {
|
} else {
|
||||||
r.OkJson(CreateWorkerResponse{
|
r.OkJson(CreateWorkerResponse{
|
||||||
Ok: true,
|
Ok: true,
|
||||||
WorkerId: id,
|
Worker: worker,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,6 +95,9 @@ func (api *WebAPI) WorkerGet(r *Request) {
|
|||||||
worker := api.Database.GetWorker(id)
|
worker := api.Database.GetWorker(id)
|
||||||
|
|
||||||
if worker != nil {
|
if worker != nil {
|
||||||
|
|
||||||
|
worker.Secret = nil
|
||||||
|
|
||||||
r.OkJson(GetWorkerResponse{
|
r.OkJson(GetWorkerResponse{
|
||||||
Ok: true,
|
Ok: true,
|
||||||
Worker: worker,
|
Worker: worker,
|
||||||
@ -136,22 +150,75 @@ func (api *WebAPI) WorkerRemoveAccess(r *Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *WebAPI) workerCreate(request *CreateWorkerRequest, identity *storage.Identity) (uuid.UUID, error) {
|
func (api *WebAPI) WorkerUpdate(r *Request) {
|
||||||
|
|
||||||
|
worker, err := api.validateSignature(r)
|
||||||
|
if err != nil {
|
||||||
|
r.Json(GetTaskResponse{
|
||||||
|
Ok: false,
|
||||||
|
Message: err.Error(),
|
||||||
|
}, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &UpdateWorkerRequest{}
|
||||||
|
if r.GetJson(req) {
|
||||||
|
|
||||||
|
worker.Alias = req.Alias
|
||||||
|
|
||||||
|
ok := api.Database.UpdateWorker(worker)
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
r.OkJson(UpdateWorkerResponse{
|
||||||
|
Ok: true,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
r.OkJson(UpdateWorkerResponse{
|
||||||
|
Ok: false,
|
||||||
|
Message: "Could not update worker",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *WebAPI) workerCreate(request *CreateWorkerRequest, identity *storage.Identity) (*storage.Worker, error) {
|
||||||
|
|
||||||
|
if request.Alias == "" {
|
||||||
|
request.Alias = "default_alias"
|
||||||
|
}
|
||||||
|
|
||||||
worker := storage.Worker{
|
worker := storage.Worker{
|
||||||
Id: uuid.New(),
|
Id: uuid.New(),
|
||||||
Created: time.Now().Unix(),
|
Created: time.Now().Unix(),
|
||||||
Identity: identity,
|
Identity: identity,
|
||||||
|
Secret: makeSecret(),
|
||||||
|
Alias: request.Alias,
|
||||||
}
|
}
|
||||||
|
|
||||||
api.Database.SaveWorker(&worker)
|
api.Database.SaveWorker(&worker)
|
||||||
return worker.Id, nil
|
return &worker, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func canCreateWorker(r *Request, cwr *CreateWorkerRequest, identity *storage.Identity) bool {
|
func canCreateWorker(r *Request, cwr *CreateWorkerRequest, identity *storage.Identity) bool {
|
||||||
|
|
||||||
|
if cwr.Alias == "unassigned" {
|
||||||
|
//Reserved alias
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeSecret() []byte {
|
||||||
|
|
||||||
|
secret := make([]byte, 32)
|
||||||
|
for i := 0; i < 32; i++ {
|
||||||
|
secret[i] = byte(rand.Int31())
|
||||||
|
}
|
||||||
|
|
||||||
|
return secret
|
||||||
|
}
|
||||||
|
|
||||||
func getIdentity(r *Request) *storage.Identity {
|
func getIdentity(r *Request) *storage.Identity {
|
||||||
|
|
||||||
identity := storage.Identity{
|
identity := storage.Identity{
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math/rand"
|
||||||
"src/task_tracker/api"
|
"src/task_tracker/api"
|
||||||
"src/task_tracker/config"
|
"src/task_tracker/config"
|
||||||
"src/task_tracker/storage"
|
"src/task_tracker/storage"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func tmpDebugSetup() {
|
func tmpDebugSetup() {
|
||||||
@ -15,6 +17,7 @@ func tmpDebugSetup() {
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
|
rand.Seed(time.Now().UTC().UnixNano())
|
||||||
config.SetupConfig()
|
config.SetupConfig()
|
||||||
|
|
||||||
webApi := api.New()
|
webApi := api.New()
|
||||||
|
@ -26,9 +26,10 @@ CREATE TABLE worker_identity
|
|||||||
CREATE TABLE worker
|
CREATE TABLE worker
|
||||||
(
|
(
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
alias TEXT DEFAULT NULL,
|
alias TEXT,
|
||||||
created INTEGER,
|
created INTEGER,
|
||||||
identity INTEGER REFERENCES workerIdentity (id)
|
identity INTEGER REFERENCES worker_identity (id),
|
||||||
|
secret BYTEA
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE project
|
CREATE TABLE project
|
||||||
@ -61,7 +62,8 @@ CREATE TABLE task
|
|||||||
status Status DEFAULT 'new',
|
status Status DEFAULT 'new',
|
||||||
recipe TEXT,
|
recipe TEXT,
|
||||||
max_assign_time INTEGER DEFAULT 0,
|
max_assign_time INTEGER DEFAULT 0,
|
||||||
assign_time INTEGER DEFAULT 0
|
assign_time INTEGER DEFAULT 0,
|
||||||
|
hash64 BIGINT DEFAULT NULL UNIQUE
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE log_entry
|
CREATE TABLE log_entry
|
||||||
|
@ -3,7 +3,6 @@ package storage
|
|||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/google/uuid"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -19,7 +18,7 @@ type Project struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AssignedTasks struct {
|
type AssignedTasks struct {
|
||||||
Assignee uuid.UUID `json:"assignee"`
|
Assignee string `json:"assignee"`
|
||||||
TaskCount int64 `json:"task_count"`
|
TaskCount int64 `json:"task_count"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,14 +115,16 @@ func (database *Database) GetProjectWithRepoName(repoName string) *Project {
|
|||||||
return project
|
return project
|
||||||
}
|
}
|
||||||
|
|
||||||
func (database *Database) UpdateProject(project *Project) {
|
func (database *Database) UpdateProject(project *Project) error {
|
||||||
|
|
||||||
db := database.getDB()
|
db := database.getDB()
|
||||||
|
|
||||||
res, err := db.Exec(`UPDATE project
|
res, err := db.Exec(`UPDATE project
|
||||||
SET (priority, name, clone_url, git_repo, version, motd, public) = ($1,$2,$3,$4,$5,$6,$7) WHERE id=$8`,
|
SET (priority, name, clone_url, git_repo, version, motd, public) = ($1,$2,$3,$4,$5,$6,$7) WHERE id=$8`,
|
||||||
project.Priority, project.Name, project.CloneUrl, project.GitRepo, project.Version, project.Motd, project.Public, project.Id)
|
project.Priority, project.Name, project.CloneUrl, project.GitRepo, project.Version, project.Motd, project.Public, project.Id)
|
||||||
handleErr(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
rowsAffected, _ := res.RowsAffected()
|
rowsAffected, _ := res.RowsAffected()
|
||||||
|
|
||||||
@ -132,7 +133,7 @@ func (database *Database) UpdateProject(project *Project) {
|
|||||||
"rowsAffected": rowsAffected,
|
"rowsAffected": rowsAffected,
|
||||||
}).Trace("Database.updateProject UPDATE project")
|
}).Trace("Database.updateProject UPDATE project")
|
||||||
|
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (database *Database) GetProjectStats(id int64) *ProjectStats {
|
func (database *Database) GetProjectStats(id int64) *ProjectStats {
|
||||||
@ -154,18 +155,27 @@ func (database *Database) GetProjectStats(id int64) *ProjectStats {
|
|||||||
logrus.WithError(err).WithFields(logrus.Fields{
|
logrus.WithError(err).WithFields(logrus.Fields{
|
||||||
"id": id,
|
"id": id,
|
||||||
}).Trace("Get project stats: No task for this project")
|
}).Trace("Get project stats: No task for this project")
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//todo: only expose worker alias
|
rows, err := db.Query(`SELECT worker.alias, COUNT(*) as wc FROM TASK
|
||||||
rows, err := db.Query(`SELECT assignee, COUNT(*) FROM TASK
|
LEFT JOIN worker ON TASK.assignee = worker.id WHERE project=$1
|
||||||
LEFT JOIN worker ON TASK.assignee = worker.id WHERE project=$1 GROUP BY assignee`, id)
|
GROUP BY worker.id ORDER BY wc LIMIT 10`, id)
|
||||||
|
|
||||||
|
stats.Assignees = []*AssignedTasks{}
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
assignee := AssignedTasks{}
|
assignee := AssignedTasks{}
|
||||||
err = rows.Scan(&assignee.Assignee, &assignee.TaskCount)
|
var assigneeAlias sql.NullString
|
||||||
|
err = rows.Scan(&assigneeAlias, &assignee.TaskCount)
|
||||||
handleErr(err)
|
handleErr(err)
|
||||||
|
|
||||||
|
if assigneeAlias.Valid {
|
||||||
|
assignee.Assignee = assigneeAlias.String
|
||||||
|
} else {
|
||||||
|
assignee.Assignee = "unassigned"
|
||||||
|
}
|
||||||
|
|
||||||
stats.Assignees = append(stats.Assignees, &assignee)
|
stats.Assignees = append(stats.Assignees, &assignee)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -182,8 +192,8 @@ func (database Database) GetAllProjectsStats() *[]ProjectStats {
|
|||||||
SUM(CASE WHEN status='failed' THEN 1 ELSE 0 END) failedCount,
|
SUM(CASE WHEN status='failed' THEN 1 ELSE 0 END) failedCount,
|
||||||
SUM(CASE WHEN status='closed' THEN 1 ELSE 0 END) closedCount,
|
SUM(CASE WHEN status='closed' THEN 1 ELSE 0 END) closedCount,
|
||||||
p.*
|
p.*
|
||||||
FROM task INNER JOIN project p on task.project = p.id
|
FROM task RIGHT JOIN project p on task.project = p.id
|
||||||
GROUP BY p.id`)
|
GROUP BY p.id ORDER BY p.name`)
|
||||||
handleErr(err)
|
handleErr(err)
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
@ -191,7 +201,7 @@ func (database Database) GetAllProjectsStats() *[]ProjectStats {
|
|||||||
stats := ProjectStats{}
|
stats := ProjectStats{}
|
||||||
p := &Project{}
|
p := &Project{}
|
||||||
err := rows.Scan(&stats.NewTaskCount, &stats.FailedTaskCount, &stats.ClosedTaskCount,
|
err := rows.Scan(&stats.NewTaskCount, &stats.FailedTaskCount, &stats.ClosedTaskCount,
|
||||||
&p.Id, &p.Priority, &p.Motd, &p.Name, &p.CloneUrl, &p.GitRepo, &p.Version, &p.Public)
|
&p.Id, &p.Priority, &p.Name, &p.CloneUrl, &p.GitRepo, &p.Version, &p.Motd, &p.Public)
|
||||||
handleErr(err)
|
handleErr(err)
|
||||||
|
|
||||||
stats.Project = p
|
stats.Project = p
|
||||||
|
@ -2,6 +2,7 @@ package storage
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
@ -19,13 +20,14 @@ type Task struct {
|
|||||||
AssignTime int64 `json:"assign_time"`
|
AssignTime int64 `json:"assign_time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (database *Database) SaveTask(task *Task, project int64) error {
|
func (database *Database) SaveTask(task *Task, project int64, hash64 int64) error {
|
||||||
|
|
||||||
db := database.getDB()
|
db := database.getDB()
|
||||||
|
|
||||||
res, err := db.Exec(`
|
//TODO: For some reason it refuses to insert the 64-bit value unless I do that...
|
||||||
INSERT INTO task (project, max_retries, recipe, priority, max_assign_time)
|
res, err := db.Exec(fmt.Sprintf(`
|
||||||
VALUES ($1,$2,$3,$4,$5)`,
|
INSERT INTO task (project, max_retries, recipe, priority, max_assign_time, hash64)
|
||||||
|
VALUES ($1,$2,$3,$4,$5,NULLIF(%d, 0))`, hash64),
|
||||||
project, task.MaxRetries, task.Recipe, task.Priority, task.MaxAssignTime)
|
project, task.MaxRetries, task.Recipe, task.Priority, task.MaxAssignTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithError(err).WithFields(logrus.Fields{
|
logrus.WithError(err).WithFields(logrus.Fields{
|
||||||
@ -57,7 +59,7 @@ func (database *Database) GetTask(worker *Worker) *Task {
|
|||||||
SELECT task.id
|
SELECT task.id
|
||||||
FROM task
|
FROM task
|
||||||
INNER JOIN project p on task.project = p.id
|
INNER JOIN project p on task.project = p.id
|
||||||
WHERE assignee IS NULL
|
WHERE assignee IS NULL AND task.status='new'
|
||||||
AND (p.public OR EXISTS (
|
AND (p.public OR EXISTS (
|
||||||
SELECT 1 FROM worker_has_access_to_project a WHERE a.worker=$1 AND a.project=p.id
|
SELECT 1 FROM worker_has_access_to_project a WHERE a.worker=$1 AND a.project=p.id
|
||||||
))
|
))
|
||||||
@ -88,7 +90,8 @@ func (database *Database) GetTask(worker *Worker) *Task {
|
|||||||
func getTaskById(id int64, db *sql.DB) *Task {
|
func getTaskById(id int64, db *sql.DB) *Task {
|
||||||
|
|
||||||
row := db.QueryRow(`
|
row := db.QueryRow(`
|
||||||
SELECT * FROM task
|
SELECT task.id, task.priority, task.project, assignee, retries, max_retries,
|
||||||
|
status, recipe, max_assign_time, assign_time, project.* FROM task
|
||||||
INNER JOIN project ON task.project = project.id
|
INNER JOIN project ON task.project = project.id
|
||||||
WHERE task.id=$1`, id)
|
WHERE task.id=$1`, id)
|
||||||
task := scanTask(row)
|
task := scanTask(row)
|
||||||
@ -109,11 +112,11 @@ func (database Database) ReleaseTask(id int64, workerId *uuid.UUID, success bool
|
|||||||
var err error
|
var err error
|
||||||
if success {
|
if success {
|
||||||
res, err = db.Exec(`UPDATE task SET (status, assignee) = ('closed', NULL)
|
res, err = db.Exec(`UPDATE task SET (status, assignee) = ('closed', NULL)
|
||||||
WHERE id=$2 AND task.assignee=$2`, id, workerId)
|
WHERE id=$1 AND task.assignee=$2`, id, workerId)
|
||||||
} else {
|
} else {
|
||||||
res, err = db.Exec(`UPDATE task SET (status, assignee, retries) =
|
res, err = db.Exec(`UPDATE task SET (status, assignee, retries) =
|
||||||
(CASE WHEN retries+1 >= max_retries THEN 'failed' ELSE 'new' END, NULL, retries+1)
|
(CASE WHEN retries+1 >= max_retries THEN 'failed' ELSE 'new' END, NULL, retries+1)
|
||||||
WHERE id=$2 AND assignee=$2`, id, workerId)
|
WHERE id=$1 AND assignee=$2`, id, workerId)
|
||||||
}
|
}
|
||||||
handleErr(err)
|
handleErr(err)
|
||||||
|
|
||||||
@ -138,7 +141,7 @@ func (database *Database) GetTaskFromProject(worker *Worker, projectId int64) *T
|
|||||||
SELECT task.id
|
SELECT task.id
|
||||||
FROM task
|
FROM task
|
||||||
INNER JOIN project p on task.project = p.id
|
INNER JOIN project p on task.project = p.id
|
||||||
WHERE assignee IS NULL AND p.id=$2
|
WHERE assignee IS NULL AND p.id=$2 AND status='new'
|
||||||
AND (p.public OR EXISTS (
|
AND (p.public OR EXISTS (
|
||||||
SELECT 1 FROM worker_has_access_to_project a WHERE a.worker=$1 AND a.project=$2
|
SELECT 1 FROM worker_has_access_to_project a WHERE a.worker=$1 AND a.project=$2
|
||||||
))
|
))
|
||||||
|
@ -16,6 +16,8 @@ type Worker struct {
|
|||||||
Id uuid.UUID `json:"id"`
|
Id uuid.UUID `json:"id"`
|
||||||
Created int64 `json:"created"`
|
Created int64 `json:"created"`
|
||||||
Identity *Identity `json:"identity"`
|
Identity *Identity `json:"identity"`
|
||||||
|
Alias string `json:"alias,omitempty"`
|
||||||
|
Secret []byte `json:"secret"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (database *Database) SaveWorker(worker *Worker) {
|
func (database *Database) SaveWorker(worker *Worker) {
|
||||||
@ -24,8 +26,8 @@ func (database *Database) SaveWorker(worker *Worker) {
|
|||||||
|
|
||||||
identityId := getOrCreateIdentity(worker.Identity, db)
|
identityId := getOrCreateIdentity(worker.Identity, db)
|
||||||
|
|
||||||
res, err := db.Exec("INSERT INTO worker (id, created, identity) VALUES ($1,$2,$3)",
|
res, err := db.Exec("INSERT INTO worker (id, created, identity, secret, alias) VALUES ($1,$2,$3,$4,$5)",
|
||||||
worker.Id, worker.Created, identityId)
|
worker.Id, worker.Created, identityId, worker.Secret, worker.Alias)
|
||||||
handleErr(err)
|
handleErr(err)
|
||||||
|
|
||||||
var rowsAffected, _ = res.RowsAffected()
|
var rowsAffected, _ = res.RowsAffected()
|
||||||
@ -41,8 +43,8 @@ func (database *Database) GetWorker(id uuid.UUID) *Worker {
|
|||||||
worker := &Worker{}
|
worker := &Worker{}
|
||||||
var identityId int64
|
var identityId int64
|
||||||
|
|
||||||
row := db.QueryRow("SELECT id, created, identity FROM worker WHERE id=$1", id)
|
row := db.QueryRow("SELECT id, created, identity, secret, alias FROM worker WHERE id=$1", id)
|
||||||
err := row.Scan(&worker.Id, &worker.Created, &identityId)
|
err := row.Scan(&worker.Id, &worker.Created, &identityId, &worker.Secret, &worker.Alias)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithFields(logrus.Fields{
|
logrus.WithFields(logrus.Fields{
|
||||||
"id": id,
|
"id": id,
|
||||||
@ -64,7 +66,7 @@ func getIdentity(id int64, db *sql.DB) (*Identity, error) {
|
|||||||
|
|
||||||
identity := &Identity{}
|
identity := &Identity{}
|
||||||
|
|
||||||
row := db.QueryRow("SELECT remote_addr, user_agent FROM workeridentity WHERE id=$1", id)
|
row := db.QueryRow("SELECT remote_addr, user_agent FROM worker_identity WHERE id=$1", id)
|
||||||
err := row.Scan(&identity.RemoteAddr, &identity.UserAgent)
|
err := row.Scan(&identity.RemoteAddr, &identity.UserAgent)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -80,7 +82,7 @@ func getIdentity(id int64, db *sql.DB) (*Identity, error) {
|
|||||||
|
|
||||||
func getOrCreateIdentity(identity *Identity, db *sql.DB) int64 {
|
func getOrCreateIdentity(identity *Identity, db *sql.DB) int64 {
|
||||||
|
|
||||||
res, err := db.Exec("INSERT INTO workeridentity (remote_addr, user_agent) VALUES ($1,$2) ON CONFLICT DO NOTHING",
|
res, err := db.Exec("INSERT INTO worker_identity (remote_addr, user_agent) VALUES ($1,$2) ON CONFLICT DO NOTHING",
|
||||||
identity.RemoteAddr, identity.UserAgent)
|
identity.RemoteAddr, identity.UserAgent)
|
||||||
handleErr(err)
|
handleErr(err)
|
||||||
|
|
||||||
@ -89,7 +91,7 @@ func getOrCreateIdentity(identity *Identity, db *sql.DB) int64 {
|
|||||||
"rowsAffected": rowsAffected,
|
"rowsAffected": rowsAffected,
|
||||||
}).Trace("Database.saveWorker INSERT workerIdentity")
|
}).Trace("Database.saveWorker INSERT workerIdentity")
|
||||||
|
|
||||||
row := db.QueryRow("SELECT (id) FROM workeridentity WHERE remote_addr=$1", identity.RemoteAddr)
|
row := db.QueryRow("SELECT (id) FROM worker_identity WHERE remote_addr=$1", identity.RemoteAddr)
|
||||||
|
|
||||||
var rowId int64
|
var rowId int64
|
||||||
err = row.Scan(&rowId)
|
err = row.Scan(&rowId)
|
||||||
@ -127,7 +129,7 @@ func (database *Database) GrantAccess(workerId *uuid.UUID, projectId int64) bool
|
|||||||
return rowsAffected == 1
|
return rowsAffected == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (database Database) RemoveAccess(workerId *uuid.UUID, projectId int64) bool {
|
func (database *Database) RemoveAccess(workerId *uuid.UUID, projectId int64) bool {
|
||||||
|
|
||||||
db := database.getDB()
|
db := database.getDB()
|
||||||
res, err := db.Exec(`DELETE FROM worker_has_access_to_project WHERE worker=$1 AND project=$2`,
|
res, err := db.Exec(`DELETE FROM worker_has_access_to_project WHERE worker=$1 AND project=$2`,
|
||||||
@ -144,3 +146,20 @@ func (database Database) RemoveAccess(workerId *uuid.UUID, projectId int64) bool
|
|||||||
|
|
||||||
return rowsAffected == 1
|
return rowsAffected == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (database *Database) UpdateWorker(worker *Worker) bool {
|
||||||
|
|
||||||
|
db := database.getDB()
|
||||||
|
res, err := db.Exec(`UPDATE worker SET alias=$1 WHERE id=$2`,
|
||||||
|
worker.Alias, worker.Id)
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
rowsAffected, _ := res.RowsAffected()
|
||||||
|
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"rowsAffected": rowsAffected,
|
||||||
|
"worker": worker,
|
||||||
|
}).Trace("Database.UpdateWorker UPDATE worker")
|
||||||
|
|
||||||
|
return rowsAffected == 1
|
||||||
|
}
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
|
|
||||||
func TestWebHookNoSignature(t *testing.T) {
|
func TestWebHookNoSignature(t *testing.T) {
|
||||||
|
|
||||||
r := Post("/git/receivehook", api.GitPayload{})
|
r := Post("/git/receivehook", api.GitPayload{}, nil)
|
||||||
|
|
||||||
if r.StatusCode != 403 {
|
if r.StatusCode != 403 {
|
||||||
t.Error()
|
t.Error()
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
func TestIndex(t *testing.T) {
|
func TestIndex(t *testing.T) {
|
||||||
|
|
||||||
r := Get("/")
|
r := Get("/", nil)
|
||||||
|
|
||||||
body, _ := ioutil.ReadAll(r.Body)
|
body, _ := ioutil.ReadAll(r.Body)
|
||||||
var info api.Info
|
var info api.Info
|
||||||
|
@ -16,7 +16,7 @@ func TestTraceValid(t *testing.T) {
|
|||||||
Scope: "test",
|
Scope: "test",
|
||||||
Message: "This is a test message",
|
Message: "This is a test message",
|
||||||
TimeStamp: time.Now().Unix(),
|
TimeStamp: time.Now().Unix(),
|
||||||
})
|
}, nil)
|
||||||
|
|
||||||
if r.StatusCode != 200 {
|
if r.StatusCode != 200 {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
@ -27,7 +27,7 @@ func TestTraceInvalidScope(t *testing.T) {
|
|||||||
r := Post("/log/trace", api.LogRequest{
|
r := Post("/log/trace", api.LogRequest{
|
||||||
Message: "this is a test message",
|
Message: "this is a test message",
|
||||||
TimeStamp: time.Now().Unix(),
|
TimeStamp: time.Now().Unix(),
|
||||||
})
|
}, nil)
|
||||||
|
|
||||||
if r.StatusCode != 500 {
|
if r.StatusCode != 500 {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
@ -37,7 +37,7 @@ func TestTraceInvalidScope(t *testing.T) {
|
|||||||
Scope: "",
|
Scope: "",
|
||||||
Message: "this is a test message",
|
Message: "this is a test message",
|
||||||
TimeStamp: time.Now().Unix(),
|
TimeStamp: time.Now().Unix(),
|
||||||
})
|
}, nil)
|
||||||
|
|
||||||
if r.StatusCode != 500 {
|
if r.StatusCode != 500 {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
@ -52,7 +52,7 @@ func TestTraceInvalidMessage(t *testing.T) {
|
|||||||
Scope: "test",
|
Scope: "test",
|
||||||
Message: "",
|
Message: "",
|
||||||
TimeStamp: time.Now().Unix(),
|
TimeStamp: time.Now().Unix(),
|
||||||
})
|
}, nil)
|
||||||
|
|
||||||
if r.StatusCode != 500 {
|
if r.StatusCode != 500 {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
@ -66,7 +66,7 @@ func TestTraceInvalidTime(t *testing.T) {
|
|||||||
r := Post("/log/trace", api.LogRequest{
|
r := Post("/log/trace", api.LogRequest{
|
||||||
Scope: "test",
|
Scope: "test",
|
||||||
Message: "test",
|
Message: "test",
|
||||||
})
|
}, nil)
|
||||||
if r.StatusCode != 500 {
|
if r.StatusCode != 500 {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
@ -81,7 +81,7 @@ func TestWarnValid(t *testing.T) {
|
|||||||
Scope: "test",
|
Scope: "test",
|
||||||
Message: "test",
|
Message: "test",
|
||||||
TimeStamp: time.Now().Unix(),
|
TimeStamp: time.Now().Unix(),
|
||||||
})
|
}, nil)
|
||||||
if r.StatusCode != 200 {
|
if r.StatusCode != 200 {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
@ -93,7 +93,7 @@ func TestInfoValid(t *testing.T) {
|
|||||||
Scope: "test",
|
Scope: "test",
|
||||||
Message: "test",
|
Message: "test",
|
||||||
TimeStamp: time.Now().Unix(),
|
TimeStamp: time.Now().Unix(),
|
||||||
})
|
}, nil)
|
||||||
if r.StatusCode != 200 {
|
if r.StatusCode != 200 {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
@ -105,7 +105,7 @@ func TestErrorValid(t *testing.T) {
|
|||||||
Scope: "test",
|
Scope: "test",
|
||||||
Message: "test",
|
Message: "test",
|
||||||
TimeStamp: time.Now().Unix(),
|
TimeStamp: time.Now().Unix(),
|
||||||
})
|
}, nil)
|
||||||
if r.StatusCode != 200 {
|
if r.StatusCode != 200 {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
@ -171,7 +171,7 @@ func getLogs(since int64, level logrus.Level) *api.GetLogResponse {
|
|||||||
r := Post(fmt.Sprintf("/logs"), api.GetLogRequest{
|
r := Post(fmt.Sprintf("/logs"), api.GetLogRequest{
|
||||||
Since: since,
|
Since: since,
|
||||||
Level: level,
|
Level: level,
|
||||||
})
|
}, nil)
|
||||||
|
|
||||||
resp := &api.GetLogResponse{}
|
resp := &api.GetLogResponse{}
|
||||||
data, _ := ioutil.ReadAll(r.Body)
|
data, _ := ioutil.ReadAll(r.Body)
|
||||||
|
@ -3,7 +3,6 @@ package test
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/google/uuid"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"src/task_tracker/api"
|
"src/task_tracker/api"
|
||||||
@ -132,28 +131,30 @@ func TestGetProjectStats(t *testing.T) {
|
|||||||
CloneUrl: "http://github.com/drone/test",
|
CloneUrl: "http://github.com/drone/test",
|
||||||
GitRepo: "drone/test",
|
GitRepo: "drone/test",
|
||||||
Priority: 3,
|
Priority: 3,
|
||||||
|
Public: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
pid := r.Id
|
pid := r.Id
|
||||||
|
worker := genWid()
|
||||||
|
|
||||||
createTask(api.CreateTaskRequest{
|
createTask(api.CreateTaskRequest{
|
||||||
Priority: 1,
|
Priority: 1,
|
||||||
Project: pid,
|
Project: pid,
|
||||||
MaxRetries: 0,
|
MaxRetries: 0,
|
||||||
Recipe: "{}",
|
Recipe: "{}",
|
||||||
})
|
}, worker)
|
||||||
createTask(api.CreateTaskRequest{
|
createTask(api.CreateTaskRequest{
|
||||||
Priority: 2,
|
Priority: 2,
|
||||||
Project: pid,
|
Project: pid,
|
||||||
MaxRetries: 0,
|
MaxRetries: 0,
|
||||||
Recipe: "{}",
|
Recipe: "{}",
|
||||||
})
|
}, worker)
|
||||||
createTask(api.CreateTaskRequest{
|
createTask(api.CreateTaskRequest{
|
||||||
Priority: 3,
|
Priority: 3,
|
||||||
Project: pid,
|
Project: pid,
|
||||||
MaxRetries: 0,
|
MaxRetries: 0,
|
||||||
Recipe: "{}",
|
Recipe: "{}",
|
||||||
})
|
}, worker)
|
||||||
|
|
||||||
stats := getProjectStats(pid)
|
stats := getProjectStats(pid)
|
||||||
|
|
||||||
@ -169,7 +170,7 @@ func TestGetProjectStats(t *testing.T) {
|
|||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
if stats.Stats.Assignees[0].Assignee != uuid.Nil {
|
if stats.Stats.Assignees[0].Assignee != "unassigned" {
|
||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
if stats.Stats.Assignees[0].TaskCount != 3 {
|
if stats.Stats.Assignees[0].TaskCount != 3 {
|
||||||
@ -189,19 +190,132 @@ func TestGetProjectStatsNotFound(t *testing.T) {
|
|||||||
})
|
})
|
||||||
s := getProjectStats(r.Id)
|
s := getProjectStats(r.Id)
|
||||||
|
|
||||||
if s.Ok != false {
|
if s.Ok != true {
|
||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(s.Message) <= 0 {
|
if s.Stats == nil {
|
||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUpdateProjectValid(t *testing.T) {
|
||||||
|
|
||||||
|
pid := createProject(api.CreateProjectRequest{
|
||||||
|
Public: true,
|
||||||
|
Version: "versionA",
|
||||||
|
Motd: "MotdA",
|
||||||
|
Name: "NameA",
|
||||||
|
CloneUrl: "CloneUrlA",
|
||||||
|
GitRepo: "GitRepoA",
|
||||||
|
Priority: 1,
|
||||||
|
}).Id
|
||||||
|
|
||||||
|
updateResp := updateProject(api.UpdateProjectRequest{
|
||||||
|
Priority: 2,
|
||||||
|
GitRepo: "GitRepoB",
|
||||||
|
CloneUrl: "CloneUrlB",
|
||||||
|
Name: "NameB",
|
||||||
|
Motd: "MotdB",
|
||||||
|
Public: false,
|
||||||
|
}, pid)
|
||||||
|
|
||||||
|
if updateResp.Ok != true {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
proj, _ := getProject(pid)
|
||||||
|
|
||||||
|
if proj.Project.Public != false {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
if proj.Project.Motd != "MotdB" {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
if proj.Project.CloneUrl != "CloneUrlB" {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
if proj.Project.GitRepo != "GitRepoB" {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
if proj.Project.Priority != 2 {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateProjectInvalid(t *testing.T) {
|
||||||
|
|
||||||
|
pid := createProject(api.CreateProjectRequest{
|
||||||
|
Public: true,
|
||||||
|
Version: "lllllllllllll",
|
||||||
|
Motd: "2wwwwwwwwwwwwwww",
|
||||||
|
Name: "aaaaaaaaaaaaaaaaaaaaaa",
|
||||||
|
CloneUrl: "333333333333333",
|
||||||
|
GitRepo: "llllllllllllllllllls",
|
||||||
|
Priority: 1,
|
||||||
|
}).Id
|
||||||
|
|
||||||
|
updateResp := updateProject(api.UpdateProjectRequest{
|
||||||
|
Priority: -1,
|
||||||
|
GitRepo: "GitRepo------",
|
||||||
|
CloneUrl: "CloneUrlB000000",
|
||||||
|
Name: "NameB-0",
|
||||||
|
Motd: "MotdB000000",
|
||||||
|
Public: false,
|
||||||
|
}, pid)
|
||||||
|
|
||||||
|
if updateResp.Ok != false {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(updateResp.Message) <= 0 {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateProjectConstraintFail(t *testing.T) {
|
||||||
|
|
||||||
|
pid := createProject(api.CreateProjectRequest{
|
||||||
|
Public: true,
|
||||||
|
Version: "testUpdateProjectConstraintFail",
|
||||||
|
Motd: "testUpdateProjectConstraintFail",
|
||||||
|
Name: "testUpdateProjectConstraintFail",
|
||||||
|
CloneUrl: "testUpdateProjectConstraintFail",
|
||||||
|
GitRepo: "testUpdateProjectConstraintFail",
|
||||||
|
Priority: 1,
|
||||||
|
}).Id
|
||||||
|
|
||||||
|
createProject(api.CreateProjectRequest{
|
||||||
|
Public: true,
|
||||||
|
Version: "testUpdateProjectConstraintFail_d",
|
||||||
|
Motd: "testUpdateProjectConstraintFail_d",
|
||||||
|
Name: "testUpdateProjectConstraintFail_d",
|
||||||
|
CloneUrl: "testUpdateProjectConstraintFail_d",
|
||||||
|
GitRepo: "testUpdateProjectConstraintFail_d",
|
||||||
|
Priority: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
updateResp := updateProject(api.UpdateProjectRequest{
|
||||||
|
Priority: 1,
|
||||||
|
GitRepo: "testUpdateProjectConstraintFail_d",
|
||||||
|
CloneUrl: "testUpdateProjectConstraintFail_d",
|
||||||
|
Name: "testUpdateProjectConstraintFail_d",
|
||||||
|
Motd: "testUpdateProjectConstraintFail_d",
|
||||||
|
}, pid)
|
||||||
|
|
||||||
|
if updateResp.Ok != false {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(updateResp.Message) <= 0 {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func createProject(req api.CreateProjectRequest) *api.CreateProjectResponse {
|
func createProject(req api.CreateProjectRequest) *api.CreateProjectResponse {
|
||||||
|
|
||||||
r := Post("/project/create", req)
|
r := Post("/project/create", req, nil)
|
||||||
|
|
||||||
var resp api.CreateProjectResponse
|
var resp api.CreateProjectResponse
|
||||||
data, _ := ioutil.ReadAll(r.Body)
|
data, _ := ioutil.ReadAll(r.Body)
|
||||||
@ -213,7 +327,7 @@ func createProject(req api.CreateProjectRequest) *api.CreateProjectResponse {
|
|||||||
|
|
||||||
func getProject(id int64) (*api.GetProjectResponse, *http.Response) {
|
func getProject(id int64) (*api.GetProjectResponse, *http.Response) {
|
||||||
|
|
||||||
r := Get(fmt.Sprintf("/project/get/%d", id))
|
r := Get(fmt.Sprintf("/project/get/%d", id), nil)
|
||||||
|
|
||||||
var getResp api.GetProjectResponse
|
var getResp api.GetProjectResponse
|
||||||
data, _ := ioutil.ReadAll(r.Body)
|
data, _ := ioutil.ReadAll(r.Body)
|
||||||
@ -225,7 +339,7 @@ func getProject(id int64) (*api.GetProjectResponse, *http.Response) {
|
|||||||
|
|
||||||
func getProjectStats(id int64) *api.GetProjectStatsResponse {
|
func getProjectStats(id int64) *api.GetProjectStatsResponse {
|
||||||
|
|
||||||
r := Get(fmt.Sprintf("/project/stats/%d", id))
|
r := Get(fmt.Sprintf("/project/stats/%d", id), nil)
|
||||||
|
|
||||||
var getResp api.GetProjectStatsResponse
|
var getResp api.GetProjectStatsResponse
|
||||||
data, _ := ioutil.ReadAll(r.Body)
|
data, _ := ioutil.ReadAll(r.Body)
|
||||||
@ -234,3 +348,15 @@ func getProjectStats(id int64) *api.GetProjectStatsResponse {
|
|||||||
|
|
||||||
return &getResp
|
return &getResp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateProject(request api.UpdateProjectRequest, pid int64) *api.UpdateProjectResponse {
|
||||||
|
|
||||||
|
r := Post(fmt.Sprintf("/project/update/%d", pid), request, nil)
|
||||||
|
|
||||||
|
var resp api.UpdateProjectResponse
|
||||||
|
data, _ := ioutil.ReadAll(r.Body)
|
||||||
|
err := json.Unmarshal(data, &resp)
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
return &resp
|
||||||
|
}
|
||||||
|
@ -15,6 +15,8 @@ func BenchmarkCreateTask(b *testing.B) {
|
|||||||
CloneUrl: "http://localhost",
|
CloneUrl: "http://localhost",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
worker := genWid()
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
createTask(api.CreateTaskRequest{
|
createTask(api.CreateTaskRequest{
|
||||||
@ -22,6 +24,6 @@ func BenchmarkCreateTask(b *testing.B) {
|
|||||||
Priority: 1,
|
Priority: 1,
|
||||||
Recipe: "{}",
|
Recipe: "{}",
|
||||||
MaxRetries: 1,
|
MaxRetries: 1,
|
||||||
})
|
}, worker)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"src/task_tracker/api"
|
"src/task_tracker/api"
|
||||||
|
"src/task_tracker/storage"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -18,11 +19,13 @@ func TestCreateTaskValid(t *testing.T) {
|
|||||||
CloneUrl: "http://github.com/test/test",
|
CloneUrl: "http://github.com/test/test",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
worker := genWid()
|
||||||
|
|
||||||
resp := createTask(api.CreateTaskRequest{
|
resp := createTask(api.CreateTaskRequest{
|
||||||
Project: 1,
|
Project: 1,
|
||||||
Recipe: "{}",
|
Recipe: "{}",
|
||||||
MaxRetries: 3,
|
MaxRetries: 3,
|
||||||
})
|
}, worker)
|
||||||
|
|
||||||
if resp.Ok != true {
|
if resp.Ok != true {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
@ -31,11 +34,13 @@ func TestCreateTaskValid(t *testing.T) {
|
|||||||
|
|
||||||
func TestCreateTaskInvalidProject(t *testing.T) {
|
func TestCreateTaskInvalidProject(t *testing.T) {
|
||||||
|
|
||||||
|
worker := genWid()
|
||||||
|
|
||||||
resp := createTask(api.CreateTaskRequest{
|
resp := createTask(api.CreateTaskRequest{
|
||||||
Project: 123456,
|
Project: 123456,
|
||||||
Recipe: "{}",
|
Recipe: "{}",
|
||||||
MaxRetries: 3,
|
MaxRetries: 3,
|
||||||
})
|
}, worker)
|
||||||
|
|
||||||
if resp.Ok != false {
|
if resp.Ok != false {
|
||||||
t.Error()
|
t.Error()
|
||||||
@ -62,7 +67,9 @@ func TestGetTaskInvalidWid(t *testing.T) {
|
|||||||
func TestGetTaskInvalidWorker(t *testing.T) {
|
func TestGetTaskInvalidWorker(t *testing.T) {
|
||||||
|
|
||||||
id := uuid.New()
|
id := uuid.New()
|
||||||
resp := getTask(&id)
|
resp := getTask(&storage.Worker{
|
||||||
|
Id: id,
|
||||||
|
})
|
||||||
|
|
||||||
if resp.Ok != false {
|
if resp.Ok != false {
|
||||||
t.Error()
|
t.Error()
|
||||||
@ -76,7 +83,9 @@ func TestGetTaskInvalidWorker(t *testing.T) {
|
|||||||
func TestGetTaskFromProjectInvalidWorker(t *testing.T) {
|
func TestGetTaskFromProjectInvalidWorker(t *testing.T) {
|
||||||
|
|
||||||
id := uuid.New()
|
id := uuid.New()
|
||||||
resp := getTaskFromProject(1, &id)
|
resp := getTaskFromProject(1, &storage.Worker{
|
||||||
|
Id: id,
|
||||||
|
})
|
||||||
|
|
||||||
if resp.Ok != false {
|
if resp.Ok != false {
|
||||||
t.Error()
|
t.Error()
|
||||||
@ -89,10 +98,12 @@ func TestGetTaskFromProjectInvalidWorker(t *testing.T) {
|
|||||||
|
|
||||||
func TestCreateTaskInvalidRetries(t *testing.T) {
|
func TestCreateTaskInvalidRetries(t *testing.T) {
|
||||||
|
|
||||||
|
worker := genWid()
|
||||||
|
|
||||||
resp := createTask(api.CreateTaskRequest{
|
resp := createTask(api.CreateTaskRequest{
|
||||||
Project: 1,
|
Project: 1,
|
||||||
MaxRetries: -1,
|
MaxRetries: -1,
|
||||||
})
|
}, worker)
|
||||||
|
|
||||||
if resp.Ok != false {
|
if resp.Ok != false {
|
||||||
t.Error()
|
t.Error()
|
||||||
@ -105,11 +116,13 @@ func TestCreateTaskInvalidRetries(t *testing.T) {
|
|||||||
|
|
||||||
func TestCreateTaskInvalidRecipe(t *testing.T) {
|
func TestCreateTaskInvalidRecipe(t *testing.T) {
|
||||||
|
|
||||||
|
worker := genWid()
|
||||||
|
|
||||||
resp := createTask(api.CreateTaskRequest{
|
resp := createTask(api.CreateTaskRequest{
|
||||||
Project: 1,
|
Project: 1,
|
||||||
Recipe: "",
|
Recipe: "",
|
||||||
MaxRetries: 3,
|
MaxRetries: 3,
|
||||||
})
|
}, worker)
|
||||||
|
|
||||||
if resp.Ok != false {
|
if resp.Ok != false {
|
||||||
t.Error()
|
t.Error()
|
||||||
@ -132,12 +145,14 @@ func TestCreateGetTask(t *testing.T) {
|
|||||||
Public: true,
|
Public: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
worker := genWid()
|
||||||
|
|
||||||
createTask(api.CreateTaskRequest{
|
createTask(api.CreateTaskRequest{
|
||||||
Project: resp.Id,
|
Project: resp.Id,
|
||||||
Recipe: "{\"url\":\"test\"}",
|
Recipe: "{\"url\":\"test\"}",
|
||||||
MaxRetries: 3,
|
MaxRetries: 3,
|
||||||
Priority: 9999,
|
Priority: 9999,
|
||||||
})
|
}, worker)
|
||||||
|
|
||||||
taskResp := getTaskFromProject(resp.Id, genWid())
|
taskResp := getTaskFromProject(resp.Id, genWid())
|
||||||
|
|
||||||
@ -194,26 +209,27 @@ func createTasks(prefix string) (int64, int64) {
|
|||||||
Priority: 999,
|
Priority: 999,
|
||||||
Public: true,
|
Public: true,
|
||||||
})
|
})
|
||||||
|
worker := genWid()
|
||||||
createTask(api.CreateTaskRequest{
|
createTask(api.CreateTaskRequest{
|
||||||
Project: lowP.Id,
|
Project: lowP.Id,
|
||||||
Recipe: "low1",
|
Recipe: "low1",
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
})
|
}, worker)
|
||||||
createTask(api.CreateTaskRequest{
|
createTask(api.CreateTaskRequest{
|
||||||
Project: lowP.Id,
|
Project: lowP.Id,
|
||||||
Recipe: "low2",
|
Recipe: "low2",
|
||||||
Priority: 1,
|
Priority: 1,
|
||||||
})
|
}, worker)
|
||||||
createTask(api.CreateTaskRequest{
|
createTask(api.CreateTaskRequest{
|
||||||
Project: highP.Id,
|
Project: highP.Id,
|
||||||
Recipe: "high1",
|
Recipe: "high1",
|
||||||
Priority: 100,
|
Priority: 100,
|
||||||
})
|
}, worker)
|
||||||
createTask(api.CreateTaskRequest{
|
createTask(api.CreateTaskRequest{
|
||||||
Project: highP.Id,
|
Project: highP.Id,
|
||||||
Recipe: "high2",
|
Recipe: "high2",
|
||||||
Priority: 101,
|
Priority: 101,
|
||||||
})
|
}, worker)
|
||||||
|
|
||||||
return lowP.Id, highP.Id
|
return lowP.Id, highP.Id
|
||||||
}
|
}
|
||||||
@ -274,7 +290,7 @@ func TestTaskPriority(t *testing.T) {
|
|||||||
|
|
||||||
func TestTaskNoAccess(t *testing.T) {
|
func TestTaskNoAccess(t *testing.T) {
|
||||||
|
|
||||||
wid := genWid()
|
worker := genWid()
|
||||||
|
|
||||||
pid := createProject(api.CreateProjectRequest{
|
pid := createProject(api.CreateProjectRequest{
|
||||||
Name: "This is a private proj",
|
Name: "This is a private proj",
|
||||||
@ -292,16 +308,16 @@ func TestTaskNoAccess(t *testing.T) {
|
|||||||
MaxAssignTime: 10,
|
MaxAssignTime: 10,
|
||||||
MaxRetries: 2,
|
MaxRetries: 2,
|
||||||
Recipe: "---",
|
Recipe: "---",
|
||||||
})
|
}, worker)
|
||||||
|
|
||||||
if createResp.Ok != true {
|
if createResp.Ok != true {
|
||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
grantAccess(wid, pid)
|
grantAccess(&worker.Id, pid)
|
||||||
removeAccess(wid, pid)
|
removeAccess(&worker.Id, pid)
|
||||||
|
|
||||||
tResp := getTaskFromProject(pid, wid)
|
tResp := getTaskFromProject(pid, worker)
|
||||||
|
|
||||||
if tResp.Ok != false {
|
if tResp.Ok != false {
|
||||||
t.Error()
|
t.Error()
|
||||||
@ -316,7 +332,7 @@ func TestTaskNoAccess(t *testing.T) {
|
|||||||
|
|
||||||
func TestTaskHasAccess(t *testing.T) {
|
func TestTaskHasAccess(t *testing.T) {
|
||||||
|
|
||||||
wid := genWid()
|
worker := genWid()
|
||||||
|
|
||||||
pid := createProject(api.CreateProjectRequest{
|
pid := createProject(api.CreateProjectRequest{
|
||||||
Name: "This is a private proj1",
|
Name: "This is a private proj1",
|
||||||
@ -334,15 +350,15 @@ func TestTaskHasAccess(t *testing.T) {
|
|||||||
MaxAssignTime: 10,
|
MaxAssignTime: 10,
|
||||||
MaxRetries: 2,
|
MaxRetries: 2,
|
||||||
Recipe: "---",
|
Recipe: "---",
|
||||||
})
|
}, worker)
|
||||||
|
|
||||||
if createResp.Ok != true {
|
if createResp.Ok != true {
|
||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
grantAccess(wid, pid)
|
grantAccess(&worker.Id, pid)
|
||||||
|
|
||||||
tResp := getTaskFromProject(pid, wid)
|
tResp := getTaskFromProject(pid, worker)
|
||||||
|
|
||||||
if tResp.Ok != true {
|
if tResp.Ok != true {
|
||||||
t.Error()
|
t.Error()
|
||||||
@ -354,16 +370,16 @@ func TestTaskHasAccess(t *testing.T) {
|
|||||||
|
|
||||||
func TestNoMoreTasks(t *testing.T) {
|
func TestNoMoreTasks(t *testing.T) {
|
||||||
|
|
||||||
wid := genWid()
|
worker := genWid()
|
||||||
|
|
||||||
for i := 0; i < 15; i++ {
|
for i := 0; i < 15; i++ {
|
||||||
getTask(wid)
|
getTask(worker)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReleaseTaskSuccess(t *testing.T) {
|
func TestReleaseTaskSuccess(t *testing.T) {
|
||||||
|
|
||||||
//wid := genWid()
|
worker := genWid()
|
||||||
|
|
||||||
pid := createProject(api.CreateProjectRequest{
|
pid := createProject(api.CreateProjectRequest{
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
@ -372,6 +388,7 @@ func TestReleaseTaskSuccess(t *testing.T) {
|
|||||||
Version: "11111111111111111",
|
Version: "11111111111111111",
|
||||||
Name: "testreleasetask",
|
Name: "testreleasetask",
|
||||||
Motd: "",
|
Motd: "",
|
||||||
|
Public: true,
|
||||||
}).Id
|
}).Id
|
||||||
|
|
||||||
createTask(api.CreateTaskRequest{
|
createTask(api.CreateTaskRequest{
|
||||||
@ -379,13 +396,119 @@ func TestReleaseTaskSuccess(t *testing.T) {
|
|||||||
Project: pid,
|
Project: pid,
|
||||||
Recipe: "{}",
|
Recipe: "{}",
|
||||||
MaxRetries: 3,
|
MaxRetries: 3,
|
||||||
})
|
}, worker)
|
||||||
|
|
||||||
|
task := getTaskFromProject(pid, worker).Task
|
||||||
|
|
||||||
|
releaseResp := releaseTask(api.ReleaseTaskRequest{
|
||||||
|
TaskId: task.Id,
|
||||||
|
Success: true,
|
||||||
|
}, worker)
|
||||||
|
|
||||||
|
if releaseResp.Ok != true {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
otherTask := getTaskFromProject(pid, worker)
|
||||||
|
|
||||||
|
//Shouldn't have more tasks available
|
||||||
|
if otherTask.Ok != false {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTask(request api.CreateTaskRequest) *api.CreateTaskResponse {
|
func TestCreateIntCollision(t *testing.T) {
|
||||||
|
|
||||||
r := Post("/task/create", request)
|
pid := createProject(api.CreateProjectRequest{
|
||||||
|
Priority: 1,
|
||||||
|
GitRepo: "testcreateintcollision",
|
||||||
|
CloneUrl: "testcreateintcollision",
|
||||||
|
Motd: "testcreateintcollision",
|
||||||
|
Public: true,
|
||||||
|
Name: "testcreateintcollision",
|
||||||
|
Version: "testcreateintcollision",
|
||||||
|
}).Id
|
||||||
|
|
||||||
|
w := genWid()
|
||||||
|
|
||||||
|
if createTask(api.CreateTaskRequest{
|
||||||
|
Project: pid,
|
||||||
|
Hash64: 123,
|
||||||
|
Priority: 1,
|
||||||
|
Recipe: "{}",
|
||||||
|
}, w).Ok != true {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := createTask(api.CreateTaskRequest{
|
||||||
|
Project: pid,
|
||||||
|
Hash64: 123,
|
||||||
|
Priority: 1,
|
||||||
|
Recipe: "{}",
|
||||||
|
}, w)
|
||||||
|
|
||||||
|
if resp.Ok != false {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(resp.Message)
|
||||||
|
if len(resp.Message) <= 0 {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateStringCollision(t *testing.T) {
|
||||||
|
|
||||||
|
pid := createProject(api.CreateProjectRequest{
|
||||||
|
Priority: 1,
|
||||||
|
GitRepo: "testcreatestringcollision",
|
||||||
|
CloneUrl: "testcreatestringcollision",
|
||||||
|
Motd: "testcreatestringcollision",
|
||||||
|
Public: true,
|
||||||
|
Name: "testcreatestringcollision",
|
||||||
|
Version: "testcreatestringcollision",
|
||||||
|
}).Id
|
||||||
|
|
||||||
|
w := genWid()
|
||||||
|
|
||||||
|
if createTask(api.CreateTaskRequest{
|
||||||
|
Project: pid,
|
||||||
|
UniqueString: "Hello, world",
|
||||||
|
Priority: 1,
|
||||||
|
Recipe: "{}",
|
||||||
|
}, w).Ok != true {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := createTask(api.CreateTaskRequest{
|
||||||
|
Project: pid,
|
||||||
|
UniqueString: "Hello, world",
|
||||||
|
Priority: 1,
|
||||||
|
Recipe: "{}",
|
||||||
|
}, w)
|
||||||
|
|
||||||
|
if !createTask(api.CreateTaskRequest{
|
||||||
|
Project: pid,
|
||||||
|
UniqueString: "This one should work",
|
||||||
|
Priority: 1,
|
||||||
|
Recipe: "{}",
|
||||||
|
}, w).Ok {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.Ok != false {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(resp.Message)
|
||||||
|
if len(resp.Message) <= 0 {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTask(request api.CreateTaskRequest, worker *storage.Worker) *api.CreateTaskResponse {
|
||||||
|
|
||||||
|
r := Post("/task/create", request, worker)
|
||||||
|
|
||||||
var resp api.CreateTaskResponse
|
var resp api.CreateTaskResponse
|
||||||
data, _ := ioutil.ReadAll(r.Body)
|
data, _ := ioutil.ReadAll(r.Body)
|
||||||
@ -395,9 +518,9 @@ func createTask(request api.CreateTaskRequest) *api.CreateTaskResponse {
|
|||||||
return &resp
|
return &resp
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTask(wid *uuid.UUID) *api.GetTaskResponse {
|
func getTask(worker *storage.Worker) *api.GetTaskResponse {
|
||||||
|
|
||||||
r := Get(fmt.Sprintf("/task/get?wid=%s", wid))
|
r := Get(fmt.Sprintf("/task/get"), worker)
|
||||||
|
|
||||||
var resp api.GetTaskResponse
|
var resp api.GetTaskResponse
|
||||||
data, _ := ioutil.ReadAll(r.Body)
|
data, _ := ioutil.ReadAll(r.Body)
|
||||||
@ -407,9 +530,9 @@ func getTask(wid *uuid.UUID) *api.GetTaskResponse {
|
|||||||
return &resp
|
return &resp
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTaskFromProject(project int64, wid *uuid.UUID) *api.GetTaskResponse {
|
func getTaskFromProject(project int64, worker *storage.Worker) *api.GetTaskResponse {
|
||||||
|
|
||||||
r := Get(fmt.Sprintf("/task/get/%d?wid=%s", project, wid))
|
r := Get(fmt.Sprintf("/task/get/%d", project), worker)
|
||||||
|
|
||||||
var resp api.GetTaskResponse
|
var resp api.GetTaskResponse
|
||||||
data, _ := ioutil.ReadAll(r.Body)
|
data, _ := ioutil.ReadAll(r.Body)
|
||||||
@ -418,3 +541,15 @@ func getTaskFromProject(project int64, wid *uuid.UUID) *api.GetTaskResponse {
|
|||||||
|
|
||||||
return &resp
|
return &resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func releaseTask(request api.ReleaseTaskRequest, worker *storage.Worker) *api.ReleaseTaskResponse {
|
||||||
|
|
||||||
|
r := Post("/task/release", request, worker)
|
||||||
|
|
||||||
|
var resp api.ReleaseTaskResponse
|
||||||
|
data, _ := ioutil.ReadAll(r.Body)
|
||||||
|
err := json.Unmarshal(data, &resp)
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
return &resp
|
||||||
|
}
|
||||||
|
@ -7,12 +7,15 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"src/task_tracker/api"
|
"src/task_tracker/api"
|
||||||
|
"src/task_tracker/storage"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCreateGetWorker(t *testing.T) {
|
func TestCreateGetWorker(t *testing.T) {
|
||||||
|
|
||||||
resp, r := createWorker(api.CreateWorkerRequest{})
|
resp, r := createWorker(api.CreateWorkerRequest{
|
||||||
|
Alias: "my_worker_alias",
|
||||||
|
})
|
||||||
|
|
||||||
if r.StatusCode != 200 {
|
if r.StatusCode != 200 {
|
||||||
t.Error()
|
t.Error()
|
||||||
@ -22,12 +25,12 @@ func TestCreateGetWorker(t *testing.T) {
|
|||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
getResp, r := getWorker(resp.WorkerId.String())
|
getResp, r := getWorker(resp.Worker.Id.String())
|
||||||
|
|
||||||
if r.StatusCode != 200 {
|
if r.StatusCode != 200 {
|
||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
if resp.WorkerId != getResp.Worker.Id {
|
if resp.Worker.Id != getResp.Worker.Id {
|
||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,6 +40,9 @@ func TestCreateGetWorker(t *testing.T) {
|
|||||||
if len(getResp.Worker.Identity.UserAgent) <= 0 {
|
if len(getResp.Worker.Identity.UserAgent) <= 0 {
|
||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
|
if resp.Worker.Alias != "my_worker_alias" {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetWorkerNotFound(t *testing.T) {
|
func TestGetWorkerNotFound(t *testing.T) {
|
||||||
@ -70,7 +76,7 @@ func TestGrantAccessFailedProjectConstraint(t *testing.T) {
|
|||||||
|
|
||||||
wid := genWid()
|
wid := genWid()
|
||||||
|
|
||||||
resp := grantAccess(wid, 38274593)
|
resp := grantAccess(&wid.Id, 38274593)
|
||||||
|
|
||||||
if resp.Ok != false {
|
if resp.Ok != false {
|
||||||
t.Error()
|
t.Error()
|
||||||
@ -82,9 +88,9 @@ func TestGrantAccessFailedProjectConstraint(t *testing.T) {
|
|||||||
|
|
||||||
func TestRemoveAccessFailedProjectConstraint(t *testing.T) {
|
func TestRemoveAccessFailedProjectConstraint(t *testing.T) {
|
||||||
|
|
||||||
wid := genWid()
|
worker := genWid()
|
||||||
|
|
||||||
resp := removeAccess(wid, 38274593)
|
resp := removeAccess(&worker.Id, 38274593)
|
||||||
|
|
||||||
if resp.Ok != false {
|
if resp.Ok != false {
|
||||||
t.Error()
|
t.Error()
|
||||||
@ -138,8 +144,43 @@ func TestGrantAccessFailedWorkerConstraint(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUpdateAliasValid(t *testing.T) {
|
||||||
|
|
||||||
|
wid := genWid()
|
||||||
|
|
||||||
|
updateResp := updateWorker(api.UpdateWorkerRequest{
|
||||||
|
Alias: "new alias",
|
||||||
|
}, wid)
|
||||||
|
|
||||||
|
if updateResp.Ok != true {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
w, _ := getWorker(wid.Id.String())
|
||||||
|
|
||||||
|
if w.Worker.Alias != "new alias" {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateWorkerAliasInvalid(t *testing.T) {
|
||||||
|
|
||||||
|
resp, _ := createWorker(api.CreateWorkerRequest{
|
||||||
|
Alias: "unassigned", //reserved alias
|
||||||
|
})
|
||||||
|
|
||||||
|
if resp.Ok != false {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(resp.Message) <= 0 {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func createWorker(req api.CreateWorkerRequest) (*api.CreateWorkerResponse, *http.Response) {
|
func createWorker(req api.CreateWorkerRequest) (*api.CreateWorkerResponse, *http.Response) {
|
||||||
r := Post("/worker/create", req)
|
r := Post("/worker/create", req, nil)
|
||||||
|
|
||||||
var resp *api.CreateWorkerResponse
|
var resp *api.CreateWorkerResponse
|
||||||
data, _ := ioutil.ReadAll(r.Body)
|
data, _ := ioutil.ReadAll(r.Body)
|
||||||
@ -151,7 +192,7 @@ func createWorker(req api.CreateWorkerRequest) (*api.CreateWorkerResponse, *http
|
|||||||
|
|
||||||
func getWorker(id string) (*api.GetWorkerResponse, *http.Response) {
|
func getWorker(id string) (*api.GetWorkerResponse, *http.Response) {
|
||||||
|
|
||||||
r := Get(fmt.Sprintf("/worker/get/%s", id))
|
r := Get(fmt.Sprintf("/worker/get/%s", id), nil)
|
||||||
|
|
||||||
var resp *api.GetWorkerResponse
|
var resp *api.GetWorkerResponse
|
||||||
data, _ := ioutil.ReadAll(r.Body)
|
data, _ := ioutil.ReadAll(r.Body)
|
||||||
@ -161,10 +202,10 @@ func getWorker(id string) (*api.GetWorkerResponse, *http.Response) {
|
|||||||
return resp, r
|
return resp, r
|
||||||
}
|
}
|
||||||
|
|
||||||
func genWid() *uuid.UUID {
|
func genWid() *storage.Worker {
|
||||||
|
|
||||||
resp, _ := createWorker(api.CreateWorkerRequest{})
|
resp, _ := createWorker(api.CreateWorkerRequest{})
|
||||||
return &resp.WorkerId
|
return resp.Worker
|
||||||
}
|
}
|
||||||
|
|
||||||
func grantAccess(wid *uuid.UUID, project int64) *api.WorkerAccessResponse {
|
func grantAccess(wid *uuid.UUID, project int64) *api.WorkerAccessResponse {
|
||||||
@ -172,7 +213,7 @@ func grantAccess(wid *uuid.UUID, project int64) *api.WorkerAccessResponse {
|
|||||||
r := Post("/access/grant", api.WorkerAccessRequest{
|
r := Post("/access/grant", api.WorkerAccessRequest{
|
||||||
WorkerId: wid,
|
WorkerId: wid,
|
||||||
ProjectId: project,
|
ProjectId: project,
|
||||||
})
|
}, nil)
|
||||||
|
|
||||||
var resp *api.WorkerAccessResponse
|
var resp *api.WorkerAccessResponse
|
||||||
data, _ := ioutil.ReadAll(r.Body)
|
data, _ := ioutil.ReadAll(r.Body)
|
||||||
@ -187,7 +228,7 @@ func removeAccess(wid *uuid.UUID, project int64) *api.WorkerAccessResponse {
|
|||||||
r := Post("/access/remove", api.WorkerAccessRequest{
|
r := Post("/access/remove", api.WorkerAccessRequest{
|
||||||
WorkerId: wid,
|
WorkerId: wid,
|
||||||
ProjectId: project,
|
ProjectId: project,
|
||||||
})
|
}, nil)
|
||||||
|
|
||||||
var resp *api.WorkerAccessResponse
|
var resp *api.WorkerAccessResponse
|
||||||
data, _ := ioutil.ReadAll(r.Body)
|
data, _ := ioutil.ReadAll(r.Body)
|
||||||
@ -196,3 +237,15 @@ func removeAccess(wid *uuid.UUID, project int64) *api.WorkerAccessResponse {
|
|||||||
|
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateWorker(request api.UpdateWorkerRequest, w *storage.Worker) *api.UpdateWorkerResponse {
|
||||||
|
|
||||||
|
r := Post("/worker/update", request, w)
|
||||||
|
|
||||||
|
var resp *api.UpdateWorkerResponse
|
||||||
|
data, _ := ioutil.ReadAll(r.Body)
|
||||||
|
err := json.Unmarshal(data, &resp)
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
@ -2,26 +2,61 @@ package test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
"crypto/hmac"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"src/task_tracker/config"
|
"src/task_tracker/config"
|
||||||
|
"src/task_tracker/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Post(path string, x interface{}) *http.Response {
|
func Post(path string, x interface{}, worker *storage.Worker) *http.Response {
|
||||||
|
|
||||||
body, err := json.Marshal(x)
|
body, err := json.Marshal(x)
|
||||||
buf := bytes.NewBuffer(body)
|
buf := bytes.NewBuffer(body)
|
||||||
|
|
||||||
r, err := http.Post("http://"+config.Cfg.ServerAddr+path, "application/json", buf)
|
req, err := http.NewRequest("POST", "http://"+config.Cfg.ServerAddr+path, buf)
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
if worker != nil {
|
||||||
|
mac := hmac.New(crypto.SHA256.New, worker.Secret)
|
||||||
|
mac.Write(body)
|
||||||
|
sig := hex.EncodeToString(mac.Sum(nil))
|
||||||
|
|
||||||
|
req.Header.Add("X-Worker-Id", worker.Id.String())
|
||||||
|
req.Header.Add("X-Signature", sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := http.Client{}
|
||||||
|
r, err := client.Do(req)
|
||||||
handleErr(err)
|
handleErr(err)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func Get(path string) *http.Response {
|
func Get(path string, worker *storage.Worker) *http.Response {
|
||||||
r, err := http.Get("http://" + config.Cfg.ServerAddr + path)
|
|
||||||
|
url := "http://" + config.Cfg.ServerAddr + path
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
if worker != nil {
|
||||||
|
|
||||||
|
fmt.Println(worker.Secret)
|
||||||
|
mac := hmac.New(crypto.SHA256.New, worker.Secret)
|
||||||
|
mac.Write([]byte(path))
|
||||||
|
sig := hex.EncodeToString(mac.Sum(nil))
|
||||||
|
|
||||||
|
req.Header.Add("X-Worker-Id", worker.Id.String())
|
||||||
|
req.Header.Add("X-Signature", sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := http.Client{}
|
||||||
|
r, err := client.Do(req)
|
||||||
handleErr(err)
|
handleErr(err)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
|
@ -26,9 +26,10 @@ CREATE TABLE worker_identity
|
|||||||
CREATE TABLE worker
|
CREATE TABLE worker
|
||||||
(
|
(
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
alias TEXT DEFAULT NULL,
|
alias TEXT,
|
||||||
created INTEGER,
|
created INTEGER,
|
||||||
identity INTEGER REFERENCES workerIdentity (id)
|
identity INTEGER REFERENCES worker_identity (id),
|
||||||
|
secret BYTEA
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE project
|
CREATE TABLE project
|
||||||
@ -61,7 +62,8 @@ CREATE TABLE task
|
|||||||
status Status DEFAULT 'new',
|
status Status DEFAULT 'new',
|
||||||
recipe TEXT,
|
recipe TEXT,
|
||||||
max_assign_time INTEGER DEFAULT 0,
|
max_assign_time INTEGER DEFAULT 0,
|
||||||
assign_time INTEGER DEFAULT 0
|
assign_time INTEGER DEFAULT 0,
|
||||||
|
hash64 BIGINT UNIQUE
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE log_entry
|
CREATE TABLE log_entry
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {HttpClient} from "@angular/common/http";
|
import {HttpClient} from "@angular/common/http";
|
||||||
|
import {Project} from "./models/project";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ApiService {
|
export class ApiService {
|
||||||
@ -26,4 +27,12 @@ export class ApiService {
|
|||||||
getProject(id: number) {
|
getProject(id: number) {
|
||||||
return this.http.get(this.url + "/project/get/" + id)
|
return this.http.get(this.url + "/project/get/" + id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createProject(project: Project) {
|
||||||
|
return this.http.post(this.url + "/project/create", project)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProject(project: Project) {
|
||||||
|
return this.http.post(this.url + "/project/update/" + project.id, project)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,11 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<li><a [routerLink]="''">Index</a></li>
|
<li><a [routerLink]="''">Index</a></li>
|
||||||
<li><a [routerLink]="'log'">Logs</a></li>
|
<li><a [routerLink]="'log'">Logs</a></li>
|
||||||
<li><a [routerLink]="'project'">Project</a></li>
|
|
||||||
<li><a [routerLink]="'projects'">list</a></li>
|
<li><a [routerLink]="'projects'">list</a></li>
|
||||||
<li><a [routerLink]="'new_project'">new project</a></li>
|
<li><a [routerLink]="'new_project'">new project</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<!--</mat-toolbar>-->
|
<!--</mat-toolbar>-->
|
||||||
|
|
||||||
|
<messenger-snack-bar></messenger-snack-bar>
|
||||||
|
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
|
@ -20,18 +20,21 @@ import {
|
|||||||
MatPaginatorModule,
|
MatPaginatorModule,
|
||||||
MatSliderModule,
|
MatSliderModule,
|
||||||
MatSlideToggleModule,
|
MatSlideToggleModule,
|
||||||
|
MatSnackBarModule,
|
||||||
MatSortModule,
|
MatSortModule,
|
||||||
MatTableModule,
|
MatTableModule,
|
||||||
MatToolbarModule,
|
MatToolbarModule,
|
||||||
MatTreeModule
|
MatTreeModule
|
||||||
} from "@angular/material";
|
} from "@angular/material";
|
||||||
import {ApiService} from "./api.service";
|
import {ApiService} from "./api.service";
|
||||||
|
import {MessengerService} from "./messenger.service";
|
||||||
import {HttpClientModule} from "@angular/common/http";
|
import {HttpClientModule} from "@angular/common/http";
|
||||||
import {ProjectDashboardComponent} from './project-dashboard/project-dashboard.component';
|
import {ProjectDashboardComponent} from './project-dashboard/project-dashboard.component';
|
||||||
import {ProjectListComponent} from './project-list/project-list.component';
|
import {ProjectListComponent} from './project-list/project-list.component';
|
||||||
import {CreateProjectComponent} from './create-project/create-project.component';
|
import {CreateProjectComponent} from './create-project/create-project.component';
|
||||||
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
|
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
|
||||||
import {UpdateProjectComponent} from './update-project/update-project.component';
|
import {UpdateProjectComponent} from './update-project/update-project.component';
|
||||||
|
import {SnackBarComponent} from "./messenger/snack-bar.component";
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -40,7 +43,8 @@ import {UpdateProjectComponent} from './update-project/update-project.component'
|
|||||||
ProjectDashboardComponent,
|
ProjectDashboardComponent,
|
||||||
ProjectListComponent,
|
ProjectListComponent,
|
||||||
CreateProjectComponent,
|
CreateProjectComponent,
|
||||||
UpdateProjectComponent
|
UpdateProjectComponent,
|
||||||
|
SnackBarComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
@ -65,12 +69,17 @@ import {UpdateProjectComponent} from './update-project/update-project.component'
|
|||||||
MatSliderModule,
|
MatSliderModule,
|
||||||
MatSlideToggleModule,
|
MatSlideToggleModule,
|
||||||
MatCheckboxModule,
|
MatCheckboxModule,
|
||||||
MatDividerModule
|
MatDividerModule,
|
||||||
|
MatSnackBarModule,
|
||||||
|
|
||||||
],
|
],
|
||||||
exports: [],
|
exports: [],
|
||||||
providers: [
|
providers: [
|
||||||
ApiService,
|
ApiService,
|
||||||
|
MessengerService,
|
||||||
|
],
|
||||||
|
entryComponents: [
|
||||||
|
SnackBarComponent,
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
})
|
})
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<mat-card-subtitle></mat-card-subtitle>
|
<mat-card-subtitle></mat-card-subtitle>
|
||||||
|
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<form>
|
<form (ngSubmit)="onSubmit()">
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline">
|
||||||
<mat-label>Project name</mat-label>
|
<mat-label>Project name</mat-label>
|
||||||
<input type="text" matInput [(ngModel)]="project.name" name="name" placeholder="Project name">
|
<input type="text" matInput [(ngModel)]="project.name" name="name" placeholder="Project name">
|
||||||
@ -29,11 +29,10 @@
|
|||||||
|
|
||||||
|
|
||||||
<input type="hidden" name="version" value="{{project.version}}">
|
<input type="hidden" name="version" value="{{project.version}}">
|
||||||
|
|
||||||
|
<input type="submit" value="Create">
|
||||||
</form>
|
</form>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
|
|
||||||
<mat-card-actions>
|
|
||||||
<button>Create</button>
|
|
||||||
</mat-card-actions>
|
|
||||||
|
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {Project} from "../models/project";
|
import {Project} from "../models/project";
|
||||||
|
import {ApiService} from "../api.service";
|
||||||
|
import {MessengerService} from "../messenger.service";
|
||||||
|
import {Router} from "@angular/router";
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-create-project',
|
selector: 'app-create-project',
|
||||||
@ -10,7 +14,9 @@ export class CreateProjectComponent implements OnInit {
|
|||||||
|
|
||||||
private project = new Project();
|
private project = new Project();
|
||||||
|
|
||||||
constructor() {
|
constructor(private apiService: ApiService,
|
||||||
|
private messengerService: MessengerService,
|
||||||
|
private router: Router) {
|
||||||
this.project.name = "test";
|
this.project.name = "test";
|
||||||
this.project.public = true;
|
this.project.public = true;
|
||||||
}
|
}
|
||||||
@ -18,4 +24,16 @@ export class CreateProjectComponent implements OnInit {
|
|||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSubmit() {
|
||||||
|
this.apiService.createProject(this.project).subscribe(
|
||||||
|
data => {
|
||||||
|
this.router.navigateByUrl("/project/" + data["id"]);
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.log(error.error.message);
|
||||||
|
this.messengerService.show(error.error.message);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
22
web/angular/src/app/messenger.service.ts
Normal file
22
web/angular/src/app/messenger.service.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
import {Subject} from "rxjs";
|
||||||
|
import {MessengerState} from "./messenger/messenger";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MessengerService {
|
||||||
|
|
||||||
|
public messengerSubject = new Subject<MessengerState>();
|
||||||
|
|
||||||
|
show(message: string) {
|
||||||
|
this.messengerSubject.next({
|
||||||
|
message: message,
|
||||||
|
hidden: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
hide() {
|
||||||
|
this.messengerSubject.next({
|
||||||
|
hidden: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
6
web/angular/src/app/messenger/messenger.ts
Normal file
6
web/angular/src/app/messenger/messenger.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export class MessengerState {
|
||||||
|
|
||||||
|
hidden: boolean;
|
||||||
|
message?: string;
|
||||||
|
|
||||||
|
}
|
32
web/angular/src/app/messenger/snack-bar.component.ts
Normal file
32
web/angular/src/app/messenger/snack-bar.component.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import {Component, OnInit} from '@angular/core';
|
||||||
|
import {MessengerService} from "../messenger.service";
|
||||||
|
import {MessengerState} from "./messenger";
|
||||||
|
import {Subscription} from "rxjs";
|
||||||
|
import {MatSnackBar, MatSnackBarConfig} from "@angular/material";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'messenger-snack-bar',
|
||||||
|
templateUrl: 'messenger-snack-bar.html',
|
||||||
|
styleUrls: ['messenger-snack-bar.css'],
|
||||||
|
})
|
||||||
|
export class SnackBarComponent implements OnInit {
|
||||||
|
|
||||||
|
private subscription: Subscription;
|
||||||
|
|
||||||
|
constructor(private messengerService: MessengerService, private snackBar: MatSnackBar) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.subscription = this.messengerService.messengerSubject
|
||||||
|
.subscribe((state: MessengerState) => {
|
||||||
|
if (state.hidden) {
|
||||||
|
this.snackBar.dismiss();
|
||||||
|
} else {
|
||||||
|
this.snackBar.open(state.message, "Close", <MatSnackBarConfig>{
|
||||||
|
duration: 10 * 1000,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
export class Project {
|
export class Project {
|
||||||
|
|
||||||
|
public id: number;
|
||||||
public priority: number;
|
public priority: number;
|
||||||
public motd: string;
|
public motd: string;
|
||||||
public name: string;
|
public name: string;
|
||||||
|
@ -37,6 +37,8 @@
|
|||||||
<pre>{{projectStats | json}}</pre>
|
<pre>{{projectStats | json}}</pre>
|
||||||
</mat-expansion-panel>
|
</mat-expansion-panel>
|
||||||
|
|
||||||
|
<a [routerLink]="'/project/' + projectStats.project.id + '/update'">Update</a>
|
||||||
|
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</div>
|
</div>
|
||||||
|
@ -56,14 +56,14 @@ export class ProjectDashboardComponent implements OnInit {
|
|||||||
setupStatusPieChart() {
|
setupStatusPieChart() {
|
||||||
let tooltip = d3.select("#stooltip");
|
let tooltip = d3.select("#stooltip");
|
||||||
|
|
||||||
this.statusSvg = d3.select('#status')
|
this.statusSvg = d3.select("#status")
|
||||||
.append('svg')
|
.append('svg')
|
||||||
.attr('width', this.pieWidth)
|
.attr('width', this.pieWidth)
|
||||||
.attr('height', this.pieHeight)
|
.attr('height', this.pieHeight)
|
||||||
.append("g")
|
.append("g")
|
||||||
.attr("transform", "translate(" + this.pieRadius + "," + this.pieRadius + ")");
|
.attr("transform", "translate(" + this.pieRadius + "," + this.pieRadius + ")");
|
||||||
|
|
||||||
this.statusPath = this.statusSvg.selectAll("path")
|
this.statusPath = this.statusSvg.selectAll()
|
||||||
.data(this.pieFun(this.statusData))
|
.data(this.pieFun(this.statusData))
|
||||||
.enter()
|
.enter()
|
||||||
.append('path')
|
.append('path')
|
||||||
@ -76,14 +76,14 @@ export class ProjectDashboardComponent implements OnInit {
|
|||||||
setupAssigneesPieChart() {
|
setupAssigneesPieChart() {
|
||||||
let tooltip = d3.select("#atooltip");
|
let tooltip = d3.select("#atooltip");
|
||||||
|
|
||||||
this.assigneesSvg = d3.select('#assignees')
|
this.assigneesSvg = d3.select("#assignees")
|
||||||
.append('svg')
|
.append('svg')
|
||||||
.attr('width', this.pieWidth)
|
.attr('width', this.pieWidth)
|
||||||
.attr('height', this.pieHeight)
|
.attr('height', this.pieHeight)
|
||||||
.append("g")
|
.append("g")
|
||||||
.attr("transform", "translate(" + this.pieRadius + "," + this.pieRadius + ")");
|
.attr("transform", "translate(" + this.pieRadius + "," + this.pieRadius + ")");
|
||||||
|
|
||||||
this.assigneesPath = this.assigneesSvg.selectAll("path")
|
this.assigneesPath = this.assigneesSvg.selectAll()
|
||||||
.data(this.pieFun(this.assigneesData))
|
.data(this.pieFun(this.assigneesData))
|
||||||
.enter()
|
.enter()
|
||||||
.append('path')
|
.append('path')
|
||||||
@ -229,10 +229,10 @@ export class ProjectDashboardComponent implements OnInit {
|
|||||||
{label: "Failed", count: this.projectStats["failed_task_count"]},
|
{label: "Failed", count: this.projectStats["failed_task_count"]},
|
||||||
{label: "Closed", count: this.projectStats["closed_task_count"]},
|
{label: "Closed", count: this.projectStats["closed_task_count"]},
|
||||||
];
|
];
|
||||||
this.assigneesData = _.map(this.projectStats["assignees"], (assignedTasks) => {
|
this.assigneesData = _.map(this.projectStats["assignees"], assignedTask => {
|
||||||
return {
|
return {
|
||||||
label: assignedTasks["assignee"] == "00000000-0000-0000-0000-000000000000" ? "unassigned" : assignedTasks["assignee"],
|
label: assignedTask["assignee"],
|
||||||
count: assignedTasks["task_count"]
|
count: assignedTask["task_count"],
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -256,6 +256,16 @@ export class ProjectDashboardComponent implements OnInit {
|
|||||||
];
|
];
|
||||||
this.assigneesData = [
|
this.assigneesData = [
|
||||||
{label: 'null', count: 0},
|
{label: 'null', count: 0},
|
||||||
|
{label: 'null', count: 0},
|
||||||
|
{label: 'null', count: 0},
|
||||||
|
{label: 'null', count: 0},
|
||||||
|
{label: 'null', count: 0},
|
||||||
|
{label: 'null', count: 0},
|
||||||
|
{label: 'null', count: 0},
|
||||||
|
{label: 'null', count: 0},
|
||||||
|
{label: 'null', count: 0},
|
||||||
|
{label: 'null', count: 0},
|
||||||
|
{label: 'null', count: 0},
|
||||||
];
|
];
|
||||||
|
|
||||||
this.setupStatusPieChart();
|
this.setupStatusPieChart();
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
<pre>{{stats.project | json}}</pre>
|
<pre>{{stats.project | json}}</pre>
|
||||||
<div style="display: flex;">
|
<div style="display: flex;">
|
||||||
<a [routerLink]="'/project/' + stats.project.id">Dashboard</a>
|
<a [routerLink]="'/project/' + stats.project.id">Dashboard</a>
|
||||||
|
<a [routerLink]="'/project/' + stats.project.id + '/update'">Update</a>
|
||||||
</div>
|
</div>
|
||||||
</mat-expansion-panel>
|
</mat-expansion-panel>
|
||||||
</mat-accordion>
|
</mat-accordion>
|
||||||
|
@ -1,29 +1,29 @@
|
|||||||
<mat-card>
|
<mat-card>
|
||||||
<mat-card-title>Update project</mat-card-title>
|
<mat-card-title>Update project</mat-card-title>
|
||||||
<mat-card-subtitle>Changes are saved in real time</mat-card-subtitle>
|
|
||||||
|
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<form>
|
<form (ngSubmit)="onSubmit()" *ngIf="project != undefined">
|
||||||
<mat-form-field>
|
<mat-form-field appearance="outline">
|
||||||
<input type="text" matInput [(ngModel)]="project.name" name="name" placeholder="Name">
|
<input type="text" matInput [(ngModel)]="project.name" name="name" placeholder="Name">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field>
|
<mat-form-field appearance="outline">
|
||||||
<textarea matInput placeholder="Message of the day"></textarea>
|
<textarea matInput [(ngModel)]="project.motd" placeholder="Message of the day" name="motd"></textarea>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field>
|
<mat-form-field appearance="outline">
|
||||||
<input type="text" matInput [(ngModel)]="project.clone_url" name="clone_url"
|
<input type="text" matInput [(ngModel)]="project.clone_url" name="clone_url"
|
||||||
placeholder="Git clone url">
|
placeholder="Git clone url">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field>
|
<mat-form-field appearance="outline">
|
||||||
<input type="text" matInput [(ngModel)]="project.git_repo" name="clone_url"
|
<input type="text" matInput [(ngModel)]="project.git_repo" name="git_repo"
|
||||||
placeholder='Full repository name (e.g. "simon987/task_tracker")'>
|
placeholder='Full repository name (e.g. "simon987/task_tracker")'>
|
||||||
<mat-hint align="start">Changes on the <strong>master</strong> branch will be tracked if webhooks are
|
<mat-hint align="start">Changes on the <strong>master</strong> branch will be tracked if webhooks are
|
||||||
enabled
|
enabled
|
||||||
</mat-hint>
|
</mat-hint>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<input type="hidden" name="version" value="{{project.version}}">
|
|
||||||
|
<input type="submit" value="Update">
|
||||||
</form>
|
</form>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {Project} from "../models/project";
|
import {Project} from "../models/project";
|
||||||
import {ApiService} from "../api.service";
|
import {ApiService} from "../api.service";
|
||||||
import {ActivatedRoute} from "@angular/router";
|
import {ActivatedRoute, Router} from "@angular/router";
|
||||||
|
import {MessengerService} from "../messenger.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-update-project',
|
selector: 'app-update-project',
|
||||||
@ -10,7 +11,10 @@ import {ActivatedRoute} from "@angular/router";
|
|||||||
})
|
})
|
||||||
export class UpdateProjectComponent implements OnInit {
|
export class UpdateProjectComponent implements OnInit {
|
||||||
|
|
||||||
constructor(private apiService: ApiService, private route: ActivatedRoute) {
|
constructor(private apiService: ApiService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private messengerService: MessengerService,
|
||||||
|
private router: Router) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private project: Project;
|
private project: Project;
|
||||||
@ -26,15 +30,28 @@ export class UpdateProjectComponent implements OnInit {
|
|||||||
private getProject() {
|
private getProject() {
|
||||||
this.apiService.getProject(this.projectId).subscribe(data => {
|
this.apiService.getProject(this.projectId).subscribe(data => {
|
||||||
this.project = <Project>{
|
this.project = <Project>{
|
||||||
|
id: data["project"]["id"],
|
||||||
name: data["project"]["name"],
|
name: data["project"]["name"],
|
||||||
clone_url: data["project"]["clone_url"],
|
clone_url: data["project"]["clone_url"],
|
||||||
git_repo: data["project"]["git_repo"],
|
git_repo: data["project"]["git_repo"],
|
||||||
motd: data["project"]["motd"],
|
motd: data["project"]["motd"],
|
||||||
priority: data["project"]["priority"],
|
priority: data["project"]["priority"],
|
||||||
version: data["project"]["version"]
|
version: data["project"]["version"],
|
||||||
|
public: data["project"]["public"],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSubmit() {
|
||||||
|
this.apiService.updateProject(this.project).subscribe(
|
||||||
|
data => {
|
||||||
|
this.router.navigateByUrl("/project/" + this.project.id);
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.log(error.error.message);
|
||||||
|
this.messengerService.show(error.error.message);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user