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)
|
||||
|
||||
project.Version = version
|
||||
api.Database.UpdateProject(project)
|
||||
err := api.Database.UpdateProject(project)
|
||||
handleErr(err, r)
|
||||
}
|
||||
|
||||
func signatureValid(r *Request) (matches bool) {
|
||||
|
16
api/main.go
16
api/main.go
@ -1,6 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/buaazp/fasthttprouter"
|
||||
"github.com/valyala/fasthttp"
|
||||
@ -48,6 +49,7 @@ func New() *WebAPI {
|
||||
api.router.POST("/log/error", LogRequestMiddleware(LogError))
|
||||
|
||||
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.POST("/access/grant", LogRequestMiddleware(api.WorkerGrantAccess))
|
||||
@ -55,6 +57,7 @@ func New() *WebAPI {
|
||||
|
||||
api.router.POST("/project/create", LogRequestMiddleware(api.ProjectCreate))
|
||||
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", LogRequestMiddleware(api.ProjectGetAllStats))
|
||||
|
||||
@ -67,6 +70,19 @@ func New() *WebAPI {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,20 @@ type CreateProjectRequest struct {
|
||||
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 {
|
||||
Ok bool `json:"ok"`
|
||||
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 {
|
||||
if len(project.Name) <= 0 {
|
||||
return false
|
||||
}
|
||||
if project.Priority < 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@ -97,7 +167,7 @@ func isValidProject(project *storage.Project) bool {
|
||||
func (api *WebAPI) ProjectGet(r *Request) {
|
||||
|
||||
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)
|
||||
|
||||
|
77
api/task.go
77
api/task.go
@ -1,8 +1,13 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/hmac"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/dchest/siphash"
|
||||
"github.com/google/uuid"
|
||||
"src/task_tracker/storage"
|
||||
"strconv"
|
||||
@ -14,12 +19,13 @@ type CreateTaskRequest struct {
|
||||
Recipe string `json:"recipe"`
|
||||
Priority int64 `json:"priority"`
|
||||
MaxAssignTime int64 `json:"max_assign_time"`
|
||||
Hash64 int64 `json:"hash_u64"`
|
||||
UniqueString string `json:"unique_string"`
|
||||
}
|
||||
|
||||
type ReleaseTaskRequest struct {
|
||||
TaskId int64 `json:"task_id"`
|
||||
Success bool `json:"success"`
|
||||
WorkerId *uuid.UUID `json:"worker_id"`
|
||||
TaskId int64 `json:"task_id"`
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
type ReleaseTaskResponse struct {
|
||||
@ -51,8 +57,14 @@ func (api *WebAPI) TaskCreate(r *Request) {
|
||||
MaxAssignTime: createReq.MaxAssignTime,
|
||||
}
|
||||
|
||||
if isTaskValid(task) {
|
||||
err := api.Database.SaveTask(task, createReq.Project)
|
||||
if createReq.IsValid() && isTaskValid(task) {
|
||||
|
||||
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 {
|
||||
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 {
|
||||
if task.MaxRetries < 0 {
|
||||
return false
|
||||
@ -89,7 +105,7 @@ func isTaskValid(task *storage.Task) bool {
|
||||
|
||||
func (api *WebAPI) TaskGetFromProject(r *Request) {
|
||||
|
||||
worker, err := api.workerFromQueryArgs(r)
|
||||
worker, err := api.validateSignature(r)
|
||||
if err != nil {
|
||||
r.Json(GetTaskResponse{
|
||||
Ok: false,
|
||||
@ -121,7 +137,7 @@ func (api *WebAPI) TaskGetFromProject(r *Request) {
|
||||
|
||||
func (api *WebAPI) TaskGet(r *Request) {
|
||||
|
||||
worker, err := api.workerFromQueryArgs(r)
|
||||
worker, err := api.validateSignature(r)
|
||||
if err != nil {
|
||||
r.Json(GetTaskResponse{
|
||||
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)
|
||||
if err != nil {
|
||||
logrus.WithError(err).WithFields(logrus.Fields{
|
||||
@ -155,20 +173,53 @@ func (api WebAPI) workerFromQueryArgs(r *Request) (*storage.Worker, error) {
|
||||
if worker == nil {
|
||||
logrus.WithError(err).WithFields(logrus.Fields{
|
||||
"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")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (api *WebAPI) TaskRelease(r *Request) {
|
||||
|
||||
req := ReleaseTaskRequest{}
|
||||
if r.GetJson(req) {
|
||||
worker, err := api.validateSignature(r)
|
||||
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{
|
||||
Ok: res,
|
||||
|
@ -3,17 +3,28 @@ package api
|
||||
import (
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/google/uuid"
|
||||
"math/rand"
|
||||
"src/task_tracker/storage"
|
||||
"time"
|
||||
)
|
||||
|
||||
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 {
|
||||
Ok bool `json:"ok"`
|
||||
Message string `json:"message,omitempty"`
|
||||
WorkerId uuid.UUID `json:"id,omitempty"`
|
||||
Ok bool `json:"ok"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Worker *storage.Worker `json:"worker,omitempty"`
|
||||
}
|
||||
|
||||
type GetWorkerResponse struct {
|
||||
@ -55,13 +66,13 @@ func (api *WebAPI) WorkerCreate(r *Request) {
|
||||
return
|
||||
}
|
||||
|
||||
id, err := api.workerCreate(workerReq, getIdentity(r))
|
||||
worker, err := api.workerCreate(workerReq, getIdentity(r))
|
||||
if err != nil {
|
||||
handleErr(err, r)
|
||||
} else {
|
||||
r.OkJson(CreateWorkerResponse{
|
||||
Ok: true,
|
||||
WorkerId: id,
|
||||
Ok: true,
|
||||
Worker: worker,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -84,6 +95,9 @@ func (api *WebAPI) WorkerGet(r *Request) {
|
||||
worker := api.Database.GetWorker(id)
|
||||
|
||||
if worker != nil {
|
||||
|
||||
worker.Secret = nil
|
||||
|
||||
r.OkJson(GetWorkerResponse{
|
||||
Ok: true,
|
||||
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{
|
||||
Id: uuid.New(),
|
||||
Created: time.Now().Unix(),
|
||||
Identity: identity,
|
||||
Secret: makeSecret(),
|
||||
Alias: request.Alias,
|
||||
}
|
||||
|
||||
api.Database.SaveWorker(&worker)
|
||||
return worker.Id, nil
|
||||
return &worker, nil
|
||||
}
|
||||
|
||||
func canCreateWorker(r *Request, cwr *CreateWorkerRequest, identity *storage.Identity) bool {
|
||||
|
||||
if cwr.Alias == "unassigned" {
|
||||
//Reserved alias
|
||||
return false
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
identity := storage.Identity{
|
||||
|
@ -1,9 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"src/task_tracker/api"
|
||||
"src/task_tracker/config"
|
||||
"src/task_tracker/storage"
|
||||
"time"
|
||||
)
|
||||
|
||||
func tmpDebugSetup() {
|
||||
@ -15,6 +17,7 @@ func tmpDebugSetup() {
|
||||
|
||||
func main() {
|
||||
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
config.SetupConfig()
|
||||
|
||||
webApi := api.New()
|
||||
|
@ -26,9 +26,10 @@ CREATE TABLE worker_identity
|
||||
CREATE TABLE worker
|
||||
(
|
||||
id TEXT PRIMARY KEY,
|
||||
alias TEXT DEFAULT NULL,
|
||||
alias TEXT,
|
||||
created INTEGER,
|
||||
identity INTEGER REFERENCES workerIdentity (id)
|
||||
identity INTEGER REFERENCES worker_identity (id),
|
||||
secret BYTEA
|
||||
);
|
||||
|
||||
CREATE TABLE project
|
||||
@ -61,7 +62,8 @@ CREATE TABLE task
|
||||
status Status DEFAULT 'new',
|
||||
recipe TEXT,
|
||||
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
|
||||
|
@ -3,7 +3,6 @@ package storage
|
||||
import (
|
||||
"database/sql"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/google/uuid"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -19,8 +18,8 @@ type Project struct {
|
||||
}
|
||||
|
||||
type AssignedTasks struct {
|
||||
Assignee uuid.UUID `json:"assignee"`
|
||||
TaskCount int64 `json:"task_count"`
|
||||
Assignee string `json:"assignee"`
|
||||
TaskCount int64 `json:"task_count"`
|
||||
}
|
||||
|
||||
type ProjectStats struct {
|
||||
@ -116,14 +115,16 @@ func (database *Database) GetProjectWithRepoName(repoName string) *Project {
|
||||
return project
|
||||
}
|
||||
|
||||
func (database *Database) UpdateProject(project *Project) {
|
||||
func (database *Database) UpdateProject(project *Project) error {
|
||||
|
||||
db := database.getDB()
|
||||
|
||||
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`,
|
||||
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()
|
||||
|
||||
@ -132,7 +133,7 @@ func (database *Database) UpdateProject(project *Project) {
|
||||
"rowsAffected": rowsAffected,
|
||||
}).Trace("Database.updateProject UPDATE project")
|
||||
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
func (database *Database) GetProjectStats(id int64) *ProjectStats {
|
||||
@ -154,18 +155,27 @@ func (database *Database) GetProjectStats(id int64) *ProjectStats {
|
||||
logrus.WithError(err).WithFields(logrus.Fields{
|
||||
"id": id,
|
||||
}).Trace("Get project stats: No task for this project")
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
//todo: only expose worker alias
|
||||
rows, err := db.Query(`SELECT assignee, COUNT(*) FROM TASK
|
||||
LEFT JOIN worker ON TASK.assignee = worker.id WHERE project=$1 GROUP BY assignee`, id)
|
||||
rows, err := db.Query(`SELECT worker.alias, COUNT(*) as wc FROM TASK
|
||||
LEFT JOIN worker ON TASK.assignee = worker.id WHERE project=$1
|
||||
GROUP BY worker.id ORDER BY wc LIMIT 10`, id)
|
||||
|
||||
stats.Assignees = []*AssignedTasks{}
|
||||
|
||||
for rows.Next() {
|
||||
assignee := AssignedTasks{}
|
||||
err = rows.Scan(&assignee.Assignee, &assignee.TaskCount)
|
||||
var assigneeAlias sql.NullString
|
||||
err = rows.Scan(&assigneeAlias, &assignee.TaskCount)
|
||||
handleErr(err)
|
||||
|
||||
if assigneeAlias.Valid {
|
||||
assignee.Assignee = assigneeAlias.String
|
||||
} else {
|
||||
assignee.Assignee = "unassigned"
|
||||
}
|
||||
|
||||
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='closed' THEN 1 ELSE 0 END) closedCount,
|
||||
p.*
|
||||
FROM task INNER JOIN project p on task.project = p.id
|
||||
GROUP BY p.id`)
|
||||
FROM task RIGHT JOIN project p on task.project = p.id
|
||||
GROUP BY p.id ORDER BY p.name`)
|
||||
handleErr(err)
|
||||
|
||||
for rows.Next() {
|
||||
@ -191,7 +201,7 @@ func (database Database) GetAllProjectsStats() *[]ProjectStats {
|
||||
stats := ProjectStats{}
|
||||
p := &Project{}
|
||||
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)
|
||||
|
||||
stats.Project = p
|
||||
|
@ -2,6 +2,7 @@ package storage
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
@ -19,13 +20,14 @@ type Task struct {
|
||||
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()
|
||||
|
||||
res, err := db.Exec(`
|
||||
INSERT INTO task (project, max_retries, recipe, priority, max_assign_time)
|
||||
VALUES ($1,$2,$3,$4,$5)`,
|
||||
//TODO: For some reason it refuses to insert the 64-bit value unless I do that...
|
||||
res, err := db.Exec(fmt.Sprintf(`
|
||||
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)
|
||||
if err != nil {
|
||||
logrus.WithError(err).WithFields(logrus.Fields{
|
||||
@ -57,7 +59,7 @@ func (database *Database) GetTask(worker *Worker) *Task {
|
||||
SELECT task.id
|
||||
FROM task
|
||||
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 (
|
||||
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 {
|
||||
|
||||
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
|
||||
WHERE task.id=$1`, id)
|
||||
task := scanTask(row)
|
||||
@ -109,11 +112,11 @@ func (database Database) ReleaseTask(id int64, workerId *uuid.UUID, success bool
|
||||
var err error
|
||||
if success {
|
||||
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 {
|
||||
res, err = db.Exec(`UPDATE task SET (status, assignee, retries) =
|
||||
(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)
|
||||
|
||||
@ -138,7 +141,7 @@ func (database *Database) GetTaskFromProject(worker *Worker, projectId int64) *T
|
||||
SELECT task.id
|
||||
FROM task
|
||||
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 (
|
||||
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"`
|
||||
Created int64 `json:"created"`
|
||||
Identity *Identity `json:"identity"`
|
||||
Alias string `json:"alias,omitempty"`
|
||||
Secret []byte `json:"secret"`
|
||||
}
|
||||
|
||||
func (database *Database) SaveWorker(worker *Worker) {
|
||||
@ -24,8 +26,8 @@ func (database *Database) SaveWorker(worker *Worker) {
|
||||
|
||||
identityId := getOrCreateIdentity(worker.Identity, db)
|
||||
|
||||
res, err := db.Exec("INSERT INTO worker (id, created, identity) VALUES ($1,$2,$3)",
|
||||
worker.Id, worker.Created, identityId)
|
||||
res, err := db.Exec("INSERT INTO worker (id, created, identity, secret, alias) VALUES ($1,$2,$3,$4,$5)",
|
||||
worker.Id, worker.Created, identityId, worker.Secret, worker.Alias)
|
||||
handleErr(err)
|
||||
|
||||
var rowsAffected, _ = res.RowsAffected()
|
||||
@ -41,8 +43,8 @@ func (database *Database) GetWorker(id uuid.UUID) *Worker {
|
||||
worker := &Worker{}
|
||||
var identityId int64
|
||||
|
||||
row := db.QueryRow("SELECT id, created, identity FROM worker WHERE id=$1", id)
|
||||
err := row.Scan(&worker.Id, &worker.Created, &identityId)
|
||||
row := db.QueryRow("SELECT id, created, identity, secret, alias FROM worker WHERE id=$1", id)
|
||||
err := row.Scan(&worker.Id, &worker.Created, &identityId, &worker.Secret, &worker.Alias)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"id": id,
|
||||
@ -64,7 +66,7 @@ func getIdentity(id int64, db *sql.DB) (*Identity, error) {
|
||||
|
||||
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)
|
||||
|
||||
if err != nil {
|
||||
@ -80,7 +82,7 @@ func getIdentity(id int64, db *sql.DB) (*Identity, error) {
|
||||
|
||||
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)
|
||||
handleErr(err)
|
||||
|
||||
@ -89,7 +91,7 @@ func getOrCreateIdentity(identity *Identity, db *sql.DB) int64 {
|
||||
"rowsAffected": rowsAffected,
|
||||
}).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
|
||||
err = row.Scan(&rowId)
|
||||
@ -127,7 +129,7 @@ func (database *Database) GrantAccess(workerId *uuid.UUID, projectId int64) bool
|
||||
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()
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
r := Post("/git/receivehook", api.GitPayload{})
|
||||
r := Post("/git/receivehook", api.GitPayload{}, nil)
|
||||
|
||||
if r.StatusCode != 403 {
|
||||
t.Error()
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
|
||||
func TestIndex(t *testing.T) {
|
||||
|
||||
r := Get("/")
|
||||
r := Get("/", nil)
|
||||
|
||||
body, _ := ioutil.ReadAll(r.Body)
|
||||
var info api.Info
|
||||
|
@ -16,7 +16,7 @@ func TestTraceValid(t *testing.T) {
|
||||
Scope: "test",
|
||||
Message: "This is a test message",
|
||||
TimeStamp: time.Now().Unix(),
|
||||
})
|
||||
}, nil)
|
||||
|
||||
if r.StatusCode != 200 {
|
||||
t.Fail()
|
||||
@ -27,7 +27,7 @@ func TestTraceInvalidScope(t *testing.T) {
|
||||
r := Post("/log/trace", api.LogRequest{
|
||||
Message: "this is a test message",
|
||||
TimeStamp: time.Now().Unix(),
|
||||
})
|
||||
}, nil)
|
||||
|
||||
if r.StatusCode != 500 {
|
||||
t.Fail()
|
||||
@ -37,7 +37,7 @@ func TestTraceInvalidScope(t *testing.T) {
|
||||
Scope: "",
|
||||
Message: "this is a test message",
|
||||
TimeStamp: time.Now().Unix(),
|
||||
})
|
||||
}, nil)
|
||||
|
||||
if r.StatusCode != 500 {
|
||||
t.Fail()
|
||||
@ -52,7 +52,7 @@ func TestTraceInvalidMessage(t *testing.T) {
|
||||
Scope: "test",
|
||||
Message: "",
|
||||
TimeStamp: time.Now().Unix(),
|
||||
})
|
||||
}, nil)
|
||||
|
||||
if r.StatusCode != 500 {
|
||||
t.Fail()
|
||||
@ -66,7 +66,7 @@ func TestTraceInvalidTime(t *testing.T) {
|
||||
r := Post("/log/trace", api.LogRequest{
|
||||
Scope: "test",
|
||||
Message: "test",
|
||||
})
|
||||
}, nil)
|
||||
if r.StatusCode != 500 {
|
||||
t.Fail()
|
||||
}
|
||||
@ -81,7 +81,7 @@ func TestWarnValid(t *testing.T) {
|
||||
Scope: "test",
|
||||
Message: "test",
|
||||
TimeStamp: time.Now().Unix(),
|
||||
})
|
||||
}, nil)
|
||||
if r.StatusCode != 200 {
|
||||
t.Fail()
|
||||
}
|
||||
@ -93,7 +93,7 @@ func TestInfoValid(t *testing.T) {
|
||||
Scope: "test",
|
||||
Message: "test",
|
||||
TimeStamp: time.Now().Unix(),
|
||||
})
|
||||
}, nil)
|
||||
if r.StatusCode != 200 {
|
||||
t.Fail()
|
||||
}
|
||||
@ -105,7 +105,7 @@ func TestErrorValid(t *testing.T) {
|
||||
Scope: "test",
|
||||
Message: "test",
|
||||
TimeStamp: time.Now().Unix(),
|
||||
})
|
||||
}, nil)
|
||||
if r.StatusCode != 200 {
|
||||
t.Fail()
|
||||
}
|
||||
@ -171,7 +171,7 @@ func getLogs(since int64, level logrus.Level) *api.GetLogResponse {
|
||||
r := Post(fmt.Sprintf("/logs"), api.GetLogRequest{
|
||||
Since: since,
|
||||
Level: level,
|
||||
})
|
||||
}, nil)
|
||||
|
||||
resp := &api.GetLogResponse{}
|
||||
data, _ := ioutil.ReadAll(r.Body)
|
||||
|
@ -3,7 +3,6 @@ package test
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"src/task_tracker/api"
|
||||
@ -132,28 +131,30 @@ func TestGetProjectStats(t *testing.T) {
|
||||
CloneUrl: "http://github.com/drone/test",
|
||||
GitRepo: "drone/test",
|
||||
Priority: 3,
|
||||
Public: true,
|
||||
})
|
||||
|
||||
pid := r.Id
|
||||
worker := genWid()
|
||||
|
||||
createTask(api.CreateTaskRequest{
|
||||
Priority: 1,
|
||||
Project: pid,
|
||||
MaxRetries: 0,
|
||||
Recipe: "{}",
|
||||
})
|
||||
}, worker)
|
||||
createTask(api.CreateTaskRequest{
|
||||
Priority: 2,
|
||||
Project: pid,
|
||||
MaxRetries: 0,
|
||||
Recipe: "{}",
|
||||
})
|
||||
}, worker)
|
||||
createTask(api.CreateTaskRequest{
|
||||
Priority: 3,
|
||||
Project: pid,
|
||||
MaxRetries: 0,
|
||||
Recipe: "{}",
|
||||
})
|
||||
}, worker)
|
||||
|
||||
stats := getProjectStats(pid)
|
||||
|
||||
@ -169,7 +170,7 @@ func TestGetProjectStats(t *testing.T) {
|
||||
t.Error()
|
||||
}
|
||||
|
||||
if stats.Stats.Assignees[0].Assignee != uuid.Nil {
|
||||
if stats.Stats.Assignees[0].Assignee != "unassigned" {
|
||||
t.Error()
|
||||
}
|
||||
if stats.Stats.Assignees[0].TaskCount != 3 {
|
||||
@ -189,19 +190,132 @@ func TestGetProjectStatsNotFound(t *testing.T) {
|
||||
})
|
||||
s := getProjectStats(r.Id)
|
||||
|
||||
if s.Ok != false {
|
||||
if s.Ok != true {
|
||||
t.Error()
|
||||
}
|
||||
|
||||
if len(s.Message) <= 0 {
|
||||
if s.Stats == nil {
|
||||
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 {
|
||||
|
||||
r := Post("/project/create", req)
|
||||
r := Post("/project/create", req, nil)
|
||||
|
||||
var resp api.CreateProjectResponse
|
||||
data, _ := ioutil.ReadAll(r.Body)
|
||||
@ -213,7 +327,7 @@ func createProject(req api.CreateProjectRequest) *api.CreateProjectResponse {
|
||||
|
||||
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
|
||||
data, _ := ioutil.ReadAll(r.Body)
|
||||
@ -225,7 +339,7 @@ func getProject(id int64) (*api.GetProjectResponse, *http.Response) {
|
||||
|
||||
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
|
||||
data, _ := ioutil.ReadAll(r.Body)
|
||||
@ -234,3 +348,15 @@ func getProjectStats(id int64) *api.GetProjectStatsResponse {
|
||||
|
||||
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",
|
||||
})
|
||||
|
||||
worker := genWid()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
createTask(api.CreateTaskRequest{
|
||||
@ -22,6 +24,6 @@ func BenchmarkCreateTask(b *testing.B) {
|
||||
Priority: 1,
|
||||
Recipe: "{}",
|
||||
MaxRetries: 1,
|
||||
})
|
||||
}, worker)
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"io/ioutil"
|
||||
"src/task_tracker/api"
|
||||
"src/task_tracker/storage"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -18,11 +19,13 @@ func TestCreateTaskValid(t *testing.T) {
|
||||
CloneUrl: "http://github.com/test/test",
|
||||
})
|
||||
|
||||
worker := genWid()
|
||||
|
||||
resp := createTask(api.CreateTaskRequest{
|
||||
Project: 1,
|
||||
Recipe: "{}",
|
||||
MaxRetries: 3,
|
||||
})
|
||||
}, worker)
|
||||
|
||||
if resp.Ok != true {
|
||||
t.Fail()
|
||||
@ -31,11 +34,13 @@ func TestCreateTaskValid(t *testing.T) {
|
||||
|
||||
func TestCreateTaskInvalidProject(t *testing.T) {
|
||||
|
||||
worker := genWid()
|
||||
|
||||
resp := createTask(api.CreateTaskRequest{
|
||||
Project: 123456,
|
||||
Recipe: "{}",
|
||||
MaxRetries: 3,
|
||||
})
|
||||
}, worker)
|
||||
|
||||
if resp.Ok != false {
|
||||
t.Error()
|
||||
@ -62,7 +67,9 @@ func TestGetTaskInvalidWid(t *testing.T) {
|
||||
func TestGetTaskInvalidWorker(t *testing.T) {
|
||||
|
||||
id := uuid.New()
|
||||
resp := getTask(&id)
|
||||
resp := getTask(&storage.Worker{
|
||||
Id: id,
|
||||
})
|
||||
|
||||
if resp.Ok != false {
|
||||
t.Error()
|
||||
@ -76,7 +83,9 @@ func TestGetTaskInvalidWorker(t *testing.T) {
|
||||
func TestGetTaskFromProjectInvalidWorker(t *testing.T) {
|
||||
|
||||
id := uuid.New()
|
||||
resp := getTaskFromProject(1, &id)
|
||||
resp := getTaskFromProject(1, &storage.Worker{
|
||||
Id: id,
|
||||
})
|
||||
|
||||
if resp.Ok != false {
|
||||
t.Error()
|
||||
@ -89,10 +98,12 @@ func TestGetTaskFromProjectInvalidWorker(t *testing.T) {
|
||||
|
||||
func TestCreateTaskInvalidRetries(t *testing.T) {
|
||||
|
||||
worker := genWid()
|
||||
|
||||
resp := createTask(api.CreateTaskRequest{
|
||||
Project: 1,
|
||||
MaxRetries: -1,
|
||||
})
|
||||
}, worker)
|
||||
|
||||
if resp.Ok != false {
|
||||
t.Error()
|
||||
@ -105,11 +116,13 @@ func TestCreateTaskInvalidRetries(t *testing.T) {
|
||||
|
||||
func TestCreateTaskInvalidRecipe(t *testing.T) {
|
||||
|
||||
worker := genWid()
|
||||
|
||||
resp := createTask(api.CreateTaskRequest{
|
||||
Project: 1,
|
||||
Recipe: "",
|
||||
MaxRetries: 3,
|
||||
})
|
||||
}, worker)
|
||||
|
||||
if resp.Ok != false {
|
||||
t.Error()
|
||||
@ -132,12 +145,14 @@ func TestCreateGetTask(t *testing.T) {
|
||||
Public: true,
|
||||
})
|
||||
|
||||
worker := genWid()
|
||||
|
||||
createTask(api.CreateTaskRequest{
|
||||
Project: resp.Id,
|
||||
Recipe: "{\"url\":\"test\"}",
|
||||
MaxRetries: 3,
|
||||
Priority: 9999,
|
||||
})
|
||||
}, worker)
|
||||
|
||||
taskResp := getTaskFromProject(resp.Id, genWid())
|
||||
|
||||
@ -194,26 +209,27 @@ func createTasks(prefix string) (int64, int64) {
|
||||
Priority: 999,
|
||||
Public: true,
|
||||
})
|
||||
worker := genWid()
|
||||
createTask(api.CreateTaskRequest{
|
||||
Project: lowP.Id,
|
||||
Recipe: "low1",
|
||||
Priority: 0,
|
||||
})
|
||||
}, worker)
|
||||
createTask(api.CreateTaskRequest{
|
||||
Project: lowP.Id,
|
||||
Recipe: "low2",
|
||||
Priority: 1,
|
||||
})
|
||||
}, worker)
|
||||
createTask(api.CreateTaskRequest{
|
||||
Project: highP.Id,
|
||||
Recipe: "high1",
|
||||
Priority: 100,
|
||||
})
|
||||
}, worker)
|
||||
createTask(api.CreateTaskRequest{
|
||||
Project: highP.Id,
|
||||
Recipe: "high2",
|
||||
Priority: 101,
|
||||
})
|
||||
}, worker)
|
||||
|
||||
return lowP.Id, highP.Id
|
||||
}
|
||||
@ -274,7 +290,7 @@ func TestTaskPriority(t *testing.T) {
|
||||
|
||||
func TestTaskNoAccess(t *testing.T) {
|
||||
|
||||
wid := genWid()
|
||||
worker := genWid()
|
||||
|
||||
pid := createProject(api.CreateProjectRequest{
|
||||
Name: "This is a private proj",
|
||||
@ -292,16 +308,16 @@ func TestTaskNoAccess(t *testing.T) {
|
||||
MaxAssignTime: 10,
|
||||
MaxRetries: 2,
|
||||
Recipe: "---",
|
||||
})
|
||||
}, worker)
|
||||
|
||||
if createResp.Ok != true {
|
||||
t.Error()
|
||||
}
|
||||
|
||||
grantAccess(wid, pid)
|
||||
removeAccess(wid, pid)
|
||||
grantAccess(&worker.Id, pid)
|
||||
removeAccess(&worker.Id, pid)
|
||||
|
||||
tResp := getTaskFromProject(pid, wid)
|
||||
tResp := getTaskFromProject(pid, worker)
|
||||
|
||||
if tResp.Ok != false {
|
||||
t.Error()
|
||||
@ -316,7 +332,7 @@ func TestTaskNoAccess(t *testing.T) {
|
||||
|
||||
func TestTaskHasAccess(t *testing.T) {
|
||||
|
||||
wid := genWid()
|
||||
worker := genWid()
|
||||
|
||||
pid := createProject(api.CreateProjectRequest{
|
||||
Name: "This is a private proj1",
|
||||
@ -334,15 +350,15 @@ func TestTaskHasAccess(t *testing.T) {
|
||||
MaxAssignTime: 10,
|
||||
MaxRetries: 2,
|
||||
Recipe: "---",
|
||||
})
|
||||
}, worker)
|
||||
|
||||
if createResp.Ok != true {
|
||||
t.Error()
|
||||
}
|
||||
|
||||
grantAccess(wid, pid)
|
||||
grantAccess(&worker.Id, pid)
|
||||
|
||||
tResp := getTaskFromProject(pid, wid)
|
||||
tResp := getTaskFromProject(pid, worker)
|
||||
|
||||
if tResp.Ok != true {
|
||||
t.Error()
|
||||
@ -354,16 +370,16 @@ func TestTaskHasAccess(t *testing.T) {
|
||||
|
||||
func TestNoMoreTasks(t *testing.T) {
|
||||
|
||||
wid := genWid()
|
||||
worker := genWid()
|
||||
|
||||
for i := 0; i < 15; i++ {
|
||||
getTask(wid)
|
||||
getTask(worker)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReleaseTaskSuccess(t *testing.T) {
|
||||
|
||||
//wid := genWid()
|
||||
worker := genWid()
|
||||
|
||||
pid := createProject(api.CreateProjectRequest{
|
||||
Priority: 0,
|
||||
@ -372,6 +388,7 @@ func TestReleaseTaskSuccess(t *testing.T) {
|
||||
Version: "11111111111111111",
|
||||
Name: "testreleasetask",
|
||||
Motd: "",
|
||||
Public: true,
|
||||
}).Id
|
||||
|
||||
createTask(api.CreateTaskRequest{
|
||||
@ -379,13 +396,119 @@ func TestReleaseTaskSuccess(t *testing.T) {
|
||||
Project: pid,
|
||||
Recipe: "{}",
|
||||
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
|
||||
data, _ := ioutil.ReadAll(r.Body)
|
||||
@ -395,9 +518,9 @@ func createTask(request api.CreateTaskRequest) *api.CreateTaskResponse {
|
||||
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
|
||||
data, _ := ioutil.ReadAll(r.Body)
|
||||
@ -407,9 +530,9 @@ func getTask(wid *uuid.UUID) *api.GetTaskResponse {
|
||||
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
|
||||
data, _ := ioutil.ReadAll(r.Body)
|
||||
@ -418,3 +541,15 @@ func getTaskFromProject(project int64, wid *uuid.UUID) *api.GetTaskResponse {
|
||||
|
||||
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"
|
||||
"net/http"
|
||||
"src/task_tracker/api"
|
||||
"src/task_tracker/storage"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCreateGetWorker(t *testing.T) {
|
||||
|
||||
resp, r := createWorker(api.CreateWorkerRequest{})
|
||||
resp, r := createWorker(api.CreateWorkerRequest{
|
||||
Alias: "my_worker_alias",
|
||||
})
|
||||
|
||||
if r.StatusCode != 200 {
|
||||
t.Error()
|
||||
@ -22,12 +25,12 @@ func TestCreateGetWorker(t *testing.T) {
|
||||
t.Error()
|
||||
}
|
||||
|
||||
getResp, r := getWorker(resp.WorkerId.String())
|
||||
getResp, r := getWorker(resp.Worker.Id.String())
|
||||
|
||||
if r.StatusCode != 200 {
|
||||
t.Error()
|
||||
}
|
||||
if resp.WorkerId != getResp.Worker.Id {
|
||||
if resp.Worker.Id != getResp.Worker.Id {
|
||||
t.Error()
|
||||
}
|
||||
|
||||
@ -37,6 +40,9 @@ func TestCreateGetWorker(t *testing.T) {
|
||||
if len(getResp.Worker.Identity.UserAgent) <= 0 {
|
||||
t.Error()
|
||||
}
|
||||
if resp.Worker.Alias != "my_worker_alias" {
|
||||
t.Error()
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetWorkerNotFound(t *testing.T) {
|
||||
@ -70,7 +76,7 @@ func TestGrantAccessFailedProjectConstraint(t *testing.T) {
|
||||
|
||||
wid := genWid()
|
||||
|
||||
resp := grantAccess(wid, 38274593)
|
||||
resp := grantAccess(&wid.Id, 38274593)
|
||||
|
||||
if resp.Ok != false {
|
||||
t.Error()
|
||||
@ -82,9 +88,9 @@ func TestGrantAccessFailedProjectConstraint(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 {
|
||||
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) {
|
||||
r := Post("/worker/create", req)
|
||||
r := Post("/worker/create", req, nil)
|
||||
|
||||
var resp *api.CreateWorkerResponse
|
||||
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) {
|
||||
|
||||
r := Get(fmt.Sprintf("/worker/get/%s", id))
|
||||
r := Get(fmt.Sprintf("/worker/get/%s", id), nil)
|
||||
|
||||
var resp *api.GetWorkerResponse
|
||||
data, _ := ioutil.ReadAll(r.Body)
|
||||
@ -161,10 +202,10 @@ func getWorker(id string) (*api.GetWorkerResponse, *http.Response) {
|
||||
return resp, r
|
||||
}
|
||||
|
||||
func genWid() *uuid.UUID {
|
||||
func genWid() *storage.Worker {
|
||||
|
||||
resp, _ := createWorker(api.CreateWorkerRequest{})
|
||||
return &resp.WorkerId
|
||||
return resp.Worker
|
||||
}
|
||||
|
||||
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{
|
||||
WorkerId: wid,
|
||||
ProjectId: project,
|
||||
})
|
||||
}, nil)
|
||||
|
||||
var resp *api.WorkerAccessResponse
|
||||
data, _ := ioutil.ReadAll(r.Body)
|
||||
@ -187,7 +228,7 @@ func removeAccess(wid *uuid.UUID, project int64) *api.WorkerAccessResponse {
|
||||
r := Post("/access/remove", api.WorkerAccessRequest{
|
||||
WorkerId: wid,
|
||||
ProjectId: project,
|
||||
})
|
||||
}, nil)
|
||||
|
||||
var resp *api.WorkerAccessResponse
|
||||
data, _ := ioutil.ReadAll(r.Body)
|
||||
@ -196,3 +237,15 @@ func removeAccess(wid *uuid.UUID, project int64) *api.WorkerAccessResponse {
|
||||
|
||||
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 (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/hmac"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"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)
|
||||
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)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func Get(path string) *http.Response {
|
||||
r, err := http.Get("http://" + config.Cfg.ServerAddr + path)
|
||||
func Get(path string, worker *storage.Worker) *http.Response {
|
||||
|
||||
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)
|
||||
|
||||
return r
|
||||
|
@ -26,9 +26,10 @@ CREATE TABLE worker_identity
|
||||
CREATE TABLE worker
|
||||
(
|
||||
id TEXT PRIMARY KEY,
|
||||
alias TEXT DEFAULT NULL,
|
||||
alias TEXT,
|
||||
created INTEGER,
|
||||
identity INTEGER REFERENCES workerIdentity (id)
|
||||
identity INTEGER REFERENCES worker_identity (id),
|
||||
secret BYTEA
|
||||
);
|
||||
|
||||
CREATE TABLE project
|
||||
@ -61,7 +62,8 @@ CREATE TABLE task
|
||||
status Status DEFAULT 'new',
|
||||
recipe TEXT,
|
||||
max_assign_time INTEGER DEFAULT 0,
|
||||
assign_time INTEGER DEFAULT 0
|
||||
assign_time INTEGER DEFAULT 0,
|
||||
hash64 BIGINT UNIQUE
|
||||
);
|
||||
|
||||
CREATE TABLE log_entry
|
||||
|
@ -1,5 +1,6 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {Project} from "./models/project";
|
||||
|
||||
@Injectable()
|
||||
export class ApiService {
|
||||
@ -26,4 +27,12 @@ export class ApiService {
|
||||
getProject(id: number) {
|
||||
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>
|
||||
<li><a [routerLink]="''">Index</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]="'new_project'">new project</a></li>
|
||||
</ul>
|
||||
<!--</mat-toolbar>-->
|
||||
|
||||
<messenger-snack-bar></messenger-snack-bar>
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
|
@ -20,18 +20,21 @@ import {
|
||||
MatPaginatorModule,
|
||||
MatSliderModule,
|
||||
MatSlideToggleModule,
|
||||
MatSnackBarModule,
|
||||
MatSortModule,
|
||||
MatTableModule,
|
||||
MatToolbarModule,
|
||||
MatTreeModule
|
||||
} from "@angular/material";
|
||||
import {ApiService} from "./api.service";
|
||||
import {MessengerService} from "./messenger.service";
|
||||
import {HttpClientModule} from "@angular/common/http";
|
||||
import {ProjectDashboardComponent} from './project-dashboard/project-dashboard.component';
|
||||
import {ProjectListComponent} from './project-list/project-list.component';
|
||||
import {CreateProjectComponent} from './create-project/create-project.component';
|
||||
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
|
||||
import {UpdateProjectComponent} from './update-project/update-project.component';
|
||||
import {SnackBarComponent} from "./messenger/snack-bar.component";
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@ -40,7 +43,8 @@ import {UpdateProjectComponent} from './update-project/update-project.component'
|
||||
ProjectDashboardComponent,
|
||||
ProjectListComponent,
|
||||
CreateProjectComponent,
|
||||
UpdateProjectComponent
|
||||
UpdateProjectComponent,
|
||||
SnackBarComponent,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
@ -65,12 +69,17 @@ import {UpdateProjectComponent} from './update-project/update-project.component'
|
||||
MatSliderModule,
|
||||
MatSlideToggleModule,
|
||||
MatCheckboxModule,
|
||||
MatDividerModule
|
||||
MatDividerModule,
|
||||
MatSnackBarModule,
|
||||
|
||||
],
|
||||
exports: [],
|
||||
providers: [
|
||||
ApiService,
|
||||
MessengerService,
|
||||
],
|
||||
entryComponents: [
|
||||
SnackBarComponent,
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
|
@ -3,7 +3,7 @@
|
||||
<mat-card-subtitle></mat-card-subtitle>
|
||||
|
||||
<mat-card-content>
|
||||
<form>
|
||||
<form (ngSubmit)="onSubmit()">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Project name</mat-label>
|
||||
<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="submit" value="Create">
|
||||
</form>
|
||||
</mat-card-content>
|
||||
|
||||
<mat-card-actions>
|
||||
<button>Create</button>
|
||||
</mat-card-actions>
|
||||
|
||||
</mat-card>
|
||||
|
@ -1,5 +1,9 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {Project} from "../models/project";
|
||||
import {ApiService} from "../api.service";
|
||||
import {MessengerService} from "../messenger.service";
|
||||
import {Router} from "@angular/router";
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-create-project',
|
||||
@ -10,7 +14,9 @@ export class CreateProjectComponent implements OnInit {
|
||||
|
||||
private project = new Project();
|
||||
|
||||
constructor() {
|
||||
constructor(private apiService: ApiService,
|
||||
private messengerService: MessengerService,
|
||||
private router: Router) {
|
||||
this.project.name = "test";
|
||||
this.project.public = true;
|
||||
}
|
||||
@ -18,4 +24,16 @@ export class CreateProjectComponent implements OnInit {
|
||||
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 {
|
||||
|
||||
public id: number;
|
||||
public priority: number;
|
||||
public motd: string;
|
||||
public name: string;
|
||||
|
@ -37,6 +37,8 @@
|
||||
<pre>{{projectStats | json}}</pre>
|
||||
</mat-expansion-panel>
|
||||
|
||||
<a [routerLink]="'/project/' + projectStats.project.id + '/update'">Update</a>
|
||||
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
|
@ -56,14 +56,14 @@ export class ProjectDashboardComponent implements OnInit {
|
||||
setupStatusPieChart() {
|
||||
let tooltip = d3.select("#stooltip");
|
||||
|
||||
this.statusSvg = d3.select('#status')
|
||||
this.statusSvg = d3.select("#status")
|
||||
.append('svg')
|
||||
.attr('width', this.pieWidth)
|
||||
.attr('height', this.pieHeight)
|
||||
.append("g")
|
||||
.attr("transform", "translate(" + this.pieRadius + "," + this.pieRadius + ")");
|
||||
|
||||
this.statusPath = this.statusSvg.selectAll("path")
|
||||
this.statusPath = this.statusSvg.selectAll()
|
||||
.data(this.pieFun(this.statusData))
|
||||
.enter()
|
||||
.append('path')
|
||||
@ -76,14 +76,14 @@ export class ProjectDashboardComponent implements OnInit {
|
||||
setupAssigneesPieChart() {
|
||||
let tooltip = d3.select("#atooltip");
|
||||
|
||||
this.assigneesSvg = d3.select('#assignees')
|
||||
this.assigneesSvg = d3.select("#assignees")
|
||||
.append('svg')
|
||||
.attr('width', this.pieWidth)
|
||||
.attr('height', this.pieHeight)
|
||||
.append("g")
|
||||
.attr("transform", "translate(" + this.pieRadius + "," + this.pieRadius + ")");
|
||||
|
||||
this.assigneesPath = this.assigneesSvg.selectAll("path")
|
||||
this.assigneesPath = this.assigneesSvg.selectAll()
|
||||
.data(this.pieFun(this.assigneesData))
|
||||
.enter()
|
||||
.append('path')
|
||||
@ -229,10 +229,10 @@ export class ProjectDashboardComponent implements OnInit {
|
||||
{label: "Failed", count: this.projectStats["failed_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 {
|
||||
label: assignedTasks["assignee"] == "00000000-0000-0000-0000-000000000000" ? "unassigned" : assignedTasks["assignee"],
|
||||
count: assignedTasks["task_count"]
|
||||
label: assignedTask["assignee"],
|
||||
count: assignedTask["task_count"],
|
||||
}
|
||||
});
|
||||
|
||||
@ -256,6 +256,16 @@ export class ProjectDashboardComponent implements OnInit {
|
||||
];
|
||||
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},
|
||||
];
|
||||
|
||||
this.setupStatusPieChart();
|
||||
|
@ -12,6 +12,7 @@
|
||||
<pre>{{stats.project | json}}</pre>
|
||||
<div style="display: flex;">
|
||||
<a [routerLink]="'/project/' + stats.project.id">Dashboard</a>
|
||||
<a [routerLink]="'/project/' + stats.project.id + '/update'">Update</a>
|
||||
</div>
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
|
@ -1,29 +1,29 @@
|
||||
<mat-card>
|
||||
<mat-card-title>Update project</mat-card-title>
|
||||
<mat-card-subtitle>Changes are saved in real time</mat-card-subtitle>
|
||||
|
||||
<mat-card-content>
|
||||
<form>
|
||||
<mat-form-field>
|
||||
<form (ngSubmit)="onSubmit()" *ngIf="project != undefined">
|
||||
<mat-form-field appearance="outline">
|
||||
<input type="text" matInput [(ngModel)]="project.name" name="name" placeholder="Name">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field>
|
||||
<textarea matInput placeholder="Message of the day"></textarea>
|
||||
<mat-form-field appearance="outline">
|
||||
<textarea matInput [(ngModel)]="project.motd" placeholder="Message of the day" name="motd"></textarea>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field>
|
||||
<mat-form-field appearance="outline">
|
||||
<input type="text" matInput [(ngModel)]="project.clone_url" name="clone_url"
|
||||
placeholder="Git clone url">
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<input type="text" matInput [(ngModel)]="project.git_repo" name="clone_url"
|
||||
<mat-form-field appearance="outline">
|
||||
<input type="text" matInput [(ngModel)]="project.git_repo" name="git_repo"
|
||||
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
|
||||
enabled
|
||||
</mat-hint>
|
||||
</mat-form-field>
|
||||
<input type="hidden" name="version" value="{{project.version}}">
|
||||
|
||||
<input type="submit" value="Update">
|
||||
</form>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
@ -1,7 +1,8 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {Project} from "../models/project";
|
||||
import {ApiService} from "../api.service";
|
||||
import {ActivatedRoute} from "@angular/router";
|
||||
import {ActivatedRoute, Router} from "@angular/router";
|
||||
import {MessengerService} from "../messenger.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-update-project',
|
||||
@ -10,7 +11,10 @@ import {ActivatedRoute} from "@angular/router";
|
||||
})
|
||||
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;
|
||||
@ -26,15 +30,28 @@ export class UpdateProjectComponent implements OnInit {
|
||||
private getProject() {
|
||||
this.apiService.getProject(this.projectId).subscribe(data => {
|
||||
this.project = <Project>{
|
||||
id: data["project"]["id"],
|
||||
name: data["project"]["name"],
|
||||
clone_url: data["project"]["clone_url"],
|
||||
git_repo: data["project"]["git_repo"],
|
||||
motd: data["project"]["motd"],
|
||||
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