diff --git a/api/main.go b/api/main.go index 23992bf..9208623 100644 --- a/api/main.go +++ b/api/main.go @@ -53,6 +53,7 @@ func New() *WebAPI { api.router.POST("/project/create", LogRequestMiddleware(api.ProjectCreate)) api.router.GET("/project/get/:id", LogRequestMiddleware(api.ProjectGet)) api.router.GET("/project/stats/:id", LogRequestMiddleware(api.ProjectGetStats)) + api.router.GET("/project/stats", LogRequestMiddleware(api.ProjectGetAllStats)) api.router.POST("/task/create", LogRequestMiddleware(api.TaskCreate)) api.router.GET("/task/get/:project", LogRequestMiddleware(api.TaskGetFromProject)) diff --git a/api/project.go b/api/project.go index 8dde830..be05161 100644 --- a/api/project.go +++ b/api/project.go @@ -33,6 +33,12 @@ type GetProjectStatsResponse struct { Stats *storage.ProjectStats `json:"stats,omitempty"` } +type GetAllProjectsStatsResponse struct { + Ok bool `json:"ok"` + Message string `json:"message,omitempty"` + Stats *[]storage.ProjectStats `json:"stats,omitempty"` +} + func (api *WebAPI) ProjectCreate(r *Request) { createReq := &CreateProjectRequest{} @@ -125,3 +131,13 @@ func (api *WebAPI) ProjectGetStats(r *Request) { }, 404) } } + +func (api *WebAPI) ProjectGetAllStats(r *Request) { + + stats := api.Database.GetAllProjectsStats() + + r.OkJson(GetAllProjectsStatsResponse{ + Ok: true, + Stats: stats, + }) +} diff --git a/setup.sh b/setup.sh deleted file mode 100755 index a639ee1..0000000 --- a/setup.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash - -export INSTALL_DIR="/home/drone/task_tracker" - -mkdir ${INSTALL_DIR} 2> /dev/null - -# Gogs -if [[ ! -d "${INSTALL_DIR}/gogs" ]]; then - wget "https://dl.gogs.io/0.11.79/gogs_0.11.79_linux_amd64.tar.gz" - tar -xzf "gogs_0.11.79_linux_amd64.tar.gz" -C ${INSTALL_DIR} - rm "gogs_0.11.79_linux_amd64.tar.gz" -fi - -# Postgres -su - postgres -c "createuser task_tracker" -su - postgres -c "dropdb gogs" -su - postgres -c "createdb gogs" diff --git a/storage/log.go b/storage/log.go index 669ef49..3622fba 100644 --- a/storage/log.go +++ b/storage/log.go @@ -1,7 +1,6 @@ package storage import ( - "database/sql" "encoding/json" "github.com/Sirupsen/logrus" "src/task_tracker/config" @@ -44,12 +43,6 @@ func (database *Database) SetupLoggerHook() { func (database *Database) GetLogs(since int64, level logrus.Level) *[]LogEntry { db := database.getDB() - logs := getLogs(since, level, db) - - return logs -} - -func getLogs(since int64, level logrus.Level, db *sql.DB) *[]LogEntry { var logs []LogEntry diff --git a/storage/project.go b/storage/project.go index f377245..2e6a0e7 100644 --- a/storage/project.go +++ b/storage/project.go @@ -102,12 +102,6 @@ func scanProject(row *sql.Row) (*Project, error) { func (database *Database) GetProjectWithRepoName(repoName string) *Project { db := database.getDB() - project := getProjectWithRepoName(repoName, db) - return project -} - -func getProjectWithRepoName(repoName string, db *sql.DB) *Project { - row := db.QueryRow(`SELECT * FROM project WHERE LOWER(git_repo)=$1`, strings.ToLower(repoName)) project, err := scanProject(row) @@ -124,10 +118,6 @@ func getProjectWithRepoName(repoName string, db *sql.DB) *Project { func (database *Database) UpdateProject(project *Project) { db := database.getDB() - updateProject(project, db) -} - -func updateProject(project *Project, db *sql.DB) { res, err := db.Exec(`UPDATE project SET (priority, name, clone_url, git_repo, version, motd) = ($1,$2,$3,$4,$5,$6) WHERE id=$7`, @@ -147,13 +137,6 @@ func updateProject(project *Project, db *sql.DB) { func (database *Database) GetProjectStats(id int64) *ProjectStats { db := database.getDB() - stats := getProjectStats(id, db) - - return stats -} - -func getProjectStats(id int64, db *sql.DB) *ProjectStats { - stats := ProjectStats{} stats.Project = getProject(id, db) @@ -169,7 +152,7 @@ func getProjectStats(id int64, db *sql.DB) *ProjectStats { if err != nil { logrus.WithError(err).WithFields(logrus.Fields{ "id": id, - }).Warn("???") //todo + }).Trace("Get project stats: No task for this project") return nil } @@ -187,3 +170,36 @@ func getProjectStats(id int64, db *sql.DB) *ProjectStats { return &stats } + +func (database Database) GetAllProjectsStats() *[]ProjectStats { + var statsList []ProjectStats + + db := database.getDB() + rows, err := db.Query(`SELECT + SUM(CASE WHEN status='new' THEN 1 ELSE 0 END) newCount, + 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`) + handleErr(err) + + for rows.Next() { + + 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) + handleErr(err) + + stats.Project = p + + statsList = append(statsList, stats) + } + + logrus.WithFields(logrus.Fields{ + "statsList": statsList, + }).Trace("Get all projects stats") + + return &statsList +} diff --git a/storage/task.go b/storage/task.go index e4517b3..4605ae2 100644 --- a/storage/task.go +++ b/storage/task.go @@ -20,12 +20,6 @@ type Task struct { func (database *Database) SaveTask(task *Task, project int64) error { db := database.getDB() - taskErr := saveTask(task, project, db) - - return taskErr -} - -func saveTask(task *Task, project int64, db *sql.DB) error { res, err := db.Exec(` INSERT INTO task (project, max_retries, recipe, priority) @@ -52,12 +46,6 @@ func saveTask(task *Task, project int64, db *sql.DB) error { func (database *Database) GetTask(worker *Worker) *Task { db := database.getDB() - task := getTask(worker, db) - - return task -} - -func getTask(worker *Worker, db *sql.DB) *Task { row := db.QueryRow(` UPDATE task @@ -111,12 +99,6 @@ func getTaskById(id int64, db *sql.DB) *Task { func (database Database) ReleaseTask(id int64, workerId *uuid.UUID, success bool) bool { db := database.getDB() - res := releaseTask(workerId, id, success, db) - - return res -} - -func releaseTask(workerId *uuid.UUID, id int64, success bool, db *sql.DB) bool { var res sql.Result var err error @@ -139,15 +121,9 @@ func releaseTask(workerId *uuid.UUID, id int64, success bool, db *sql.DB) bool { return rowsAffected == 1 } -func (database *Database) GetTaskFromProject(worker *Worker, project int64) *Task { +func (database *Database) GetTaskFromProject(worker *Worker, projectId int64) *Task { db := database.getDB() - task := getTaskFromProject(worker, project, db) - - return task -} - -func getTaskFromProject(worker *Worker, projectId int64, db *sql.DB) *Task { row := db.QueryRow(` UPDATE task @@ -158,7 +134,7 @@ func getTaskFromProject(worker *Worker, projectId int64, db *sql.DB) *Task { FROM task INNER JOIN project p on task.project = p.id WHERE assignee IS NULL AND p.id=$2 - ORDER BY p.priority DESC, task.priority DESC + ORDER BY task.priority DESC LIMIT 1 ) RETURNING id`, worker.Id, projectId) diff --git a/storage/worker.go b/storage/worker.go index b749980..f673d2c 100644 --- a/storage/worker.go +++ b/storage/worker.go @@ -21,17 +21,6 @@ type Worker struct { func (database *Database) SaveWorker(worker *Worker) { db := database.getDB() - saveWorker(worker, db) -} - -func (database *Database) GetWorker(id uuid.UUID) *Worker { - - db := database.getDB() - worker := getWorker(id, db) - return worker -} - -func saveWorker(worker *Worker, db *sql.DB) { identityId := getOrCreateIdentity(worker.Identity, db) @@ -45,7 +34,9 @@ func saveWorker(worker *Worker, db *sql.DB) { }).Trace("Database.saveWorker INSERT worker") } -func getWorker(id uuid.UUID, db *sql.DB) *Worker { +func (database *Database) GetWorker(id uuid.UUID) *Worker { + + db := database.getDB() worker := &Worker{} var identityId int64 diff --git a/test/api_task_bench_test.go b/test/api_task_bench_test.go index 897ec08..2d9b1a3 100644 --- a/test/api_task_bench_test.go +++ b/test/api_task_bench_test.go @@ -10,7 +10,6 @@ func BenchmarkCreateTask(b *testing.B) { resp := createProject(api.CreateProjectRequest{ Name: "BenchmarkCreateTask" + strconv.Itoa(b.N), - Priority: 1, GitRepo: "benchmark_test" + strconv.Itoa(b.N), Version: "f09e8c9r0w839x0c43", CloneUrl: "http://localhost", diff --git a/web/angular/e2e/protractor.conf.js b/web/angular/e2e/protractor.conf.js old mode 100644 new mode 100755 diff --git a/web/angular/e2e/src/app.e2e-spec.ts b/web/angular/e2e/src/app.e2e-spec.ts old mode 100644 new mode 100755 diff --git a/web/angular/e2e/src/app.po.ts b/web/angular/e2e/src/app.po.ts old mode 100644 new mode 100755 diff --git a/web/angular/e2e/tsconfig.e2e.json b/web/angular/e2e/tsconfig.e2e.json old mode 100644 new mode 100755 diff --git a/web/angular/src/app/api.service.ts b/web/angular/src/app/api.service.ts old mode 100644 new mode 100755 index 6623eab..71b14aa --- a/web/angular/src/app/api.service.ts +++ b/web/angular/src/app/api.service.ts @@ -12,10 +12,18 @@ export class ApiService { } getLogs() { - return this.http.get(this.url + "/logs"); + return this.http.post(this.url + "/logs", "{\"level\":\"info\", \"since\":10000}"); } getProjectStats(id: number) { return this.http.get(this.url + "/project/stats/" + id) } + + getProjects() { + return this.http.get(this.url + "/project/stats") + } + + getProject(id: number) { + return this.http.get(this.url + "/project/get/" + id) + } } diff --git a/web/angular/src/app/app-routing.module.ts b/web/angular/src/app/app-routing.module.ts old mode 100644 new mode 100755 index b442425..699534b --- a/web/angular/src/app/app-routing.module.ts +++ b/web/angular/src/app/app-routing.module.ts @@ -2,10 +2,16 @@ import {NgModule} from '@angular/core'; import {RouterModule, Routes} from '@angular/router'; import {LogsComponent} from "./logs/logs.component"; 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 {UpdateProjectComponent} from "./update-project/update-project.component"; const routes: Routes = [ {path: "log", component: LogsComponent}, - {path: "project", component: ProjectDashboardComponent} + {path: "projects", component: ProjectListComponent}, + {path: "project/:id", component: ProjectDashboardComponent}, + {path: "project/:id/update", component: UpdateProjectComponent}, + {path: "new_project", component: CreateProjectComponent} ]; @NgModule({ diff --git a/web/angular/src/app/app.component.css b/web/angular/src/app/app.component.css old mode 100644 new mode 100755 diff --git a/web/angular/src/app/app.component.html b/web/angular/src/app/app.component.html old mode 100644 new mode 100755 index 98b77f3..a9228dc --- a/web/angular/src/app/app.component.html +++ b/web/angular/src/app/app.component.html @@ -1,8 +1,12 @@ - - Index - Logs - Project - + + + diff --git a/web/angular/src/app/app.module.ts b/web/angular/src/app/app.module.ts old mode 100644 new mode 100755 index 0cfee90..f7c7cde --- a/web/angular/src/app/app.module.ts +++ b/web/angular/src/app/app.module.ts @@ -7,6 +7,8 @@ import {LogsComponent} from './logs/logs.component'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; import { + MatAutocompleteModule, + MatButtonModule, MatCardModule, MatExpansionModule, MatFormFieldModule, @@ -22,12 +24,19 @@ import { import {ApiService} from "./api.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'; @NgModule({ declarations: [ AppComponent, LogsComponent, - ProjectDashboardComponent + ProjectDashboardComponent, + ProjectListComponent, + CreateProjectComponent, + UpdateProjectComponent ], imports: [ BrowserModule, @@ -41,6 +50,10 @@ import {ProjectDashboardComponent} from './project-dashboard/project-dashboard.c MatInputModule, MatToolbarModule, MatCardModule, + MatButtonModule, + MatAutocompleteModule, + ReactiveFormsModule, + FormsModule, MatExpansionModule, MatTreeModule, BrowserAnimationsModule, diff --git a/web/angular/src/app/create-project/create-project.component.css b/web/angular/src/app/create-project/create-project.component.css new file mode 100644 index 0000000..0f6a3e4 --- /dev/null +++ b/web/angular/src/app/create-project/create-project.component.css @@ -0,0 +1,4 @@ + +.mat-form-field { + width: 100%; +} diff --git a/web/angular/src/app/create-project/create-project.component.html b/web/angular/src/app/create-project/create-project.component.html new file mode 100644 index 0000000..fa7a0af --- /dev/null +++ b/web/angular/src/app/create-project/create-project.component.html @@ -0,0 +1,30 @@ + + Create new project + + + +
+ + + + + + + + + + Changes on the master branch will be tracked if webhooks are + enabled + + + +
+
+ + + + + +
diff --git a/web/angular/src/app/create-project/create-project.component.ts b/web/angular/src/app/create-project/create-project.component.ts new file mode 100644 index 0000000..da720d3 --- /dev/null +++ b/web/angular/src/app/create-project/create-project.component.ts @@ -0,0 +1,20 @@ +import {Component, OnInit} from '@angular/core'; +import {Project} from "../models/project"; + +@Component({ + selector: 'app-create-project', + templateUrl: './create-project.component.html', + styleUrls: ['./create-project.component.css'] +}) +export class CreateProjectComponent implements OnInit { + + private project = new Project(); + + constructor() { + this.project.name = "test" + } + + ngOnInit() { + } + +} diff --git a/web/angular/src/app/logs/logs.component.spec.ts b/web/angular/src/app/logs/logs.component.spec.ts deleted file mode 100644 index e901797..0000000 --- a/web/angular/src/app/logs/logs.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {async, ComponentFixture, TestBed} from '@angular/core/testing'; - -import {LogsComponent} from './logs.component'; - -describe('LogsComponent', () => { - let component: LogsComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [LogsComponent] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(LogsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/web/angular/src/app/logs/logs.component.ts b/web/angular/src/app/logs/logs.component.ts index 34582d7..04ef8a8 100644 --- a/web/angular/src/app/logs/logs.component.ts +++ b/web/angular/src/app/logs/logs.component.ts @@ -40,7 +40,7 @@ export class LogsComponent implements OnInit { private getLogs() { this.apiService.getLogs().subscribe( data => { - this.data.data = _.map(data, (entry) => { + this.data.data = _.map(data["logs"], (entry) => { return { message: entry.message, timestamp: moment.unix(entry.timestamp).toISOString(), diff --git a/web/angular/src/app/models/project.ts b/web/angular/src/app/models/project.ts new file mode 100644 index 0000000..4e2ad4f --- /dev/null +++ b/web/angular/src/app/models/project.ts @@ -0,0 +1,9 @@ +export class Project { + + public priority: number; + public motd: string; + public name: string; + public clone_url: string; + public git_repo: string; + public version: string; +} diff --git a/web/angular/src/app/project-dashboard/project-dashboard.component.ts b/web/angular/src/app/project-dashboard/project-dashboard.component.ts index 6b29649..e82eeb8 100644 --- a/web/angular/src/app/project-dashboard/project-dashboard.component.ts +++ b/web/angular/src/app/project-dashboard/project-dashboard.component.ts @@ -4,6 +4,7 @@ import * as d3 from "d3" import * as _ from "lodash" import {interval} from "rxjs"; import {ApiService} from "../api.service"; +import {ActivatedRoute} from "@angular/router"; @Component({ selector: 'app-project-dashboard', @@ -12,7 +13,9 @@ import {ApiService} from "../api.service"; }) export class ProjectDashboardComponent implements OnInit { + private projectId; projectStats; + private pieWidth = 360; private pieHeight = 360; private pieRadius = Math.min(this.pieWidth, this.pieHeight) / 2; @@ -47,7 +50,7 @@ export class ProjectDashboardComponent implements OnInit { private assigneesPath: any; private assigneesSvg: any; - constructor(private apiService: ApiService) { + constructor(private apiService: ApiService, private route: ActivatedRoute) { } setupStatusPieChart() { @@ -165,7 +168,7 @@ export class ProjectDashboardComponent implements OnInit { } getStats() { - this.apiService.getProjectStats(2).subscribe((data) => { + this.apiService.getProjectStats(this.projectId).subscribe((data) => { this.projectStats = data["stats"]; @@ -259,9 +262,13 @@ export class ProjectDashboardComponent implements OnInit { this.setupAssigneesPieChart(); this.setupLine(); - this.getStats(); - interval(1000).subscribe(() => { - this.getStats() - }) + this.route.params.subscribe(params => { + this.projectId = params["id"]; + this.getStats(); + interval(1000).subscribe(() => { + // this.getStats() + }) + } + ) } } diff --git a/web/angular/src/app/project-list/project-list.component.css b/web/angular/src/app/project-list/project-list.component.css new file mode 100755 index 0000000..e69de29 diff --git a/web/angular/src/app/project-list/project-list.component.html b/web/angular/src/app/project-list/project-list.component.html new file mode 100755 index 0000000..aef2047 --- /dev/null +++ b/web/angular/src/app/project-list/project-list.component.html @@ -0,0 +1,19 @@ + + + Projects + + + + + + {{stats.project.id}}: {{stats.project.name}} + {{stats.project.motd}} + +
{{stats.project | json}}
+
+ Dashboard +
+
+
+
+
diff --git a/web/angular/src/app/project-list/project-list.component.ts b/web/angular/src/app/project-list/project-list.component.ts new file mode 100755 index 0000000..3f44fef --- /dev/null +++ b/web/angular/src/app/project-list/project-list.component.ts @@ -0,0 +1,24 @@ +import {Component, OnInit} from '@angular/core'; +import {ApiService} from "../api.service"; + +@Component({ + selector: 'app-project-list', + templateUrl: './project-list.component.html', + styleUrls: ['./project-list.component.css'] +}) +export class ProjectListComponent implements OnInit { + + constructor(private apiService: ApiService) { + } + + projects: any[]; + + ngOnInit() { + this.getProjects() + } + + getProjects() { + this.apiService.getProjects().subscribe(data => this.projects = data["stats"]); + } + +} diff --git a/web/angular/src/app/tsconfig.json b/web/angular/src/app/tsconfig.json deleted file mode 100644 index da5e373..0000000 --- a/web/angular/src/app/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "target": "es5", - "sourceMap": true - }, - "exclude": [ - "node_modules" - ] -} diff --git a/web/angular/src/app/update-project/update-project.component.css b/web/angular/src/app/update-project/update-project.component.css new file mode 100644 index 0000000..0f6a3e4 --- /dev/null +++ b/web/angular/src/app/update-project/update-project.component.css @@ -0,0 +1,4 @@ + +.mat-form-field { + width: 100%; +} diff --git a/web/angular/src/app/update-project/update-project.component.html b/web/angular/src/app/update-project/update-project.component.html new file mode 100644 index 0000000..e48a77b --- /dev/null +++ b/web/angular/src/app/update-project/update-project.component.html @@ -0,0 +1,29 @@ + + Update project + Changes are saved in real time + + +
+ + + + + + + + + + + + + + Changes on the master branch will be tracked if webhooks are + enabled + + + +
+
+
diff --git a/web/angular/src/app/update-project/update-project.component.ts b/web/angular/src/app/update-project/update-project.component.ts new file mode 100644 index 0000000..35567e7 --- /dev/null +++ b/web/angular/src/app/update-project/update-project.component.ts @@ -0,0 +1,40 @@ +import {Component, OnInit} from '@angular/core'; +import {Project} from "../models/project"; +import {ApiService} from "../api.service"; +import {ActivatedRoute} from "@angular/router"; + +@Component({ + selector: 'app-update-project', + templateUrl: './update-project.component.html', + styleUrls: ['./update-project.component.css'] +}) +export class UpdateProjectComponent implements OnInit { + + constructor(private apiService: ApiService, private route: ActivatedRoute) { + } + + private project: Project; + private projectId: number; + + ngOnInit() { + this.route.params.subscribe(params => { + this.projectId = params["id"]; + this.getProject(); + }) + } + + private getProject() { + this.apiService.getProject(this.projectId).subscribe(data => { + this.project = { + 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"] + + } + }) + } + +}