From 87f4d08984043bcd23bdb11ebfcaae890c4ce97b Mon Sep 17 00:00:00 2001 From: simon987 Date: Tue, 5 Feb 2019 20:11:52 -0500 Subject: [PATCH] Added i18n, started working on monitoring --- api/main.go | 3 +- api/project.go | 50 +-- schema.sql | 16 +- storage/monitoring.go | 44 +++ storage/project.go | 85 +---- test/api_project_test.go | 90 ----- test/schema.sql | 16 +- web/angular/package-lock.json | 328 +++--------------- web/angular/package.json | 6 +- web/angular/src/app/api.service.ts | 6 +- web/angular/src/app/app.component.css | 3 + web/angular/src/app/app.component.html | 16 +- web/angular/src/app/app.component.ts | 23 +- web/angular/src/app/app.module.ts | 20 +- .../create-project.component.ts | 2 +- web/angular/src/app/logs/logs.component.ts | 6 +- .../project-dashboard.component.ts | 270 +------------- .../project-list/project-list.component.html | 12 +- .../project-list/project-list.component.ts | 5 +- .../update-project.component.ts | 2 +- web/angular/src/assets/i18n/en.json | 6 + web/angular/src/assets/i18n/fr.json | 6 + 22 files changed, 230 insertions(+), 785 deletions(-) create mode 100644 storage/monitoring.go create mode 100644 web/angular/src/assets/i18n/en.json create mode 100644 web/angular/src/assets/i18n/fr.json diff --git a/api/main.go b/api/main.go index bc29b39..da68084 100644 --- a/api/main.go +++ b/api/main.go @@ -65,8 +65,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)) + api.router.GET("/project/list", LogRequestMiddleware(api.ProjectGetAllProjects)) 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 84b7779..df6bfd2 100644 --- a/api/project.go +++ b/api/project.go @@ -43,16 +43,10 @@ type GetProjectResponse struct { Project *storage.Project `json:"project,omitempty"` } -type GetProjectStatsResponse struct { - Ok bool `json:"ok"` - Message string `json:"message,omitempty"` - Stats *storage.ProjectStats `json:"stats,omitempty"` -} - -type GetAllProjectsStatsResponse struct { - Ok bool `json:"ok"` - Message string `json:"message,omitempty"` - Stats *[]storage.ProjectStats `json:"stats,omitempty"` +type GetAllProjectsResponse struct { + Ok bool `json:"ok"` + Message string `json:"message,omitempty"` + Projects *[]storage.Project `json:"projects,omitempty"` } func (api *WebAPI) ProjectCreate(r *Request) { @@ -200,38 +194,12 @@ func (api *WebAPI) ProjectGet(r *Request) { } } -func (api *WebAPI) ProjectGetStats(r *Request) { +func (api *WebAPI) ProjectGetAllProjects(r *Request) { - id, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64) - if err != nil { - r.Json(GetProjectStatsResponse{ - Ok: false, - Message: "Could not parse request", - }, 400) - return - } + projects := api.Database.GetAllProjects() - stats := api.Database.GetProjectStats(id) - - if stats != nil && stats.Project != nil { - r.OkJson(GetProjectStatsResponse{ - Ok: true, - Stats: stats, - }) - } else { - r.Json(GetProjectStatsResponse{ - Ok: false, - Message: "Project not found", - }, 404) - } -} - -func (api *WebAPI) ProjectGetAllStats(r *Request) { - - stats := api.Database.GetAllProjectsStats() - - r.OkJson(GetAllProjectsStatsResponse{ - Ok: true, - Stats: stats, + r.OkJson(GetAllProjectsResponse{ + Ok: true, + Projects: projects, }) } diff --git a/schema.sql b/schema.sql index 4098d24..1a7c13c 100755 --- a/schema.sql +++ b/schema.sql @@ -1,5 +1,6 @@ DROP TABLE IF EXISTS worker_identity, worker, project, task, log_entry, - worker_has_access_to_project, manager, manager_has_role_on_project, project_monitoring, worker_verifies_task; + worker_has_access_to_project, manager, manager_has_role_on_project, project_monitoring_snapshot, + worker_verifies_task; DROP TYPE IF EXISTS status; DROP TYPE IF EXISTS log_level; @@ -87,12 +88,15 @@ CREATE TABLE manager_has_role_on_project project INTEGER REFERENCES project (id) ); -CREATE TABLE project_monitoring +CREATE TABLE project_monitoring_snapshot ( - project INT REFERENCES project (id), - new_task_count INT, - failed_task_count INT, - closed_task_count INT + project INT REFERENCES project (id), + new_task_count INT, + failed_task_count INT, + closed_task_count INT, + awaiting_verification_task_count INT, + worker_access_count INT, + timestamp INT ); CREATE OR REPLACE FUNCTION on_task_delete_proc() RETURNS TRIGGER AS diff --git a/storage/monitoring.go b/storage/monitoring.go new file mode 100644 index 0000000..d3278b0 --- /dev/null +++ b/storage/monitoring.go @@ -0,0 +1,44 @@ +package storage + +type ProjectMonitoringSnapshot struct { + NewTaskCount int64 + FailedTaskCount int64 + ClosedTaskCount int64 + WorkerAccessCount int64 + TimeStamp int64 +} + +func (database *Database) MakeProjectSnapshots() { + + db := database.getDB() + + _, err := db.Exec(` + INSERT INTO project_monitoring_snapshot + (project, new_task_count, failed_task_count, closed_task_count, worker_access_count, timestamp) + SELECT id, + (SELECT COUNT(*) FROM task WHERE task.project = project.id AND status = 1), + (SELECT COUNT(*) FROM task WHERE task.project = project.id AND status = 2), + closed_task_count, + (SELECT COUNT(*) FROM worker_has_access_to_project wa WHERE wa.project = project.id), + extract(epoch from now() at time zone 'utc') + FROM project`) + handleErr(err) +} + +func (database *Database) GetMonitoringSnapshots(pid int64, from int64, to int64) (ss *[]ProjectMonitoringSnapshot) { + + db := database.getDB() + + rows, err := db.Query(`SELECT new_task_count, failed_task_count, closed_task_count, + worker_access_count, timestamp FROM project_monitoring_snapshot + WHERE project=$1 AND timestamp BETWEEN $2 AND $3`, pid, from, to) + handleErr(err) + + for rows.Next() { + + s := ProjectMonitoringSnapshot{} + err := rows.Scan(&s.NewTaskCount, &s.FailedTaskCount, &s.ClosedTaskCount, &s.WorkerAccessCount, &s.TimeStamp) + handleErr(err) + } + return nil +} diff --git a/storage/project.go b/storage/project.go index a7016f4..1eb6c98 100644 --- a/storage/project.go +++ b/storage/project.go @@ -22,14 +22,6 @@ type AssignedTasks struct { TaskCount int64 `json:"task_count"` } -type ProjectStats struct { - Project *Project `json:"project"` - NewTaskCount int64 `json:"new_task_count"` - FailedTaskCount int64 `json:"failed_task_count"` - ClosedTaskCount int64 `json:"closed_task_count"` - Assignees []*AssignedTasks `json:"assignees"` -} - func (database *Database) SaveProject(project *Project) (int64, error) { db := database.getDB() id, projectErr := saveProject(project, db) @@ -139,83 +131,28 @@ func (database *Database) UpdateProject(project *Project) error { return nil } -func (database *Database) GetProjectStats(id int64) *ProjectStats { - - db := database.getDB() - stats := ProjectStats{} - - stats.Project = getProject(id, db) - - if stats.Project != nil { - row := db.QueryRow(`SELECT - SUM(CASE WHEN status=1 THEN 1 ELSE 0 END) newCount, - SUM(CASE WHEN status=2 THEN 1 ELSE 0 END) failedCount, - SUM(CASE WHEN status=3 THEN 1 ELSE 0 END) closedCount - FROM task WHERE project=$1 GROUP BY project`, id) - - err := row.Scan(&stats.NewTaskCount, &stats.FailedTaskCount, &stats.ClosedTaskCount) - if err != nil { - logrus.WithError(err).WithFields(logrus.Fields{ - "id": id, - }).Trace("Get project stats: No task for this project") - - } - - 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{} - 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) - } - } - - return &stats -} - -func (database Database) GetAllProjectsStats() *[]ProjectStats { - var statsList []ProjectStats +func (database Database) GetAllProjects() *[]Project { + var projects []Project db := database.getDB() rows, err := db.Query(`SELECT - SUM(CASE WHEN status= 1 THEN 1 ELSE 0 END) newCount, - SUM(CASE WHEN status=2 THEN 1 ELSE 0 END) failedCount, - SUM(CASE WHEN status=3 THEN 1 ELSE 0 END) closedCount, - p.Id, p.priority, p.name, p.clone_url, p.git_repo, p.version, p.motd, - p.public - FROM task RIGHT JOIN project p on task.project = p.id - GROUP BY p.id ORDER BY p.name`) + Id, priority, name, clone_url, git_repo, version, motd, public + FROM project + ORDER BY name`) handleErr(err) for rows.Next() { - stats := ProjectStats{} - p := &Project{} - err := rows.Scan(&stats.NewTaskCount, &stats.FailedTaskCount, &stats.ClosedTaskCount, - &p.Id, &p.Priority, &p.Name, &p.CloneUrl, &p.GitRepo, &p.Version, &p.Motd, &p.Public) + p := Project{} + err := rows.Scan(&p.Id, &p.Priority, &p.Name, &p.CloneUrl, + &p.GitRepo, &p.Version, &p.Motd, &p.Public) handleErr(err) - - stats.Project = p - - statsList = append(statsList, stats) + projects = append(projects, p) } logrus.WithFields(logrus.Fields{ - "statsList": statsList, + "projects": projects, }).Trace("Get all projects stats") - return &statsList + return &projects } diff --git a/test/api_project_test.go b/test/api_project_test.go index 1d42a19..9f62c39 100644 --- a/test/api_project_test.go +++ b/test/api_project_test.go @@ -122,84 +122,6 @@ func TestGetProjectNotFound(t *testing.T) { } } -func TestGetProjectStats(t *testing.T) { - - r := createProject(api.CreateProjectRequest{ - Motd: "motd", - Name: "Name", - Version: "version", - 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) - - if stats.Ok != true { - t.Error() - } - - if stats.Stats.Project.Id != pid { - t.Error() - } - - if stats.Stats.NewTaskCount != 3 { - t.Error() - } - - if stats.Stats.Assignees[0].Assignee != "unassigned" { - t.Error() - } - if stats.Stats.Assignees[0].TaskCount != 3 { - t.Error() - } -} - -func TestGetProjectStatsNotFound(t *testing.T) { - - r := createProject(api.CreateProjectRequest{ - Motd: "eeeeeeeeej", - Name: "Namaaaaaaaaaaaa", - Version: "versionsssssssss", - CloneUrl: "http://github.com/drone/test1", - GitRepo: "drone/test1", - Priority: 1, - }) - s := getProjectStats(r.Id) - - if s.Ok != true { - t.Error() - } - - if s.Stats == nil { - t.Error() - } - -} - func TestUpdateProjectValid(t *testing.T) { pid := createProject(api.CreateProjectRequest{ @@ -337,18 +259,6 @@ func getProject(id int64) (*api.GetProjectResponse, *http.Response) { return &getResp, r } -func getProjectStats(id int64) *api.GetProjectStatsResponse { - - r := Get(fmt.Sprintf("/project/stats/%d", id), nil) - - var getResp api.GetProjectStatsResponse - data, _ := ioutil.ReadAll(r.Body) - err := json.Unmarshal(data, &getResp) - handleErr(err) - - return &getResp -} - func updateProject(request api.UpdateProjectRequest, pid int64) *api.UpdateProjectResponse { r := Post(fmt.Sprintf("/project/update/%d", pid), request, nil) diff --git a/test/schema.sql b/test/schema.sql index 4098d24..1a7c13c 100755 --- a/test/schema.sql +++ b/test/schema.sql @@ -1,5 +1,6 @@ DROP TABLE IF EXISTS worker_identity, worker, project, task, log_entry, - worker_has_access_to_project, manager, manager_has_role_on_project, project_monitoring, worker_verifies_task; + worker_has_access_to_project, manager, manager_has_role_on_project, project_monitoring_snapshot, + worker_verifies_task; DROP TYPE IF EXISTS status; DROP TYPE IF EXISTS log_level; @@ -87,12 +88,15 @@ CREATE TABLE manager_has_role_on_project project INTEGER REFERENCES project (id) ); -CREATE TABLE project_monitoring +CREATE TABLE project_monitoring_snapshot ( - project INT REFERENCES project (id), - new_task_count INT, - failed_task_count INT, - closed_task_count INT + project INT REFERENCES project (id), + new_task_count INT, + failed_task_count INT, + closed_task_count INT, + awaiting_verification_task_count INT, + worker_access_count INT, + timestamp INT ); CREATE OR REPLACE FUNCTION on_task_delete_proc() RETURNS TRIGGER AS diff --git a/web/angular/package-lock.json b/web/angular/package-lock.json index 286d756..54ee2cf 100644 --- a/web/angular/package-lock.json +++ b/web/angular/package-lock.json @@ -787,6 +787,22 @@ } } }, + "@ngx-translate/core": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-11.0.1.tgz", + "integrity": "sha512-nBCa1ZD9fAUY/3eskP3Lql2fNg8OMrYIej1/5GRsfcutx9tG/5fZLCv9m6UCw1aS+u4uK/vXjv1ctG/FdMvaWg==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@ngx-translate/http-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@ngx-translate/http-loader/-/http-loader-4.0.0.tgz", + "integrity": "sha512-x8LumqydWD7eX9yQTAVeoCM9gFUIGVTUjZqbxdAUavAA3qVnk9wCQux7iHLPXpydl8vyQmLoPQR+fFU+DUDOMA==", + "requires": { + "tslib": "^1.9.0" + } + }, "@schematics/angular": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-7.2.2.tgz", @@ -2099,6 +2115,39 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "chart.js": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.7.3.tgz", + "integrity": "sha512-3+7k/DbR92m6BsMUYP6M0dMsMVZpMnwkUyNSAbqolHKsbIzH2Q4LWVEHHYq7v0fmEV8whXE0DrjANulw9j2K5g==", + "requires": { + "chartjs-color": "^2.1.0", + "moment": "^2.10.2" + } + }, + "chartjs-color": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.2.0.tgz", + "integrity": "sha1-hKL7dVeH7YXDndbdjHsdiEKbrq4=", + "requires": { + "chartjs-color-string": "^0.5.0", + "color-convert": "^0.5.3" + }, + "dependencies": { + "color-convert": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz", + "integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0=" + } + } + }, + "chartjs-color-string": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.5.0.tgz", + "integrity": "sha512-amWNvCOXlOUYxZVDSa0YOab5K/lmEhbFNKI55PWc4mlv28BDzA7zaoQTGxSBgJMHIW+hGX8YUrvw/FH4LyhwSQ==", + "requires": { + "color-name": "^1.0.0" + } + }, "chokidar": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", @@ -2303,8 +2352,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "colors": { "version": "1.1.2", @@ -2333,7 +2381,8 @@ "commander": { "version": "2.17.1", "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==" + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true }, "commondir": { "version": "1.0.1", @@ -2695,270 +2744,6 @@ "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", "dev": true }, - "d3": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/d3/-/d3-5.7.0.tgz", - "integrity": "sha512-8KEIfx+dFm8PlbJN9PI0suazrZ41QcaAufsKE9PRcqYPWLngHIyWJZX96n6IQKePGgeSu0l7rtlueSSNq8Zc3g==", - "requires": { - "d3-array": "1", - "d3-axis": "1", - "d3-brush": "1", - "d3-chord": "1", - "d3-collection": "1", - "d3-color": "1", - "d3-contour": "1", - "d3-dispatch": "1", - "d3-drag": "1", - "d3-dsv": "1", - "d3-ease": "1", - "d3-fetch": "1", - "d3-force": "1", - "d3-format": "1", - "d3-geo": "1", - "d3-hierarchy": "1", - "d3-interpolate": "1", - "d3-path": "1", - "d3-polygon": "1", - "d3-quadtree": "1", - "d3-random": "1", - "d3-scale": "2", - "d3-scale-chromatic": "1", - "d3-selection": "1", - "d3-shape": "1", - "d3-time": "1", - "d3-time-format": "2", - "d3-timer": "1", - "d3-transition": "1", - "d3-voronoi": "1", - "d3-zoom": "1" - } - }, - "d3-array": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", - "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" - }, - "d3-axis": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.12.tgz", - "integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ==" - }, - "d3-brush": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.0.6.tgz", - "integrity": "sha512-lGSiF5SoSqO5/mYGD5FAeGKKS62JdA1EV7HPrU2b5rTX4qEJJtpjaGLJngjnkewQy7UnGstnFd3168wpf5z76w==", - "requires": { - "d3-dispatch": "1", - "d3-drag": "1", - "d3-interpolate": "1", - "d3-selection": "1", - "d3-transition": "1" - } - }, - "d3-chord": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.6.tgz", - "integrity": "sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==", - "requires": { - "d3-array": "1", - "d3-path": "1" - } - }, - "d3-collection": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", - "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==" - }, - "d3-color": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.2.3.tgz", - "integrity": "sha512-x37qq3ChOTLd26hnps36lexMRhNXEtVxZ4B25rL0DVdDsGQIJGB18S7y9XDwlDD6MD/ZBzITCf4JjGMM10TZkw==" - }, - "d3-contour": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-1.3.2.tgz", - "integrity": "sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==", - "requires": { - "d3-array": "^1.1.1" - } - }, - "d3-dispatch": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.5.tgz", - "integrity": "sha512-vwKx+lAqB1UuCeklr6Jh1bvC4SZgbSqbkGBLClItFBIYH4vqDJCA7qfoy14lXmJdnBOdxndAMxjCbImJYW7e6g==" - }, - "d3-drag": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.3.tgz", - "integrity": "sha512-8S3HWCAg+ilzjJsNtWW1Mutl74Nmzhb9yU6igspilaJzeZVFktmY6oO9xOh5TDk+BM2KrNFjttZNoJJmDnkjkg==", - "requires": { - "d3-dispatch": "1", - "d3-selection": "1" - } - }, - "d3-dsv": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.0.10.tgz", - "integrity": "sha512-vqklfpxmtO2ZER3fq/B33R/BIz3A1PV0FaZRuFM8w6jLo7sUX1BZDh73fPlr0s327rzq4H6EN1q9U+eCBCSN8g==", - "requires": { - "commander": "2", - "iconv-lite": "0.4", - "rw": "1" - } - }, - "d3-ease": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.5.tgz", - "integrity": "sha512-Ct1O//ly5y5lFM9YTdu+ygq7LleSgSE4oj7vUt9tPLHUi8VCV7QoizGpdWRWAwCO9LdYzIrQDg97+hGVdsSGPQ==" - }, - "d3-fetch": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.1.2.tgz", - "integrity": "sha512-S2loaQCV/ZeyTyIF2oP8D1K9Z4QizUzW7cWeAOAS4U88qOt3Ucf6GsmgthuYSdyB2HyEm4CeGvkQxWsmInsIVA==", - "requires": { - "d3-dsv": "1" - } - }, - "d3-force": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.1.2.tgz", - "integrity": "sha512-p1vcHAUF1qH7yR+e8ip7Bs61AHjLeKkIn8Z2gzwU2lwEf2wkSpWdjXG0axudTHsVFnYGlMkFaEsVy2l8tAg1Gw==", - "requires": { - "d3-collection": "1", - "d3-dispatch": "1", - "d3-quadtree": "1", - "d3-timer": "1" - } - }, - "d3-format": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.3.2.tgz", - "integrity": "sha512-Z18Dprj96ExragQ0DeGi+SYPQ7pPfRMtUXtsg/ChVIKNBCzjO8XYJvRTC1usblx52lqge56V5ect+frYTQc8WQ==" - }, - "d3-geo": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.11.3.tgz", - "integrity": "sha512-n30yN9qSKREvV2fxcrhmHUdXP9TNH7ZZj3C/qnaoU0cVf/Ea85+yT7HY7i8ySPwkwjCNYtmKqQFTvLFngfkItQ==", - "requires": { - "d3-array": "1" - } - }, - "d3-hierarchy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.8.tgz", - "integrity": "sha512-L+GHMSZNwTpiq4rt9GEsNcpLa4M96lXMR8M/nMG9p5hBE0jy6C+3hWtyZMenPQdwla249iJy7Nx0uKt3n+u9+w==" - }, - "d3-interpolate": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.3.2.tgz", - "integrity": "sha512-NlNKGopqaz9qM1PXh9gBF1KSCVh+jSFErrSlD/4hybwoNX/gt1d8CDbDW+3i+5UOHhjC6s6nMvRxcuoMVNgL2w==", - "requires": { - "d3-color": "1" - } - }, - "d3-path": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.7.tgz", - "integrity": "sha512-q0cW1RpvA5c5ma2rch62mX8AYaiLX0+bdaSM2wxSU9tXjU4DNvkx9qiUvjkuWCj3p22UO/hlPivujqMiR9PDzA==" - }, - "d3-polygon": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.5.tgz", - "integrity": "sha512-RHhh1ZUJZfhgoqzWWuRhzQJvO7LavchhitSTHGu9oj6uuLFzYZVeBzaWTQ2qSO6bz2w55RMoOCf0MsLCDB6e0w==" - }, - "d3-quadtree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.5.tgz", - "integrity": "sha512-U2tjwDFbZ75JRAg8A+cqMvqPg1G3BE7UTJn3h8DHjY/pnsAfWdbJKgyfcy7zKjqGtLAmI0q8aDSeG1TVIKRaHQ==" - }, - "d3-random": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.2.tgz", - "integrity": "sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ==" - }, - "d3-scale": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.1.2.tgz", - "integrity": "sha512-bESpd64ylaKzCDzvULcmHKZTlzA/6DGSVwx7QSDj/EnX9cpSevsdiwdHFYI9ouo9tNBbV3v5xztHS2uFeOzh8Q==", - "requires": { - "d3-array": "^1.2.0", - "d3-collection": "1", - "d3-format": "1", - "d3-interpolate": "1", - "d3-time": "1", - "d3-time-format": "2" - } - }, - "d3-scale-chromatic": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-1.3.3.tgz", - "integrity": "sha512-BWTipif1CimXcYfT02LKjAyItX5gKiwxuPRgr4xM58JwlLocWbjPLI7aMEjkcoOQXMkYsmNsvv3d2yl/OKuHHw==", - "requires": { - "d3-color": "1", - "d3-interpolate": "1" - } - }, - "d3-selection": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.3.2.tgz", - "integrity": "sha512-OoXdv1nZ7h2aKMVg3kaUFbLLK5jXUFAMLD/Tu5JA96mjf8f2a9ZUESGY+C36t8R1WFeWk/e55hy54Ml2I62CRQ==" - }, - "d3-shape": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.2.2.tgz", - "integrity": "sha512-hUGEozlKecFZ2bOSNt7ENex+4Tk9uc/m0TtTEHBvitCBxUNjhzm5hS2GrrVRD/ae4IylSmxGeqX5tWC2rASMlQ==", - "requires": { - "d3-path": "1" - } - }, - "d3-time": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.0.10.tgz", - "integrity": "sha512-hF+NTLCaJHF/JqHN5hE8HVGAXPStEq6/omumPE/SxyHVrR7/qQxusFDo0t0c/44+sCGHthC7yNGFZIEgju0P8g==" - }, - "d3-time-format": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.1.3.tgz", - "integrity": "sha512-6k0a2rZryzGm5Ihx+aFMuO1GgelgIz+7HhB4PH4OEndD5q2zGn1mDfRdNrulspOfR6JXkb2sThhDK41CSK85QA==", - "requires": { - "d3-time": "1" - } - }, - "d3-timer": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.9.tgz", - "integrity": "sha512-rT34J5HnQUHhcLvhSB9GjCkN0Ddd5Y8nCwDBG2u6wQEeYxT/Lf51fTFFkldeib/sE/J0clIe0pnCfs6g/lRbyg==" - }, - "d3-transition": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.1.3.tgz", - "integrity": "sha512-tEvo3qOXL6pZ1EzcXxFcPNxC/Ygivu5NoBY6mbzidATAeML86da+JfVIUzon3dNM6UX6zjDx+xbYDmMVtTSjuA==", - "requires": { - "d3-color": "1", - "d3-dispatch": "1", - "d3-ease": "1", - "d3-interpolate": "1", - "d3-selection": "^1.1.0", - "d3-timer": "1" - } - }, - "d3-voronoi": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz", - "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==" - }, - "d3-zoom": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.7.3.tgz", - "integrity": "sha512-xEBSwFx5Z9T3/VrwDkMt+mr0HCzv7XjpGURJ8lWmIC8wxe32L39eWHIasEe/e7Ox8MPU4p1hvH8PKN2olLzIBg==", - "requires": { - "d3-dispatch": "1", - "d3-drag": "1", - "d3-interpolate": "1", - "d3-selection": "1", - "d3-transition": "1" - } - }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -5235,6 +5020,7 @@ "version": "0.4.23", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } @@ -8817,11 +8603,6 @@ "aproba": "^1.1.1" } }, - "rw": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" - }, "rxjs": { "version": "6.3.3", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", @@ -8848,7 +8629,8 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true }, "sass-graph": { "version": "2.2.4", diff --git a/web/angular/package.json b/web/angular/package.json index d3310db..2bbe0d5 100644 --- a/web/angular/package.json +++ b/web/angular/package.json @@ -21,8 +21,10 @@ "@angular/platform-browser": "~7.2.0", "@angular/platform-browser-dynamic": "~7.2.0", "@angular/router": "~7.2.0", + "@ngx-translate/core": "^11.0.1", + "@ngx-translate/http-loader": "^4.0.0", + "chart.js": "^2.7.3", "core-js": "^2.5.4", - "d3": "^5.7.0", "lodash": "^4.17.11", "moment": "^2.23.0", "rxjs": "~6.3.3", @@ -34,9 +36,9 @@ "@angular/cli": "~7.2.2", "@angular/compiler-cli": "~7.2.0", "@angular/language-service": "~7.2.0", - "@types/node": "~8.9.4", "@types/jasmine": "~2.8.8", "@types/jasminewd2": "~2.0.3", + "@types/node": "~8.9.4", "codelyzer": "~4.5.0", "jasmine-core": "~2.99.1", "jasmine-spec-reporter": "~4.2.1", diff --git a/web/angular/src/app/api.service.ts b/web/angular/src/app/api.service.ts index 4ed0249..4d60604 100755 --- a/web/angular/src/app/api.service.ts +++ b/web/angular/src/app/api.service.ts @@ -16,12 +16,8 @@ export class ApiService { 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") + return this.http.get(this.url + "/project/list") } getProject(id: number) { diff --git a/web/angular/src/app/app.component.css b/web/angular/src/app/app.component.css index e69de29..37aa71c 100755 --- a/web/angular/src/app/app.component.css +++ b/web/angular/src/app/app.component.css @@ -0,0 +1,3 @@ +.nav-spacer { + flex: 1 1 auto; +} diff --git a/web/angular/src/app/app.component.html b/web/angular/src/app/app.component.html index 0d02342..f8509a6 100755 --- a/web/angular/src/app/app.component.html +++ b/web/angular/src/app/app.component.html @@ -1,11 +1,23 @@ - + + {{ "nav.title" | translate }} + + favorite + delete + + + + {{lang.display}} + + + + + - diff --git a/web/angular/src/app/app.component.ts b/web/angular/src/app/app.component.ts index 77dfa3a..df8990d 100644 --- a/web/angular/src/app/app.component.ts +++ b/web/angular/src/app/app.component.ts @@ -1,4 +1,6 @@ import {Component} from '@angular/core'; +import {TranslateService} from "@ngx-translate/core"; +import {MatSelectChange} from "@angular/material"; @Component({ selector: 'app-root', @@ -6,5 +8,24 @@ import {Component} from '@angular/core'; styleUrls: ['./app.component.css'] }) export class AppComponent { - title = 'angular'; + + langChange(event: MatSelectChange) { + this.translate.use(event.value) + } + + langList: any[] = [ + {lang: "fr", display: "Français"}, + {lang: "en", display: "English"}, + ]; + + constructor(private translate: TranslateService) { + + translate.addLangs([ + "en", + "fr" + ]); + + translate.setDefaultLang("en"); + } + } diff --git a/web/angular/src/app/app.module.ts b/web/angular/src/app/app.module.ts index cd22cc0..9497248 100755 --- a/web/angular/src/app/app.module.ts +++ b/web/angular/src/app/app.module.ts @@ -18,6 +18,7 @@ import { MatInputModule, MatMenuModule, MatPaginatorModule, + MatSelectModule, MatSliderModule, MatSlideToggleModule, MatSnackBarModule, @@ -28,13 +29,21 @@ import { } from "@angular/material"; import {ApiService} from "./api.service"; import {MessengerService} from "./messenger.service"; -import {HttpClientModule} from "@angular/common/http"; +import {HttpClient, 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"; +import {TranslateLoader, TranslateModule} from "@ngx-translate/core"; +import {TranslateHttpLoader} from "@ngx-translate/http-loader"; + + +export function createTranslateLoader(http: HttpClient) { + return new TranslateHttpLoader(http, './assets/i18n/', '.json'); +} + @NgModule({ declarations: [ @@ -71,6 +80,15 @@ import {SnackBarComponent} from "./messenger/snack-bar.component"; MatCheckboxModule, MatDividerModule, MatSnackBarModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: (createTranslateLoader), + deps: [HttpClient] + } + } + ), + MatSelectModule ], exports: [], diff --git a/web/angular/src/app/create-project/create-project.component.ts b/web/angular/src/app/create-project/create-project.component.ts index 07e972e..379bcd8 100644 --- a/web/angular/src/app/create-project/create-project.component.ts +++ b/web/angular/src/app/create-project/create-project.component.ts @@ -12,7 +12,7 @@ import {Router} from "@angular/router"; }) export class CreateProjectComponent implements OnInit { - private project = new Project(); + project = new Project(); constructor(private apiService: ApiService, private messengerService: MessengerService, diff --git a/web/angular/src/app/logs/logs.component.ts b/web/angular/src/app/logs/logs.component.ts index 04ef8a8..5bd22c0 100644 --- a/web/angular/src/app/logs/logs.component.ts +++ b/web/angular/src/app/logs/logs.component.ts @@ -12,9 +12,9 @@ import {MatPaginator, MatSort, MatTableDataSource} from "@angular/material"; }) export class LogsComponent implements OnInit { - private logs: LogEntry[] = []; - private data: MatTableDataSource; - private logsCols: string[] = ["level", "timestamp", "message", "data"]; + logs: LogEntry[] = []; + data: MatTableDataSource; + logsCols: string[] = ["level", "timestamp", "message", "data"]; @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; 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 65fb135..9e51d97 100644 --- a/web/angular/src/app/project-dashboard/project-dashboard.component.ts +++ b/web/angular/src/app/project-dashboard/project-dashboard.component.ts @@ -1,10 +1,5 @@ import {Component, OnInit} from '@angular/core'; -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', @@ -16,269 +11,6 @@ export class ProjectDashboardComponent implements OnInit { private projectId; projectStats; - private pieWidth = 360; - private pieHeight = 360; - private pieRadius = Math.min(this.pieWidth, this.pieHeight) / 2; - private pieArc = d3.arc() - .innerRadius(this.pieRadius / 2) - .outerRadius(this.pieRadius); - - private pieFun = d3.pie().value((d) => d.count); - private statusColor = d3.scaleOrdinal().range(['#31a6a2', '#8c2627', '#62f24b']); - private assigneesColor = d3.scaleOrdinal().range(["", "#AAAAAA"].concat(d3.schemePaired)); - - private statusData: any[]; - private assigneesData: any[]; - - private newTaskCounts: any[] = []; - private failedTaskCounts: any[] = []; - private closedTaskCounts: any[] = []; - - private newTaskPath: any; - private failedTaskPath: any; - private closedTaskPath: any; - - private maxY: number = 10; - private yAxis: any; - private yScale: any; - private xScale: any; - private range: number; - private line: any; - - private statusPath: any; - private statusSvg: any; - private assigneesPath: any; - private assigneesSvg: any; - - constructor(private apiService: ApiService, private route: ActivatedRoute) { - } - - setupStatusPieChart() { - let tooltip = d3.select("#stooltip"); - - 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() - .data(this.pieFun(this.statusData)) - .enter() - .append('path') - .attr('d', this.pieArc) - .attr('fill', (d) => this.statusColor(d.data.label)); - - this.setupToolTip(this.statusPath, tooltip) - } - - setupAssigneesPieChart() { - let tooltip = d3.select("#atooltip"); - - 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() - .data(this.pieFun(this.assigneesData)) - .enter() - .append('path') - .attr('d', this.pieArc) - .attr('fill', (d) => this.assigneesColor(d.data.label)); - - this.setupToolTip(this.assigneesPath, tooltip) - } - - setupToolTip(x, tooltip) { - x.on('mouseover', (d) => { - let total = d3.sum(this.assigneesData.map((d) => d.count)); - let percent = Math.round(1000 * d.data.count / total) / 10; - tooltip.select('.label').html(d.data.label); - tooltip.select('.count').html(d.data.count); - tooltip.select('.percent').html(percent + '%'); - tooltip.style('display', 'block'); - }); - x.on('mouseout', function () { - tooltip.style('display', 'none'); - }) - } - - setupLine() { - let margin = {top: 50, right: 50, bottom: 50, left: 50}; - this.range = 600; - let width = 750; - let height = 250; - - this.xScale = d3.scaleLinear() - .domain([this.range, 0]) - .range([width, 0]); - - this.yScale = d3.scaleLinear() - .domain([0, this.maxY]) - .range([height, 0]); - - this.line = d3.line() - .x((d, i) => this.xScale(i)) - .y((d) => this.yScale(d.y)) - .curve(d3.curveMonotoneX); - - let svg = d3.select("#line").append("svg") - .attr("width", width + margin.left + margin.right) - .attr("height", height + margin.top + margin.bottom) - .append("g") - .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); - - svg.append("defs").append("clipPath") - .attr("id", "clip") - .append("rect") - .attr("width", width) - .attr("height", height); - - this.newTaskPath = svg - .append("path") - .attr("clip-path", "url(#clip)") - .datum(this.newTaskCounts) - .attr("class", "line-new") - .attr("d", this.line); - this.failedTaskPath = svg - .append("path") - .attr("clip-path", "url(#clip)") - .datum(this.failedTaskCounts) - .attr("class", "line-failed") - .attr("d", this.line); - this.closedTaskPath = svg - .append("path") - .attr("clip-path", "url(#clip)") - .datum(this.closedTaskCounts) - .attr("class", "line-closed") - .attr("d", this.line); - - let xAxis = svg.append("g") - .attr("class", "x-axis") - .attr("transform", "translate(0," + height + ")"); - xAxis.call(d3.axisBottom(this.xScale).tickFormat((d) => (d - 600) + "s")); - - this.yAxis = svg.append("g") - .attr("class", "y-axis"); - this.yAxis.call(d3.axisLeft(this.yScale)); - } - - getStats() { - this.apiService.getProjectStats(this.projectId).subscribe((data) => { - - this.projectStats = data["stats"]; - - this.updateLine(); - this.updatePie(); - }); - } - - private updateLine() { - - let newVal = {"y": this.projectStats["new_task_count"]}; - let failedVal = {"y": this.projectStats["failed_task_count"]}; - let closedVal = {"y": this.projectStats["closed_task_count"]}; - - //Adjust y axis - this.maxY = Math.max(newVal["y"], this.maxY); - this.yScale.domain([0, this.maxY]); - this.yAxis.call(d3.axisLeft(this.yScale)); - - this.newTaskPath - .attr("d", this.line) - .attr("transform", null); - this.failedTaskPath - .attr("d", this.line) - .attr("transform", null); - this.closedTaskPath - .attr("d", this.line) - .attr("transform", null); - - //remove fist element - if (this.newTaskCounts.length >= this.range) { - this.newTaskCounts.shift(); - this.newTaskPath - .transition() - .attr("transform", "translate(" + this.xScale(-1) + ")"); - } - if (this.failedTaskCounts.length >= this.range) { - this.failedTaskCounts.shift(); - this.failedTaskPath - .transition() - .attr("transform", "translate(" + this.xScale(-1) + ")"); - } - if (this.closedTaskCounts.length >= this.range) { - this.closedTaskCounts.shift(); - this.closedTaskPath - .transition() - .attr("transform", "translate(" + this.xScale(-1) + ")"); - } - - this.newTaskCounts.push(newVal); - this.failedTaskCounts.push(failedVal); - this.closedTaskCounts.push(closedVal); - } - - private updatePie() { - this.statusData = [ - {label: "New", count: this.projectStats["new_task_count"]}, - {label: "Failed", count: this.projectStats["failed_task_count"]}, - {label: "Closed", count: this.projectStats["closed_task_count"]}, - ]; - this.assigneesData = _.map(this.projectStats["assignees"], assignedTask => { - return { - label: assignedTask["assignee"], - count: assignedTask["task_count"], - } - }); - - this.statusSvg.selectAll("path") - .data(this.pieFun(this.statusData)); - this.statusPath - .attr('d', this.pieArc) - .attr('fill', (d) => this.statusColor(d.data.label)); - this.assigneesSvg.selectAll("path") - .data(this.pieFun(this.assigneesData)); - this.assigneesPath - .attr('d', this.pieArc) - .attr('fill', (d) => this.assigneesColor(d.data.label)); - } - - ngOnInit() { - this.statusData = [ - {label: 'new', count: 0}, - {label: 'failed', count: 0}, - {label: 'closed', count: 0}, - ]; - 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(); - this.setupAssigneesPieChart(); - this.setupLine(); - - this.route.params.subscribe(params => { - this.projectId = params["id"]; - this.getStats(); - interval(1000).subscribe(() => { - // this.getStats() - }) - } - ) + ngOnInit(): void { } } diff --git a/web/angular/src/app/project-list/project-list.component.html b/web/angular/src/app/project-list/project-list.component.html index 70e5669..c8fce03 100755 --- a/web/angular/src/app/project-list/project-list.component.html +++ b/web/angular/src/app/project-list/project-list.component.html @@ -4,15 +4,15 @@ - + - {{stats.project.id}}: {{stats.project.name}} - {{stats.project.motd}} + {{project.id}}: {{project.name}} + {{project.motd}} -
{{stats.project | json}}
+
{{project | json}}
diff --git a/web/angular/src/app/project-list/project-list.component.ts b/web/angular/src/app/project-list/project-list.component.ts index 3f44fef..99f3273 100755 --- a/web/angular/src/app/project-list/project-list.component.ts +++ b/web/angular/src/app/project-list/project-list.component.ts @@ -1,5 +1,6 @@ import {Component, OnInit} from '@angular/core'; import {ApiService} from "../api.service"; +import {Project} from "../models/project"; @Component({ selector: 'app-project-list', @@ -11,14 +12,14 @@ export class ProjectListComponent implements OnInit { constructor(private apiService: ApiService) { } - projects: any[]; + projects: Project[]; ngOnInit() { this.getProjects() } getProjects() { - this.apiService.getProjects().subscribe(data => this.projects = data["stats"]); + this.apiService.getProjects().subscribe(data => this.projects = data["projects"]); } } diff --git a/web/angular/src/app/update-project/update-project.component.ts b/web/angular/src/app/update-project/update-project.component.ts index 49135a9..119eef1 100644 --- a/web/angular/src/app/update-project/update-project.component.ts +++ b/web/angular/src/app/update-project/update-project.component.ts @@ -17,7 +17,7 @@ export class UpdateProjectComponent implements OnInit { private router: Router) { } - private project: Project; + project: Project; private projectId: number; ngOnInit() { diff --git a/web/angular/src/assets/i18n/en.json b/web/angular/src/assets/i18n/en.json new file mode 100644 index 0000000..65bb994 --- /dev/null +++ b/web/angular/src/assets/i18n/en.json @@ -0,0 +1,6 @@ +{ + "nav": { + "title": "task_tracker", + "langSelect": "Language" + } +} diff --git a/web/angular/src/assets/i18n/fr.json b/web/angular/src/assets/i18n/fr.json new file mode 100644 index 0000000..d5df126 --- /dev/null +++ b/web/angular/src/assets/i18n/fr.json @@ -0,0 +1,6 @@ +{ + "nav": { + "title": "task_tracker (fr)", + "langSelect": "Langue" + } +}