Task chaining + some refactoring

This commit is contained in:
simon987 2019-02-15 22:10:02 -05:00
parent 07c0eca5aa
commit 6ca92bc0a7
31 changed files with 306 additions and 166 deletions

View File

@ -134,8 +134,8 @@ func (api *WebAPI) getAssociatedProject(payload *GitPayload) *storage.Project {
project := api.Database.GetProjectWithRepoName(payload.Repository.FullName) project := api.Database.GetProjectWithRepoName(payload.Repository.FullName)
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
"project": project, "projectChange": project,
}).Trace("Found project associated with WebHook") }).Trace("Found projectChange associated with WebHook")
return project return project
} }

View File

@ -83,18 +83,18 @@ func New() *WebAPI {
api.router.POST("/access/grant", LogRequestMiddleware(api.WorkerGrantAccess)) api.router.POST("/access/grant", LogRequestMiddleware(api.WorkerGrantAccess))
api.router.POST("/access/remove", LogRequestMiddleware(api.WorkerRemoveAccess)) api.router.POST("/access/remove", LogRequestMiddleware(api.WorkerRemoveAccess))
api.router.POST("/project/create", LogRequestMiddleware(api.ProjectCreate)) api.router.POST("/projectChange/create", LogRequestMiddleware(api.ProjectCreate))
api.router.GET("/project/get/:id", LogRequestMiddleware(api.ProjectGet)) api.router.GET("/projectChange/get/:id", LogRequestMiddleware(api.ProjectGet))
api.router.POST("/project/update/:id", LogRequestMiddleware(api.ProjectUpdate)) api.router.POST("/projectChange/update/:id", LogRequestMiddleware(api.ProjectUpdate))
api.router.GET("/project/list", LogRequestMiddleware(api.ProjectGetAllProjects)) api.router.GET("/projectChange/list", LogRequestMiddleware(api.ProjectGetAllProjects))
api.router.GET("/project/monitoring-between/:id", LogRequestMiddleware(api.GetSnapshotsBetween)) api.router.GET("/projectChange/monitoring-between/:id", LogRequestMiddleware(api.GetSnapshotsBetween))
api.router.GET("/project/monitoring/:id", LogRequestMiddleware(api.GetNSnapshots)) api.router.GET("/projectChange/monitoring/:id", LogRequestMiddleware(api.GetNSnapshots))
api.router.GET("/project/assignees/:id", LogRequestMiddleware(api.ProjectGetAssigneeStats)) api.router.GET("/projectChange/assignees/:id", LogRequestMiddleware(api.ProjectGetAssigneeStats))
api.router.GET("/project/requests/:id", LogRequestMiddleware(api.ProjectGetAccessRequests)) api.router.GET("/projectChange/requests/:id", LogRequestMiddleware(api.ProjectGetAccessRequests))
api.router.GET("/project/request_access/:id", LogRequestMiddleware(api.WorkerRequestAccess)) api.router.GET("/projectChange/request_access/:id", LogRequestMiddleware(api.WorkerRequestAccess))
api.router.POST("/task/create", LogRequestMiddleware(api.TaskCreate)) api.router.POST("/task/create", LogRequestMiddleware(api.TaskCreate))
api.router.GET("/task/get/:project", LogRequestMiddleware(api.TaskGetFromProject)) api.router.GET("/task/get/:projectChange", LogRequestMiddleware(api.TaskGetFromProject))
api.router.GET("/task/get", LogRequestMiddleware(api.TaskGet)) api.router.GET("/task/get", LogRequestMiddleware(api.TaskGet))
api.router.POST("/task/release", LogRequestMiddleware(api.TaskRelease)) api.router.POST("/task/release", LogRequestMiddleware(api.TaskRelease))

View File

@ -16,6 +16,7 @@ type CreateProjectRequest struct {
Motd string `json:"motd"` Motd string `json:"motd"`
Public bool `json:"public"` Public bool `json:"public"`
Hidden bool `json:"hidden"` Hidden bool `json:"hidden"`
Chain int64 `json:"chain"`
} }
type UpdateProjectRequest struct { type UpdateProjectRequest struct {
@ -26,6 +27,7 @@ type UpdateProjectRequest struct {
Motd string `json:"motd"` Motd string `json:"motd"`
Public bool `json:"public"` Public bool `json:"public"`
Hidden bool `json:"hidden"` Hidden bool `json:"hidden"`
Chain int64 `json:"chain"`
} }
type UpdateProjectResponse struct { type UpdateProjectResponse struct {
@ -42,7 +44,7 @@ type CreateProjectResponse struct {
type GetProjectResponse struct { type GetProjectResponse struct {
Ok bool `json:"ok"` Ok bool `json:"ok"`
Message string `json:"message,omitempty"` Message string `json:"message,omitempty"`
Project *storage.Project `json:"project,omitempty"` Project *storage.Project `json:"projectChange,omitempty"`
} }
type GetAllProjectsResponse struct { type GetAllProjectsResponse struct {
@ -91,28 +93,29 @@ func (api *WebAPI) ProjectCreate(r *Request) {
Motd: createReq.Motd, Motd: createReq.Motd,
Public: createReq.Public, Public: createReq.Public,
Hidden: createReq.Hidden, Hidden: createReq.Hidden,
Chain: createReq.Chain,
} }
if !isValidProject(project) { if !isValidProject(project) {
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
"project": project, "projectChange": project,
}).Warn("Invalid project") }).Warn("Invalid projectChange")
r.Json(CreateProjectResponse{ r.Json(CreateProjectResponse{
Ok: false, Ok: false,
Message: "Invalid project", Message: "Invalid projectChange",
}, 400) }, 400)
return return
} }
if !isProjectCreationAuthorized(project, manager) { if !isProjectCreationAuthorized(project, manager) {
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
"project": project, "projectChange": project,
}).Warn("Unauthorized project creation") }).Warn("Unauthorized projectChange creation")
r.Json(CreateProjectResponse{ r.Json(CreateProjectResponse{
Ok: false, Ok: false,
Message: "You are not permitted to create a project with this configuration", Message: "You are not permitted to create a projectChange with this configuration",
}, 400) }, 400)
return return
} }
@ -130,8 +133,8 @@ func (api *WebAPI) ProjectCreate(r *Request) {
Id: id, Id: id,
}) })
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
"project": project, "projectChange": project,
}).Debug("Created project") }).Debug("Created projectChange")
} }
func (api *WebAPI) ProjectUpdate(r *Request) { func (api *WebAPI) ProjectUpdate(r *Request) {
@ -140,7 +143,7 @@ func (api *WebAPI) ProjectUpdate(r *Request) {
if err != nil || id <= 0 { if err != nil || id <= 0 {
r.Json(CreateProjectResponse{ r.Json(CreateProjectResponse{
Ok: false, Ok: false,
Message: "Invalid project id", Message: "Invalid projectChange id",
}, 400) }, 400)
return return
} }
@ -163,6 +166,7 @@ func (api *WebAPI) ProjectUpdate(r *Request) {
Motd: updateReq.Motd, Motd: updateReq.Motd,
Public: updateReq.Public, Public: updateReq.Public,
Hidden: updateReq.Hidden, Hidden: updateReq.Hidden,
Chain: updateReq.Chain,
} }
if isValidProject(project) { if isValidProject(project) {
@ -175,26 +179,26 @@ func (api *WebAPI) ProjectUpdate(r *Request) {
}, 500) }, 500)
logrus.WithError(err).WithFields(logrus.Fields{ logrus.WithError(err).WithFields(logrus.Fields{
"project": project, "projectChange": project,
}).Warn("Error during project update") }).Warn("Error during projectChange update")
} else { } else {
r.OkJson(UpdateProjectResponse{ r.OkJson(UpdateProjectResponse{
Ok: true, Ok: true,
}) })
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
"project": project, "projectChange": project,
}).Debug("Updated project") }).Debug("Updated projectChange")
} }
} else { } else {
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
"project": project, "projectChange": project,
}).Warn("Invalid project") }).Warn("Invalid projectChange")
r.Json(CreateProjectResponse{ r.Json(CreateProjectResponse{
Ok: false, Ok: false,
Message: "Invalid project", Message: "Invalid projectChange",
}, 400) }, 400)
} }
} }
@ -349,7 +353,7 @@ func (api *WebAPI) WorkerRequestAccess(r *Request) {
r.Json(WorkerRequestAccessResponse{ r.Json(WorkerRequestAccessResponse{
Ok: false, Ok: false,
Message: "Project is public, you already have " + Message: "Project is public, you already have " +
"an active request or you already have access to this project", "an active request or you already have access to this projectChange",
}, 400) }, 400)
} }
} }

View File

@ -14,7 +14,7 @@ import (
) )
type CreateTaskRequest struct { type CreateTaskRequest struct {
Project int64 `json:"project"` Project int64 `json:"projectChange"`
MaxRetries int64 `json:"max_retries"` MaxRetries int64 `json:"max_retries"`
Recipe string `json:"recipe"` Recipe string `json:"recipe"`
Priority int64 `json:"priority"` Priority int64 `json:"priority"`
@ -123,7 +123,7 @@ func (api *WebAPI) TaskGetFromProject(r *Request) {
return return
} }
project, err := strconv.ParseInt(r.Ctx.UserValue("project").(string), 10, 64) project, err := strconv.ParseInt(r.Ctx.UserValue("projectChange").(string), 10, 64)
handleErr(err, r) handleErr(err, r)
task := api.Database.GetTaskFromProject(worker, project) task := api.Database.GetTaskFromProject(worker, project)
@ -254,12 +254,7 @@ func (api *WebAPI) TaskRelease(r *Request) {
} }
if !res { if !res {
response.Message = "Could not find a task with the specified Id assigned to this workerId" response.Message = "Task was not marked as closed"
logrus.WithFields(logrus.Fields{
"releaseTaskRequest": req,
"taskUpdated": res,
}).Warn("Release task: NOT FOUND")
} else { } else {
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{

View File

@ -146,7 +146,7 @@ func (api *WebAPI) WorkerGrantAccess(r *Request) {
} else { } else {
r.OkJson(WorkerAccessResponse{ r.OkJson(WorkerAccessResponse{
Ok: false, Ok: false,
Message: "Worker already has access to this project", Message: "Worker already has access to this projectChange",
}) })
} }
} }
@ -171,7 +171,7 @@ func (api *WebAPI) WorkerRemoveAccess(r *Request) {
} else { } else {
r.OkJson(WorkerAccessResponse{ r.OkJson(WorkerAccessResponse{
Ok: false, Ok: false,
Message: "Worker did not have access to this project", Message: "Worker did not have access to this projectChange",
}) })
} }
} }

View File

@ -18,6 +18,7 @@ CREATE TABLE project
id SERIAL PRIMARY KEY NOT NULL, id SERIAL PRIMARY KEY NOT NULL,
priority INTEGER DEFAULT 0 NOT NULL, priority INTEGER DEFAULT 0 NOT NULL,
closed_task_count INT DEFAULT 0 NOT NULL, closed_task_count INT DEFAULT 0 NOT NULL,
chain INT DEFAULT NULL REFERENCES project (id),
public boolean NOT NULL, public boolean NOT NULL,
hidden boolean NOT NULL, hidden boolean NOT NULL,
name TEXT UNIQUE NOT NULL, name TEXT UNIQUE NOT NULL,
@ -101,9 +102,18 @@ CREATE TABLE worker_requests_access_to_project
CREATE OR REPLACE FUNCTION on_task_delete_proc() RETURNS TRIGGER AS CREATE OR REPLACE FUNCTION on_task_delete_proc() RETURNS TRIGGER AS
$$ $$
DECLARE
chain INTEGER;
BEGIN BEGIN
UPDATE project SET closed_task_count=closed_task_count + 1 WHERE id = OLD.project; UPDATE project SET closed_task_count=closed_task_count + 1 WHERE id = OLD.project returning project.chain into chain;
UPDATE worker SET closed_task_count=closed_task_count + 1 WHERE id = OLD.assignee; UPDATE worker SET closed_task_count=closed_task_count + 1 WHERE id = OLD.assignee;
IF chain != 0 THEN
INSERT into task (hash64, project, assignee, max_assign_time, assign_time, verification_count,
priority, retries, max_retries, status, recipe)
VALUES (old.hash64, chain, NULL, old.max_assign_time, NULL,
old.verification_count, old.priority, 0, old.max_retries, 1,
old.recipe);
end if;
RETURN OLD; RETURN OLD;
END; END;
$$ LANGUAGE 'plpgsql'; $$ LANGUAGE 'plpgsql';

View File

@ -131,7 +131,7 @@ func (database *Database) ManagerHasRoleOn(manager *Manager, projectId int64) Ma
db := database.getDB() db := database.getDB()
row := db.QueryRow(`SELECT role FROM manager_has_role_on_project row := db.QueryRow(`SELECT role FROM manager_has_role_on_project
WHERE project=$1 AND manager=$2`, projectId, manager.Id) WHERE projectChange=$1 AND manager=$2`, projectId, manager.Id)
var role ManagerRole var role ManagerRole
err := row.Scan(&role) err := row.Scan(&role)

View File

@ -22,19 +22,19 @@ func (database *Database) MakeProjectSnapshots() {
insertRes, err := db.Exec(` insertRes, err := db.Exec(`
INSERT INTO project_monitoring_snapshot INSERT INTO project_monitoring_snapshot
(project, new_task_count, failed_task_count, closed_task_count, worker_access_count, (projectChange, new_task_count, failed_task_count, closed_task_count, worker_access_count,
awaiting_verification_task_count, timestamp) awaiting_verification_task_count, timestamp)
SELECT id, SELECT id,
(SELECT COUNT(*) FROM task (SELECT COUNT(*) FROM task
LEFT JOIN worker_verifies_task wvt on task.id = wvt.task LEFT JOIN worker_verifies_task wvt on task.id = wvt.task
WHERE task.project = project.id AND status = 1 AND wvt.task IS NULL), WHERE task.projectChange = projectChange.id AND status = 1 AND wvt.task IS NULL),
(SELECT COUNT(*) FROM task WHERE task.project = project.id AND status = 2), (SELECT COUNT(*) FROM task WHERE task.projectChange = projectChange.id AND status = 2),
closed_task_count, closed_task_count,
(SELECT COUNT(*) FROM worker_has_access_to_project wa WHERE wa.project = project.id), (SELECT COUNT(*) FROM worker_has_access_to_project wa WHERE wa.projectChange = projectChange.id),
(SELECT COUNT(*) FROM worker_verifies_task INNER JOIN task t on worker_verifies_task.task = t.id (SELECT COUNT(*) FROM worker_verifies_task INNER JOIN task t on worker_verifies_task.task = t.id
WHERE t.project = project.id), WHERE t.projectChange = projectChange.id),
extract(epoch from now() at time zone 'utc') extract(epoch from now() at time zone 'utc')
FROM project`) FROM projectChange`)
handleErr(err) handleErr(err)
inserted, _ := insertRes.RowsAffected() inserted, _ := insertRes.RowsAffected()
@ -47,7 +47,7 @@ func (database *Database) MakeProjectSnapshots() {
"took": time.Now().Sub(startTime), "took": time.Now().Sub(startTime),
"add": inserted, "add": inserted,
"remove": deleted, "remove": deleted,
}).Trace("Took project monitoring snapshot") }).Trace("Took projectChange monitoring snapshot")
} }
func (database *Database) GetMonitoringSnapshotsBetween(pid int64, from int, to int) (ss *[]ProjectMonitoringSnapshot) { func (database *Database) GetMonitoringSnapshotsBetween(pid int64, from int, to int) (ss *[]ProjectMonitoringSnapshot) {
@ -58,7 +58,7 @@ func (database *Database) GetMonitoringSnapshotsBetween(pid int64, from int, to
rows, err := db.Query(`SELECT new_task_count, failed_task_count, closed_task_count, rows, err := db.Query(`SELECT new_task_count, failed_task_count, closed_task_count,
worker_access_count, awaiting_verification_task_count, timestamp FROM project_monitoring_snapshot worker_access_count, awaiting_verification_task_count, timestamp FROM project_monitoring_snapshot
WHERE project=$1 AND timestamp BETWEEN $2 AND $3 ORDER BY TIMESTAMP DESC `, pid, from, to) WHERE projectChange=$1 AND timestamp BETWEEN $2 AND $3 ORDER BY TIMESTAMP DESC `, pid, from, to)
handleErr(err) handleErr(err)
for rows.Next() { for rows.Next() {
@ -89,7 +89,7 @@ func (database *Database) GetNMonitoringSnapshots(pid int64, count int) (ss *[]P
rows, err := db.Query(`SELECT new_task_count, failed_task_count, closed_task_count, rows, err := db.Query(`SELECT new_task_count, failed_task_count, closed_task_count,
worker_access_count, awaiting_verification_task_count, timestamp FROM project_monitoring_snapshot worker_access_count, awaiting_verification_task_count, timestamp FROM project_monitoring_snapshot
WHERE project=$1 ORDER BY TIMESTAMP DESC LIMIT $2`, pid, count) WHERE projectChange=$1 ORDER BY TIMESTAMP DESC LIMIT $2`, pid, count)
handleErr(err) handleErr(err)
for rows.Next() { for rows.Next() {

View File

@ -16,6 +16,7 @@ type Project struct {
Motd string `json:"motd"` Motd string `json:"motd"`
Public bool `json:"public"` Public bool `json:"public"`
Hidden bool `json:"hidden"` Hidden bool `json:"hidden"`
Chain int64 `json:"chain"`
} }
type AssignedTasks struct { type AssignedTasks struct {
@ -25,34 +26,29 @@ type AssignedTasks struct {
func (database *Database) SaveProject(project *Project) (int64, error) { func (database *Database) SaveProject(project *Project) (int64, error) {
db := database.getDB() db := database.getDB()
id, projectErr := saveProject(project, db)
return id, projectErr row := db.QueryRow(`INSERT INTO projectChange (name, git_repo, clone_url, version, priority,
} motd, public, hidden, chain)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,NULLIF($9, 0)) RETURNING id`,
func saveProject(project *Project, db *sql.DB) (int64, error) {
row := db.QueryRow(`INSERT INTO project (name, git_repo, clone_url, version, priority, motd, public, hidden)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8) RETURNING id`,
project.Name, project.GitRepo, project.CloneUrl, project.Version, project.Priority, project.Motd, project.Name, project.GitRepo, project.CloneUrl, project.Version, project.Priority, project.Motd,
project.Public, project.Hidden) project.Public, project.Hidden, project.Chain)
var id int64 var id int64
err := row.Scan(&id) err := row.Scan(&id)
if err != nil { if err != nil {
logrus.WithError(err).WithFields(logrus.Fields{ logrus.WithError(err).WithFields(logrus.Fields{
"project": project, "projectChange": project,
}).Warn("Database.saveProject INSERT project ERROR") }).Warn("Database.saveProject INSERT projectChange ERROR")
return -1, err return -1, err
} }
project.Id = id project.Id = id
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
"id": id, "id": id,
"project": project, "projectChange": project,
}).Trace("Database.saveProject INSERT project") }).Trace("Database.saveProject INSERT projectChange")
return id, nil return id, nil
} }
@ -60,52 +56,47 @@ func saveProject(project *Project, db *sql.DB) (int64, error) {
func (database *Database) GetProject(id int64) *Project { func (database *Database) GetProject(id int64) *Project {
db := database.getDB() db := database.getDB()
project := getProject(id, db) row := db.QueryRow(`SELECT id, priority, name, clone_url, git_repo, version,
return project motd, public, hidden, COALESCE(chain, 0)
} FROM projectChange WHERE id=$1`, id)
func getProject(id int64, db *sql.DB) *Project {
row := db.QueryRow(`SELECT id, priority, name, clone_url, git_repo, version, motd, public, hidden
FROM project WHERE id=$1`, id)
project, err := scanProject(row) project, err := scanProject(row)
if err != nil { if err != nil {
logrus.WithError(err).WithFields(logrus.Fields{ logrus.WithError(err).WithFields(logrus.Fields{
"id": id, "id": id,
}).Warn("Database.getProject SELECT project NOT FOUND") }).Warn("Database.getProject SELECT projectChange NOT FOUND")
return nil return nil
} }
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
"id": id, "id": id,
"project": project, "projectChange": project,
}).Trace("Database.saveProject SELECT project") }).Trace("Database.saveProject SELECT projectChange")
return project return project
} }
func scanProject(row *sql.Row) (*Project, error) { func scanProject(row *sql.Row) (*Project, error) {
project := &Project{} p := &Project{}
err := row.Scan(&project.Id, &project.Priority, &project.Name, &project.CloneUrl, err := row.Scan(&p.Id, &p.Priority, &p.Name, &p.CloneUrl, &p.GitRepo, &p.Version,
&project.GitRepo, &project.Version, &project.Motd, &project.Public, &project.Hidden) &p.Motd, &p.Public, &p.Hidden, &p.Chain)
return project, err return p, err
} }
func (database *Database) GetProjectWithRepoName(repoName string) *Project { func (database *Database) GetProjectWithRepoName(repoName string) *Project {
db := database.getDB() db := database.getDB()
row := db.QueryRow(`SELECT id, priority, name, clone_url, git_repo, version, motd, public, hidden row := db.QueryRow(`SELECT id, priority, name, clone_url, git_repo, version,
FROM project WHERE LOWER(git_repo)=$1`, motd, public, hidden, COALESCE(chain, 0) FROM projectChange WHERE LOWER(git_repo)=$1`,
strings.ToLower(repoName)) strings.ToLower(repoName))
project, err := scanProject(row) project, err := scanProject(row)
if err != nil { if err != nil {
logrus.WithError(err).WithFields(logrus.Fields{ logrus.WithError(err).WithFields(logrus.Fields{
"repoName": repoName, "repoName": repoName,
}).Warn("Database.getProjectWithRepoName SELECT project NOT FOUND") }).Warn("Database.getProjectWithRepoName SELECT projectChange NOT FOUND")
return nil return nil
} }
@ -116,10 +107,12 @@ func (database *Database) UpdateProject(project *Project) error {
db := database.getDB() db := database.getDB()
res, err := db.Exec(`UPDATE project res, err := db.Exec(`UPDATE projectChange
SET (priority, name, clone_url, git_repo, version, motd, public, hidden) = ($1,$2,$3,$4,$5,$6,$7,$8) WHERE id=$9`, SET (priority, name, clone_url, git_repo, version, motd, public, hidden, chain) =
($1,$2,$3,$4,$5,$6,$7,$8,NULLIF($9, 0))
WHERE id=$10`,
project.Priority, project.Name, project.CloneUrl, project.GitRepo, project.Version, project.Motd, project.Priority, project.Name, project.CloneUrl, project.GitRepo, project.Version, project.Motd,
project.Public, project.Hidden, project.Id) project.Public, project.Hidden, project.Chain, project.Id)
if err != nil { if err != nil {
return err return err
} }
@ -127,9 +120,9 @@ func (database *Database) UpdateProject(project *Project) error {
rowsAffected, _ := res.RowsAffected() rowsAffected, _ := res.RowsAffected()
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
"project": project, "projectChange": project,
"rowsAffected": rowsAffected, "rowsAffected": rowsAffected,
}).Trace("Database.updateProject UPDATE project") }).Trace("Database.updateProject UPDATE projectChange")
return nil return nil
} }
@ -142,15 +135,15 @@ func (database Database) GetAllProjects(workerId int64) *[]Project {
var err error var err error
if workerId == 0 { if workerId == 0 {
rows, err = db.Query(`SELECT rows, err = db.Query(`SELECT
Id, priority, name, clone_url, git_repo, version, motd, public, hidden Id, priority, name, clone_url, git_repo, version, motd, public, hidden, COALESCE(chain,0)
FROM project FROM projectChange
WHERE NOT hidden WHERE NOT hidden
ORDER BY name`) ORDER BY name`)
} else { } else {
rows, err = db.Query(`SELECT rows, err = db.Query(`SELECT
Id, priority, name, clone_url, git_repo, version, motd, public, hidden Id, priority, name, clone_url, git_repo, version, motd, public, hidden, COALESCE(chain,0)
FROM project FROM projectChange
LEFT JOIN worker_has_access_to_project whatp ON whatp.project = id LEFT JOIN worker_has_access_to_project whatp ON whatp.projectChange = id
WHERE NOT hidden OR whatp.worker = $1 WHERE NOT hidden OR whatp.worker = $1
ORDER BY name`, workerId) ORDER BY name`, workerId)
} }
@ -159,13 +152,14 @@ func (database Database) GetAllProjects(workerId int64) *[]Project {
for rows.Next() { for rows.Next() {
p := Project{} p := Project{}
err := rows.Scan(&p.Id, &p.Priority, &p.Name, &p.CloneUrl, err := rows.Scan(&p.Id, &p.Priority, &p.Name, &p.CloneUrl,
&p.GitRepo, &p.Version, &p.Motd, &p.Public, &p.Hidden) &p.GitRepo, &p.Version, &p.Motd, &p.Public, &p.Hidden,
&p.Chain)
handleErr(err) handleErr(err)
projects = append(projects, p) projects = append(projects, p)
} }
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
"projects": projects, "projects": len(projects),
}).Trace("Get all projects stats") }).Trace("Get all projects stats")
return &projects return &projects
@ -177,7 +171,7 @@ func (database *Database) GetAssigneeStats(pid int64, count int64) *[]AssignedTa
assignees := make([]AssignedTasks, 0) assignees := make([]AssignedTasks, 0)
rows, err := db.Query(`SELECT worker.alias, COUNT(*) as wc FROM TASK rows, err := db.Query(`SELECT worker.alias, COUNT(*) as wc FROM TASK
LEFT JOIN worker ON TASK.assignee = worker.id WHERE project=$1 LEFT JOIN worker ON TASK.assignee = worker.id WHERE projectChange=$1
GROUP BY worker.id ORDER BY wc LIMIT $2`, pid, count) GROUP BY worker.id ORDER BY wc LIMIT $2`, pid, count)
handleErr(err) handleErr(err)

View File

@ -9,7 +9,7 @@ import (
type Task struct { type Task struct {
Id int64 `json:"id"` Id int64 `json:"id"`
Priority int64 `json:"priority"` Priority int64 `json:"priority"`
Project *Project `json:"project"` Project *Project `json:"projectChange"`
Assignee int64 `json:"assignee"` Assignee int64 `json:"assignee"`
Retries int64 `json:"retries"` Retries int64 `json:"retries"`
MaxRetries int64 `json:"max_retries"` MaxRetries int64 `json:"max_retries"`
@ -41,7 +41,7 @@ func (database *Database) SaveTask(task *Task, project int64, hash64 int64) erro
//TODO: For some reason it refuses to insert the 64-bit value unless I do that... //TODO: For some reason it refuses to insert the 64-bit value unless I do that...
res, err := db.Exec(fmt.Sprintf(` res, err := db.Exec(fmt.Sprintf(`
INSERT INTO task (project, max_retries, recipe, priority, max_assign_time, hash64,verification_count) INSERT INTO task (projectChange, max_retries, recipe, priority, max_assign_time, hash64,verification_count)
VALUES ($1,$2,$3,$4,$5,NULLIF(%d, 0),$6)`, hash64), VALUES ($1,$2,$3,$4,$5,NULLIF(%d, 0),$6)`, hash64),
project, task.MaxRetries, task.Recipe, task.Priority, task.MaxAssignTime, task.VerificationCount) project, task.MaxRetries, task.Recipe, task.Priority, task.MaxAssignTime, task.VerificationCount)
if err != nil { if err != nil {
@ -73,14 +73,14 @@ 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 projectChange project on task.projectChange = project.id
LEFT JOIN worker_verifies_task wvt on task.id = wvt.task AND wvt.worker=$1 LEFT JOIN worker_verifies_task wvt on task.id = wvt.task AND wvt.worker=$1
WHERE assignee IS NULL AND task.status=1 WHERE assignee IS NULL AND task.status=1
AND (p.public OR EXISTS ( AND (project.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.projectChange=project.id
)) ))
AND wvt.task IS NULL AND wvt.task IS NULL
ORDER BY p.priority DESC, task.priority DESC ORDER BY project.priority DESC, task.priority DESC
LIMIT 1 LIMIT 1
) )
RETURNING id`, worker.Id) RETURNING id`, worker.Id)
@ -107,10 +107,10 @@ 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 task.id, task.priority, task.project, assignee, retries, max_retries, SELECT task.id, task.priority, task.projectChange, assignee, retries, max_retries,
status, recipe, max_assign_time, assign_time, verification_count, p.priority, p.name, status, recipe, max_assign_time, assign_time, verification_count, project.priority, project.name,
p.clone_url, p.git_repo, p.version, p.motd, p.public FROM task project.clone_url, project.git_repo, project.version, project.motd, project.public, COALESCE(project.chain,0) FROM task
INNER JOIN project p ON task.project = p.id INNER JOIN projectChange project ON task.projectChange = project.id
WHERE task.id=$1`, id) WHERE task.id=$1`, id)
project := &Project{} project := &Project{}
task := &Task{} task := &Task{}
@ -119,7 +119,8 @@ func getTaskById(id int64, db *sql.DB) *Task {
err := row.Scan(&task.Id, &task.Priority, &project.Id, &task.Assignee, err := row.Scan(&task.Id, &task.Priority, &project.Id, &task.Assignee,
&task.Retries, &task.MaxRetries, &task.Status, &task.Recipe, &task.MaxAssignTime, &task.Retries, &task.MaxRetries, &task.Status, &task.Recipe, &task.MaxAssignTime,
&task.AssignTime, &task.VerificationCount, &project.Priority, &project.Name, &task.AssignTime, &task.VerificationCount, &project.Priority, &project.Name,
&project.CloneUrl, &project.GitRepo, &project.Version, &project.Motd, &project.Public) &project.CloneUrl, &project.GitRepo, &project.Version, &project.Motd, &project.Public,
&project.Chain)
handleErr(err) handleErr(err)
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
@ -175,11 +176,11 @@ 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 projectChange project on task.projectChange = project.id
LEFT JOIN worker_verifies_task wvt on task.id = wvt.task AND wvt.worker=$1 LEFT JOIN worker_verifies_task wvt on task.id = wvt.task AND wvt.worker=$1
WHERE assignee IS NULL AND p.id=$2 AND status=1 WHERE assignee IS NULL AND project.id=$2 AND status=1
AND (p.public OR EXISTS ( AND (project.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.projectChange=$2
)) ))
AND wvt.task IS NULL AND wvt.task IS NULL
ORDER BY task.priority DESC ORDER BY task.priority DESC

View File

@ -56,7 +56,7 @@ func (database *Database) GetWorker(id int64) *Worker {
func (database *Database) GrantAccess(workerId int64, projectId int64) bool { func (database *Database) GrantAccess(workerId int64, projectId int64) bool {
db := database.getDB() db := database.getDB()
res, err := db.Exec(`INSERT INTO worker_has_access_to_project (worker, project) VALUES ($1,$2) res, err := db.Exec(`INSERT INTO worker_has_access_to_project (worker, projectChange) VALUES ($1,$2)
ON CONFLICT DO NOTHING`, ON CONFLICT DO NOTHING`,
workerId, projectId) workerId, projectId)
if err != nil { if err != nil {
@ -81,7 +81,7 @@ func (database *Database) GrantAccess(workerId int64, projectId int64) bool {
func (database *Database) RemoveAccess(workerId int64, projectId int64) bool { func (database *Database) RemoveAccess(workerId int64, 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 projectChange=$2`,
workerId, projectId) workerId, projectId)
handleErr(err) handleErr(err)
@ -118,8 +118,8 @@ func (database *Database) SaveAccessRequest(worker *Worker, projectId int64) boo
db := database.getDB() db := database.getDB()
res, err := db.Exec(`INSERT INTO worker_requests_access_to_project res, err := db.Exec(`INSERT INTO worker_requests_access_to_project
SELECT $1, id FROM project WHERE id=$2 AND NOT project.public SELECT $1, id FROM projectChange WHERE id=$2 AND NOT projectChange.public
AND NOT EXISTS(SELECT * FROM worker_has_access_to_project WHERE worker=$1 AND project=$2)`, AND NOT EXISTS(SELECT * FROM worker_has_access_to_project WHERE worker=$1 AND projectChange=$2)`,
worker.Id, projectId) worker.Id, projectId)
if err != nil { if err != nil {
return false return false
@ -139,13 +139,13 @@ func (database *Database) AcceptAccessRequest(worker *Worker, projectId int64) b
db := database.getDB() db := database.getDB()
res, err := db.Exec(`DELETE FROM worker_requests_access_to_project res, err := db.Exec(`DELETE FROM worker_requests_access_to_project
WHERE worker=$1 AND project=$2`) WHERE worker=$1 AND projectChange=$2`)
handleErr(err) handleErr(err)
rowsAffected, _ := res.RowsAffected() rowsAffected, _ := res.RowsAffected()
if rowsAffected == 1 { if rowsAffected == 1 {
_, err := db.Exec(`INSERT INTO worker_has_access_to_project _, err := db.Exec(`INSERT INTO worker_has_access_to_project
(worker, project) VALUES ($1,$2)`, (worker, projectChange) VALUES ($1,$2)`,
worker.Id, projectId) worker.Id, projectId)
handleErr(err) handleErr(err)
} }
@ -162,7 +162,7 @@ func (database *Database) RejectAccessRequest(worker *Worker, projectId int64) b
db := database.getDB() db := database.getDB()
res, err := db.Exec(`DELETE FROM worker_requests_access_to_project res, err := db.Exec(`DELETE FROM worker_requests_access_to_project
WHERE worker=$1 AND project=$2`, worker.Id, projectId) WHERE worker=$1 AND projectChange=$2`, worker.Id, projectId)
handleErr(err) handleErr(err)
rowsAffected, _ := res.RowsAffected() rowsAffected, _ := res.RowsAffected()
@ -180,7 +180,7 @@ func (database *Database) GetAllAccessRequests(projectId int64) *[]Worker {
rows, err := db.Query(`SELECT id, alias, created FROM worker_requests_access_to_project rows, err := db.Query(`SELECT id, alias, created FROM worker_requests_access_to_project
INNER JOIN worker w on worker_requests_access_to_project.worker = w.id INNER JOIN worker w on worker_requests_access_to_project.worker = w.id
WHERE project=$1`, WHERE projectChange=$1`,
projectId) projectId)
handleErr(err) handleErr(err)

View File

@ -245,7 +245,7 @@ func TestUpdateProjectConstraintFail(t *testing.T) {
func createProject(req api.CreateProjectRequest) *api.CreateProjectResponse { func createProject(req api.CreateProjectRequest) *api.CreateProjectResponse {
r := Post("/project/create", req, nil) r := Post("/projectChange/create", req, nil)
var resp api.CreateProjectResponse var resp api.CreateProjectResponse
data, _ := ioutil.ReadAll(r.Body) data, _ := ioutil.ReadAll(r.Body)
@ -257,7 +257,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), nil) r := Get(fmt.Sprintf("/projectChange/get/%d", id), nil)
var getResp api.GetProjectResponse var getResp api.GetProjectResponse
data, _ := ioutil.ReadAll(r.Body) data, _ := ioutil.ReadAll(r.Body)
@ -269,7 +269,7 @@ func getProject(id int64) (*api.GetProjectResponse, *http.Response) {
func updateProject(request api.UpdateProjectRequest, pid int64) *api.UpdateProjectResponse { func updateProject(request api.UpdateProjectRequest, pid int64) *api.UpdateProjectResponse {
r := Post(fmt.Sprintf("/project/update/%d", pid), request, nil) r := Post(fmt.Sprintf("/projectChange/update/%d", pid), request, nil)
var resp api.UpdateProjectResponse var resp api.UpdateProjectResponse
data, _ := ioutil.ReadAll(r.Body) data, _ := ioutil.ReadAll(r.Body)

View File

@ -11,7 +11,7 @@ import (
func TestCreateTaskValid(t *testing.T) { func TestCreateTaskValid(t *testing.T) {
//Make sure there is always a project for id:1 //Make sure there is always a projectChange for id:1
createProject(api.CreateProjectRequest{ createProject(api.CreateProjectRequest{
Name: "Some Test name", Name: "Some Test name",
Version: "Test Version", Version: "Test Version",
@ -132,9 +132,9 @@ func TestCreateTaskInvalidRecipe(t *testing.T) {
func TestCreateGetTask(t *testing.T) { func TestCreateGetTask(t *testing.T) {
//Make sure there is always a project for id:1 //Make sure there is always a projectChange for id:1
resp := createProject(api.CreateProjectRequest{ resp := createProject(api.CreateProjectRequest{
Name: "My project", Name: "My projectChange",
Version: "1.0", Version: "1.0",
CloneUrl: "http://github.com/test/test", CloneUrl: "http://github.com/test/test",
GitRepo: "myrepo", GitRepo: "myrepo",
@ -639,6 +639,57 @@ func TestReleaseTaskFail(t *testing.T) {
} }
func TestTaskChain(t *testing.T) {
w := genWid()
p1 := createProject(api.CreateProjectRequest{
Name: "testtaskchain1",
Public: true,
GitRepo: "testtaskchain1",
CloneUrl: "testtaskchain1",
}).Id
p2 := createProject(api.CreateProjectRequest{
Name: "testtaskchain2",
Public: true,
GitRepo: "testtaskchain2",
CloneUrl: "testtaskchain2",
Chain: p1,
}).Id
createTask(api.CreateTaskRequest{
Project: p2,
Recipe: "###",
VerificationCount: 0,
}, w)
t1 := getTaskFromProject(p2, w).Task
releaseTask(api.ReleaseTaskRequest{
TaskId: t1.Id,
Result: storage.TR_OK,
}, w)
chained := getTaskFromProject(p1, w).Task
if chained.VerificationCount != t1.VerificationCount {
t.Error()
}
if chained.Recipe != t1.Recipe {
t.Error()
}
if chained.MaxRetries != t1.MaxRetries {
t.Error()
}
if chained.Priority != t1.Priority {
t.Error()
}
if chained.Status != storage.NEW {
t.Error()
}
}
func createTask(request api.CreateTaskRequest, worker *storage.Worker) *api.CreateTaskResponse { func createTask(request api.CreateTaskRequest, worker *storage.Worker) *api.CreateTaskResponse {
r := Post("/task/create", request, worker) r := Post("/task/create", request, worker)

View File

@ -6,7 +6,6 @@ import (
"crypto/hmac" "crypto/hmac"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"fmt"
"github.com/simon987/task_tracker/config" "github.com/simon987/task_tracker/config"
"github.com/simon987/task_tracker/storage" "github.com/simon987/task_tracker/storage"
"io" "io"
@ -51,12 +50,10 @@ func Get(path string, worker *storage.Worker) *http.Response {
if worker != nil { if worker != nil {
fmt.Println(worker.Secret)
mac := hmac.New(crypto.SHA256.New, worker.Secret) mac := hmac.New(crypto.SHA256.New, worker.Secret)
mac.Write([]byte(path)) mac.Write([]byte(path))
sig := hex.EncodeToString(mac.Sum(nil)) sig := hex.EncodeToString(mac.Sum(nil))
fmt.Println(strconv.FormatInt(worker.Id, 10))
req.Header.Add("X-Worker-Id", strconv.FormatInt(worker.Id, 10)) req.Header.Add("X-Worker-Id", strconv.FormatInt(worker.Id, 10))
req.Header.Add("X-Signature", sig) req.Header.Add("X-Signature", sig)
} }

View File

@ -18,6 +18,7 @@ CREATE TABLE project
id SERIAL PRIMARY KEY NOT NULL, id SERIAL PRIMARY KEY NOT NULL,
priority INTEGER DEFAULT 0 NOT NULL, priority INTEGER DEFAULT 0 NOT NULL,
closed_task_count INT DEFAULT 0 NOT NULL, closed_task_count INT DEFAULT 0 NOT NULL,
chain INT DEFAULT NULL REFERENCES project (id),
public boolean NOT NULL, public boolean NOT NULL,
hidden boolean NOT NULL, hidden boolean NOT NULL,
name TEXT UNIQUE NOT NULL, name TEXT UNIQUE NOT NULL,
@ -101,9 +102,18 @@ CREATE TABLE worker_requests_access_to_project
CREATE OR REPLACE FUNCTION on_task_delete_proc() RETURNS TRIGGER AS CREATE OR REPLACE FUNCTION on_task_delete_proc() RETURNS TRIGGER AS
$$ $$
DECLARE
chain INTEGER;
BEGIN BEGIN
UPDATE project SET closed_task_count=closed_task_count + 1 WHERE id = OLD.project; UPDATE project SET closed_task_count=closed_task_count + 1 WHERE id = OLD.project returning project.chain into chain;
UPDATE worker SET closed_task_count=closed_task_count + 1 WHERE id = OLD.assignee; UPDATE worker SET closed_task_count=closed_task_count + 1 WHERE id = OLD.assignee;
IF chain != 0 THEN
INSERT into task (hash64, project, assignee, max_assign_time, assign_time, verification_count,
priority, retries, max_retries, status, recipe)
VALUES (old.hash64, chain, NULL, old.max_assign_time, NULL,
old.verification_count, old.priority, 0, old.max_retries, 1,
old.recipe);
end if;
RETURN OLD; RETURN OLD;
END; END;
$$ LANGUAGE 'plpgsql'; $$ LANGUAGE 'plpgsql';

View File

@ -1,7 +1,7 @@
import {AppPage} from './app.po'; import {AppPage} from './app.po';
import {browser, logging} from 'protractor'; import {browser, logging} from 'protractor';
describe('workspace-project App', () => { describe('workspace-projectChange App', () => {
let page: AppPage; let page: AppPage;
beforeEach(() => { beforeEach(() => {

View File

@ -49,6 +49,7 @@ import {AccountDetailsComponent} from './account-details/account-details.compone
import {WorkerDashboardComponent} from './worker-dashboard/worker-dashboard.component'; import {WorkerDashboardComponent} from './worker-dashboard/worker-dashboard.component';
import {ProjectPermsComponent} from './project-perms/project-perms.component'; import {ProjectPermsComponent} from './project-perms/project-perms.component';
import {ManagerListComponent} from './manager-list/manager-list.component'; import {ManagerListComponent} from './manager-list/manager-list.component';
import {ProjectSelectComponent} from './project-select/project-select.component';
export function createTranslateLoader(http: HttpClient) { export function createTranslateLoader(http: HttpClient) {
@ -70,6 +71,7 @@ export function createTranslateLoader(http: HttpClient) {
WorkerDashboardComponent, WorkerDashboardComponent,
ProjectPermsComponent, ProjectPermsComponent,
ManagerListComponent, ManagerListComponent,
ProjectSelectComponent,
], ],
imports: [ imports: [
BrowserModule, BrowserModule,

View File

@ -1,36 +1,37 @@
<div class="container"> <div class="container">
<mat-card class="mat-elevation-z8"> <mat-card class="mat-elevation-z8">
<mat-card-title>{{"project.create_title" | translate}}</mat-card-title> <mat-card-title>{{"project.create_title" | translate}}</mat-card-title>
<mat-card-subtitle>{{"project.create_subtitle" | translate}}</mat-card-subtitle> <mat-card-subtitle>{{"project.create_subtitle" | translate}}</mat-card-subtitle>
<mat-card-content> <mat-card-content>
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>{{"project.name" | translate}}</mat-label> <mat-label>{{"project.name" | translate}}</mat-label>
<input type="text" matInput [(ngModel)]="project.name" [placeholder]="'project.name' | translate"> <input type="text" matInput [(ngModel)]="project.name" [placeholder]="'project.name' | translate">
</mat-form-field> </mat-form-field>
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>{{ "project.clone_url" | translate}}</mat-label> <mat-label>{{ "project.clone_url" | translate}}</mat-label>
<input type="text" matInput [(ngModel)]="project.clone_url" (change)="cloneUrlChange()" <input type="text" matInput [(ngModel)]="project.clone_url" (change)="cloneUrlChange()"
[placeholder]="'project.clone_url_placeholder' | translate"> [placeholder]="'project.clone_url_placeholder' | translate">
</mat-form-field> </mat-form-field>
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>{{ "project.git_repo" | translate }}</mat-label> <mat-label>{{ "project.git_repo" | translate }}</mat-label>
<input type="text" matInput [(ngModel)]="project.git_repo" <input type="text" matInput [(ngModel)]="project.git_repo"
[placeholder]="'project.git_repo_placeholder' | translate"> [placeholder]="'project.git_repo_placeholder' | translate">
<mat-hint align="start"> <mat-hint align="start">
{{"project.git_repo_hint" | translate}} {{"project.git_repo_hint" | translate}}
</mat-hint> </mat-hint>
</mat-form-field> </mat-form-field>
<project-select [(project)]="selectedProject"></project-select>
<mat-checkbox [(ngModel)]="project.public" <mat-checkbox [(ngModel)]="project.public"
[disabled]="!authService.logged || !authService.account.tracker_admin" [disabled]="!authService.logged || !authService.account.tracker_admin"
style="padding-top: 1em"> style="padding-top: 1em">
{{"project.public" | translate}}</mat-checkbox> {{"project.public" | translate}}</mat-checkbox>
</mat-card-content> </mat-card-content>
<mat-card-actions> <mat-card-actions>
<button mat-raised-button color="primary" (click)="onSubmit()">{{'project.create' | translate}}</button> <button mat-raised-button color="primary" (click)="onSubmit()">{{'project.create' | translate}}</button>
</mat-card-actions> </mat-card-actions>
</mat-card> </mat-card>
</div> </div>

View File

@ -14,6 +14,7 @@ import {AuthService} from "../auth.service";
export class CreateProjectComponent implements OnInit { export class CreateProjectComponent implements OnInit {
project = <Project>{}; project = <Project>{};
selectedProject: Project = null;
constructor(private apiService: ApiService, constructor(private apiService: ApiService,
private messengerService: MessengerService, private messengerService: MessengerService,
@ -33,6 +34,8 @@ export class CreateProjectComponent implements OnInit {
} }
onSubmit() { onSubmit() {
this.project.chain = this.selectedProject ? this.selectedProject.id : 0;
this.apiService.createProject(this.project).subscribe( this.apiService.createProject(this.project).subscribe(
data => { data => {
this.router.navigateByUrl("/project/" + data["id"]); this.router.navigateByUrl("/project/" + data["id"]);

View File

@ -1,5 +1,10 @@
<div class="container"> <div class="container">
<mat-card class="table-container"> <mat-card class="table-container">
<button mat-raised-button style="float: right"
[title]="'dashboard.refresh' | translate"
(click)="refresh()">
<mat-icon>refresh</mat-icon>
</button>
<mat-card-header> <mat-card-header>
<mat-card-title>{{"logs.title" | translate}}</mat-card-title> <mat-card-title>{{"logs.title" | translate}}</mat-card-title>
<mat-card-subtitle>{{"logs.subtitle" | translate}}</mat-card-subtitle> <mat-card-subtitle>{{"logs.subtitle" | translate}}</mat-card-subtitle>
@ -18,11 +23,6 @@
<mat-button-toggle value="7">{{"logs.trace" | translate}}</mat-button-toggle> <mat-button-toggle value="7">{{"logs.trace" | translate}}</mat-button-toggle>
</mat-button-toggle-group> </mat-button-toggle-group>
<button mat-raised-button style="float: right"
[title]="'dashboard.refresh' | translate"
(click)="refresh()">
<mat-icon>refresh</mat-icon>
</button>
<div class="mat-elevation-z8"> <div class="mat-elevation-z8">
<mat-table [dataSource]="data" matSort matSortActive="timestamp" <mat-table [dataSource]="data" matSort matSortActive="timestamp"

View File

@ -7,4 +7,5 @@ export interface Project {
git_repo: string; git_repo: string;
version: string; version: string;
public: boolean; public: boolean;
chain: number;
} }

View File

@ -1,14 +1,14 @@
<div class="container"> <div class="container">
<mat-card class="mat-elevation-z8"> <mat-card class="mat-elevation-z8">
<button mat-raised-button style="float: right"
[title]="'dashboard.refresh' | translate"
(click)="refresh()"
>
<mat-icon>refresh</mat-icon>
</button>
<mat-card-title *ngIf="project">{{"dashboard.title" | translate}} "{{project.name}}"</mat-card-title> <mat-card-title *ngIf="project">{{"dashboard.title" | translate}} "{{project.name}}"</mat-card-title>
<mat-card-content style="padding: 2em 0 1em"> <mat-card-content style="padding: 2em 0 1em">
<button mat-raised-button style="float: right"
[title]="'dashboard.refresh' | translate"
(click)="refresh()"
>
<mat-icon>refresh</mat-icon>
</button>
<p *ngIf="project"> <p *ngIf="project">
{{"project.git_repo" | translate}}: {{"project.git_repo" | translate}}:
@ -54,6 +54,7 @@
</mat-card-content> </mat-card-content>
<mat-card-actions> <mat-card-actions>
<!--TODO: auth-->
<button mat-raised-button color="primary" *ngIf="project" <button mat-raised-button color="primary" *ngIf="project"
[routerLink]="'/project/' + project.id + '/update'">{{"project.update" | translate}}</button> [routerLink]="'/project/' + project.id + '/update'">{{"project.update" | translate}}</button>
<button mat-raised-button color="primary" *ngIf="project" <button mat-raised-button color="primary" *ngIf="project"

View File

@ -1,11 +1,16 @@
<div class="container"> <div class="container">
<mat-card class="mat-elevation-z8"> <mat-card class="mat-elevation-z8">
<button mat-raised-button style="float: right"
[title]="'projects.refresh' | translate"
(click)="refresh()">
<mat-icon>refresh</mat-icon>
</button>
<mat-card-header> <mat-card-header>
<mat-card-title>{{"projects.projects" | translate}}</mat-card-title> <mat-card-title>{{"projects.projects" | translate}}</mat-card-title>
</mat-card-header> </mat-card-header>
<mat-card-content> <mat-card-content>
<mat-accordion> <mat-accordion>
<mat-expansion-panel *ngFor="let project of projects"> <mat-expansion-panel *ngFor="let project of projects" style="margin-top: 1em">
<mat-expansion-panel-header> <mat-expansion-panel-header>
<mat-panel-title> <mat-panel-title>
<mat-icon *ngIf="project.public">public</mat-icon> <mat-icon *ngIf="project.public">public</mat-icon>

View File

@ -38,7 +38,7 @@ export class ProjectPermsComponent implements OnInit {
private getProject() { private getProject() {
this.apiService.getProject(this.projectId).subscribe(data => { this.apiService.getProject(this.projectId).subscribe(data => {
this.project = data["project"] this.project = data["projectChange"]
}) })
} }

View File

@ -0,0 +1,3 @@
.mat-form-field {
width: 100%;
}

View File

@ -0,0 +1,20 @@
<mat-form-field appearance="outline" style="margin-top: 1em">
<mat-label>{{"project.chain" | translate}}</mat-label>
<mat-select [(ngModel)]="project" (selectionChange)="projectChange.emit($event.value)"
[placeholder]="'project.chain' | translate"
(opened)="loadProjectList()">
<mat-select-trigger>{{project?.name}}</mat-select-trigger>
<mat-option disabled *ngIf="projectList == undefined">
{{"project_select.loading" | translate}}
</mat-option>
<mat-option [value]="null" *ngIf="projectList">
{{"project_select.none" | translate}}
</mat-option>
<mat-option *ngFor="let p of projectList" [value]="p">
<mat-icon *ngIf="p.public">public</mat-icon>
<mat-icon *ngIf="!p.public">lock</mat-icon>
<span style="width: 3em; display: inline-block">{{p.id}}</span>
{{p.name}}
</mat-option>
</mat-select>
</mat-form-field>

View File

@ -0,0 +1,28 @@
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {ApiService} from "../api.service";
import {Project} from "../models/project";
@Component({
selector: 'project-select',
templateUrl: './project-select.component.html',
styleUrls: ['./project-select.component.css']
})
export class ProjectSelectComponent implements OnInit {
projectList: Project[];
@Input() project: Project;
@Output() projectChange = new EventEmitter<Project>();
constructor(private apiService: ApiService) {
}
ngOnInit() {
}
loadProjectList() {
this.apiService.getProjects().subscribe(data => {
this.projectList = data["projects"]
})
}
}

View File

@ -25,6 +25,7 @@
enabled enabled
</mat-hint> </mat-hint>
</mat-form-field> </mat-form-field>
<project-select [(project)]="selectedProject"></project-select>
</form> </form>
</mat-card-content> </mat-card-content>
<mat-card-actions> <mat-card-actions>

View File

@ -18,6 +18,7 @@ export class UpdateProjectComponent implements OnInit {
} }
project: Project; project: Project;
selectedProject: Project;
private projectId: number; private projectId: number;
ngOnInit() { ngOnInit() {
@ -29,11 +30,13 @@ 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 = data["project"] this.project = data["project"];
this.selectedProject = <Project>{id: this.project.chain}
}) })
} }
onSubmit() { onSubmit() {
this.project.chain = this.selectedProject ? this.selectedProject.id : 0;
this.apiService.updateProject(this.project).subscribe( this.apiService.updateProject(this.project).subscribe(
data => { data => {
this.router.navigateByUrl("/project/" + this.project.id); this.router.navigateByUrl("/project/" + this.project.id);

View File

@ -62,7 +62,8 @@
"git_repo": "Git repository name", "git_repo": "Git repository name",
"motd": "Message of the day", "motd": "Message of the day",
"update": "Edit", "update": "Edit",
"perms": "Permissions" "perms": "Permissions",
"chain": "Chain tasks to"
}, },
"dashboard": { "dashboard": {
"title": "Dashboard for", "title": "Dashboard for",
@ -117,5 +118,9 @@
"promote": "Promote", "promote": "Promote",
"demote": "Demote", "demote": "Demote",
"register_time": "Register date" "register_time": "Register date"
},
"project_select": {
"list_loading": "Loading project list...",
"none": "None"
} }
} }

View File

@ -63,7 +63,8 @@
"create": "Créer", "create": "Créer",
"motd": "Message du jour", "motd": "Message du jour",
"update": "Mettre à jour", "update": "Mettre à jour",
"perms": "Permissions" "perms": "Permissions",
"chain": "Enchainer les tâches vers"
}, },
"dashboard": { "dashboard": {
"title": "Tableau de bord pour ", "title": "Tableau de bord pour ",
@ -119,6 +120,10 @@
"promote": "Promouvoir", "promote": "Promouvoir",
"demote": "Rétrograder", "demote": "Rétrograder",
"register_time": "Date d'inscription" "register_time": "Date d'inscription"
},
"project_select": {
"list_loading": "Chargement de la liste de projets...",
"none": "Aucun"
} }
} }