Merge pull request #207 from wemeya/master

add statistics api for projects and repositories
This commit is contained in:
Wenkai Yin 2016-05-19 12:06:22 +08:00
commit 91ef155da5
7 changed files with 222 additions and 92 deletions

View File

@ -113,23 +113,50 @@ func (p *ProjectAPI) Head() {
// Get ...
func (p *ProjectAPI) Get() {
queryProject := models.Project{UserID: p.userID}
var projectList []models.Project
projectName := p.GetString("project_name")
if len(projectName) > 0 {
queryProject.Name = "%" + projectName + "%"
projectName = "%" + projectName + "%"
}
var public int
var err error
isPublic := p.GetString("is_public")
if len(isPublic) > 0 {
public, err = strconv.Atoi(isPublic)
if err != nil {
log.Errorf("Error parsing public property: %d, error: %v", isPublic, err)
p.CustomAbort(http.StatusBadRequest, "invalid project Id")
}
}
isAdmin := false
if public == 1 {
projectList, err = dao.GetPublicProjects(projectName)
} else {
isAdmin, err = dao.IsAdminRole(p.userID)
if err != nil {
log.Errorf("Error occured in check admin, error: %v", err)
p.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if isAdmin {
projectList, err = dao.GetAllProjects(projectName)
} else {
projectList, err = dao.GetUserRelevantProjects(p.userID, projectName)
}
}
public, _ := p.GetInt("is_public")
queryProject.Public = public
projectList, err := dao.QueryProject(queryProject)
if err != nil {
log.Errorf("Error occurred in QueryProject, error: %v", err)
log.Errorf("Error occured in get projects info, error: %v", err)
p.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
for i := 0; i < len(projectList); i++ {
if isProjectAdmin(p.userID, projectList[i].ProjectID) {
projectList[i].Togglable = true
if public != 1 {
if isAdmin {
projectList[i].Role = models.PROJECTADMIN
}
if projectList[i].Role == models.PROJECTADMIN {
projectList[i].Togglable = true
}
}
projectList[i].RepoCount = getRepoCountByProject(projectList[i].Name)
}
p.Data["json"] = projectList
p.ServeJSON()

View File

@ -55,13 +55,13 @@ func (s *SearchAPI) Get() {
var projects []models.Project
if isSysAdmin {
projects, err = dao.GetAllProjects()
projects, err = dao.GetAllProjects("")
if err != nil {
log.Errorf("failed to get all projects: %v", err)
s.CustomAbort(http.StatusInternalServerError, "internal error")
}
} else {
projects, err = dao.GetUserRelevantProjects(userID)
projects, err = dao.SearchProjects(userID)
if err != nil {
log.Errorf("failed to get user %d 's relevant projects: %v", userID, err)
s.CustomAbort(http.StatusInternalServerError, "internal error")

117
api/statistic.go Normal file
View File

@ -0,0 +1,117 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
import (
"net/http"
"strings"
"github.com/vmware/harbor/dao"
"github.com/vmware/harbor/models"
svc_utils "github.com/vmware/harbor/service/utils"
"github.com/vmware/harbor/utils/log"
)
// StatisticAPI handles request to /api/statistics/
type StatisticAPI struct {
BaseAPI
userID int
}
//Prepare validates the URL and the user
func (s *StatisticAPI) Prepare() {
s.userID = s.ValidateUser()
}
// Get total projects and repos of the user
func (s *StatisticAPI) Get() {
isAdmin, err := dao.IsAdminRole(s.userID)
if err != nil {
log.Errorf("Error occured in check admin, error: %v", err)
s.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
var projectList []models.Project
if isAdmin {
projectList, err = dao.GetAllProjects("")
} else {
projectList, err = dao.GetUserRelevantProjects(s.userID, "")
}
if err != nil {
log.Errorf("Error occured in QueryProject, error: %v", err)
s.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
proMap := map[string]int{}
proMap["my_project_count"] = 0
proMap["my_repo_count"] = 0
proMap["public_project_count"] = 0
proMap["public_repo_count"] = 0
var publicProjects []models.Project
publicProjects, err = dao.GetPublicProjects("")
if err != nil {
log.Errorf("Error occured in QueryPublicProject, error: %v", err)
s.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
proMap["public_project_count"] = len(publicProjects)
for i := 0; i < len(publicProjects); i++ {
proMap["public_repo_count"] += getRepoCountByProject(publicProjects[i].Name)
}
if isAdmin {
proMap["total_project_count"] = len(projectList)
proMap["total_repo_count"] = getTotalRepoCount()
}
for i := 0; i < len(projectList); i++ {
if isAdmin {
projectList[i].Role = models.PROJECTADMIN
}
if projectList[i].Role == models.PROJECTADMIN || projectList[i].Role == models.DEVELOPER ||
projectList[i].Role == models.GUEST {
proMap["my_project_count"]++
proMap["my_repo_count"] += getRepoCountByProject(projectList[i].Name)
}
}
s.Data["json"] = proMap
s.ServeJSON()
}
//getReposByProject returns repo numbers of specified project
func getRepoCountByProject(projectName string) int {
repoList, err := svc_utils.GetRepoFromCache()
if err != nil {
log.Errorf("Failed to get repo from cache, error: %v", err)
return 0
}
var resp int
if len(projectName) > 0 {
for _, r := range repoList {
if strings.Contains(r, "/") && r[0:strings.LastIndex(r, "/")] == projectName {
resp++
}
}
return resp
}
return 0
}
//getTotalRepoCount returns total repo count
func getTotalRepoCount() int {
repoList, err := svc_utils.GetRepoFromCache()
if err != nil {
log.Errorf("Failed to get repo from cache, error: %v", err)
return 0
}
return len(repoList)
}

View File

@ -353,7 +353,7 @@ func TestChangeUserPasswordWithIncorrectOldPassword(t *testing.T) {
}
func TestQueryRelevantProjectsWhenNoProjectAdded(t *testing.T) {
projects, err := GetUserRelevantProjects(currentUser.UserID)
projects, err := SearchProjects(currentUser.UserID)
if err != nil {
t.Errorf("Error occurred in QueryRelevantProjects: %v", err)
}
@ -572,39 +572,6 @@ func TestIsProjectPublic(t *testing.T) {
}
}
func TestQueryProject(t *testing.T) {
query1 := models.Project{
UserID: 1,
}
projects, err := QueryProject(query1)
if err != nil {
t.Errorf("Error in Query Project: %v, query: %+v", err, query1)
}
if len(projects) != 2 {
t.Errorf("Expecting get 2 projects, but actual: %d, the list: %+v", len(projects), projects)
}
query2 := models.Project{
Public: 1,
}
projects, err = QueryProject(query2)
if err != nil {
t.Errorf("Error in Query Project: %v, query: %+v", err, query2)
}
if len(projects) != 1 {
t.Errorf("Expecting get 1 project, but actual: %d, the list: %+v", len(projects), projects)
}
query3 := models.Project{
UserID: 9,
}
projects, err = QueryProject(query3)
if err != nil {
t.Errorf("Error in Query Project: %v, query: %+v", err, query3)
}
if len(projects) != 0 {
t.Errorf("Expecting get 0 project, but actual: %d, the list: %+v", len(projects), projects)
}
}
func TestGetUserProjectRoles(t *testing.T) {
r, err := GetUserProjectRoles(currentUser.UserID, currentProject.ProjectID)
if err != nil {
@ -632,20 +599,20 @@ func TestProjectPermission(t *testing.T) {
}
func TestGetUserRelevantProjects(t *testing.T) {
projects, err := GetUserRelevantProjects(currentUser.UserID)
projects, err := GetUserRelevantProjects(currentUser.UserID, "")
if err != nil {
t.Errorf("Error occurred in GetUserRelevantProjects: %v", err)
}
if len(projects) != 2 {
t.Errorf("Expected length of relevant projects is 2, but actual: %d, the projects: %+v", len(projects), projects)
if len(projects) != 1 {
t.Errorf("Expected length of relevant projects is 1, but actual: %d, the projects: %+v", len(projects), projects)
}
if projects[1].Name != projectName {
if projects[0].Name != projectName {
t.Errorf("Expected project name in the list: %s, actual: %s", projectName, projects[1].Name)
}
}
func TestGetAllProjects(t *testing.T) {
projects, err := GetAllProjects()
projects, err := GetAllProjects("")
if err != nil {
t.Errorf("Error occurred in GetAllProjects: %v", err)
}

View File

@ -79,42 +79,6 @@ func IsProjectPublic(projectName string) bool {
return project.Public == 1
}
// QueryProject querys the projects based on publicity and user, disregarding the names etc.
func QueryProject(query models.Project) ([]models.Project, error) {
o := orm.NewOrm()
sql := `select distinct
p.project_id, p.owner_id, p.name,p.creation_time, p.update_time, p.public
from project p
left join project_member pm on p.project_id = pm.project_id
where p.deleted = 0 `
queryParam := make([]interface{}, 1)
if query.Public == 1 {
sql += ` and p.public = ?`
queryParam = append(queryParam, query.Public)
} else if isAdmin, _ := IsAdminRole(query.UserID); isAdmin == false {
sql += ` and (pm.user_id = ?) `
queryParam = append(queryParam, query.UserID)
}
if query.Name != "" {
sql += " and p.name like ? "
queryParam = append(queryParam, query.Name)
}
sql += " order by p.name "
var r []models.Project
_, err := o.Raw(sql, queryParam).QueryRows(&r)
if err != nil {
return nil, err
}
return r, nil
}
//ProjectExists returns whether the project exists according to its name of ID.
func ProjectExists(nameOrID interface{}) (bool, error) {
o := orm.NewOrm()
@ -208,11 +172,11 @@ func ToggleProjectPublicity(projectID int64, publicity int) error {
return err
}
// GetUserRelevantProjects returns a project list,
// SearchProjects returns a project list,
// which satisfies the following conditions:
// 1. the project is not deleted
// 2. the prject is public or the user is a member of the project
func GetUserRelevantProjects(userID int) ([]models.Project, error) {
func SearchProjects(userID int) ([]models.Project, error) {
o := orm.NewOrm()
sql := `select distinct p.project_id, p.name, p.public
from project p
@ -228,14 +192,66 @@ func GetUserRelevantProjects(userID int) ([]models.Project, error) {
return projects, nil
}
// GetAllProjects returns all projects which are not deleted
func GetAllProjects() ([]models.Project, error) {
// GetUserRelevantProjects returns the projects of the user which are not deleted and name like projectName
func GetUserRelevantProjects(userID int, projectName string) ([]models.Project, error) {
o := orm.NewOrm()
sql := `select project_id, name, public
sql := `select distinct
p.project_id, p.owner_id, p.name,p.creation_time, p.update_time, p.public, pm.role role
from project p
left join project_member pm on p.project_id = pm.project_id
where p.deleted = 0 and pm.user_id= ?`
queryParam := make([]interface{}, 1)
queryParam = append(queryParam, userID)
if projectName != "" {
sql += " and p.name like ? "
queryParam = append(queryParam, projectName)
}
sql += " order by p.name "
var r []models.Project
_, err := o.Raw(sql, queryParam).QueryRows(&r)
if err != nil {
return nil, err
}
return r, nil
}
//GetPublicProjects returns all public projects whose name like projectName
func GetPublicProjects(projectName string) ([]models.Project, error) {
publicProjects, err := getProjects(1, projectName)
if err != nil {
return nil, err
}
return publicProjects, nil
}
// GetAllProjects returns all projects which are not deleted and name like projectName
func GetAllProjects(projectName string) ([]models.Project, error) {
allProjects, err := getProjects(0, projectName)
if err != nil {
return nil, err
}
return allProjects, nil
}
func getProjects(public int, projectName string) ([]models.Project, error) {
o := orm.NewOrm()
sql := `select project_id, owner_id, creation_time, update_time, name, public
from project
where deleted = 0`
queryParam := make([]interface{}, 1)
if public == 1 {
sql += "and public = ?"
queryParam = append(queryParam, public)
}
if len(projectName) > 0 {
sql += " and name like ? "
queryParam = append(queryParam, projectName)
}
sql += " order by name "
var projects []models.Project
if _, err := o.Raw(sql).QueryRows(&projects); err != nil {
if _, err := o.Raw(sql, queryParam).QueryRows(&projects); err != nil {
return nil, err
}
return projects, nil

View File

@ -34,4 +34,6 @@ type Project struct {
Togglable bool
UpdateTime time.Time `orm:"update_time" json:"update_time"`
Role int `json:"role_id"`
RepoCount int `json:"repo_count"`
}

View File

@ -54,6 +54,7 @@ func initRouters() {
beego.Router("/api/search", &api.SearchAPI{})
beego.Router("/api/projects/:pid/members/?:mid", &api.ProjectMemberAPI{})
beego.Router("/api/projects/?:id", &api.ProjectAPI{})
beego.Router("/api/statistics", &api.StatisticAPI{})
beego.Router("/api/projects/:id/logs/filter", &api.ProjectAPI{}, "post:FilterAccessLog")
beego.Router("/api/users", &api.UserAPI{})
beego.Router("/api/users/?:id", &api.UserAPI{})