new user dahsboard issues
This commit is contained in:
		
							parent
							
								
									be90ea583a
								
							
						
					
					
						commit
						f808df5a7b
					
				
					 16 changed files with 427 additions and 258 deletions
				
			
		|  | @ -124,6 +124,8 @@ collaborative_repos = Collaborative Repositories | |||
| my_orgs = My Organizations | ||||
| my_mirrors = My Mirrors | ||||
| 
 | ||||
| issues.in_your_repos = In your repositories | ||||
| 
 | ||||
| [explore] | ||||
| repos = Repositories | ||||
| 
 | ||||
|  |  | |||
|  | @ -275,6 +275,26 @@ | |||
| 		"strictMath": 0, | ||||
| 		"strictUnits": 0 | ||||
| 		}, | ||||
| 	"\/public\/less\/_dashboard.less": { | ||||
| 		"allowInsecureImports": 0, | ||||
| 		"createSourceMap": 0, | ||||
| 		"disableJavascript": 0, | ||||
| 		"fileType": 1, | ||||
| 		"ieCompatibility": 1, | ||||
| 		"ignore": 1, | ||||
| 		"ignoreWasSetByUser": 0, | ||||
| 		"inputAbbreviatedPath": "\/public\/less\/_dashboard.less", | ||||
| 		"outputAbbreviatedPath": "\/public\/css\/_dashboard.css", | ||||
| 		"outputPathIsOutsideProject": 0, | ||||
| 		"outputPathIsSetByUser": 0, | ||||
| 		"outputStyle": 0, | ||||
| 		"relativeURLS": 0, | ||||
| 		"shouldRunAutoprefixer": 0, | ||||
| 		"shouldRunBless": 0, | ||||
| 		"strictImports": 0, | ||||
| 		"strictMath": 0, | ||||
| 		"strictUnits": 0 | ||||
| 		}, | ||||
| 	"\/public\/less\/_form.less": { | ||||
| 		"allowInsecureImports": 0, | ||||
| 		"createSourceMap": 0, | ||||
|  |  | |||
|  | @ -641,9 +641,8 @@ func parseCountResult(results []map[string][]byte) int64 { | |||
| } | ||||
| 
 | ||||
| // GetIssueStats returns issue statistic information by given conditions.
 | ||||
| func GetIssueStats(repoID, uid, labelID, milestoneID, assigneeID int64, isShowClosed bool, filterMode int) *IssueStats { | ||||
| func GetIssueStats(repoID, uid, labelID, milestoneID, assigneeID int64, filterMode int) *IssueStats { | ||||
| 	stats := &IssueStats{} | ||||
| 	// issue := new(Issue)
 | ||||
| 
 | ||||
| 	queryStr := "SELECT COUNT(*) FROM `issue` " | ||||
| 	if labelID > 0 { | ||||
|  | @ -659,38 +658,75 @@ func GetIssueStats(repoID, uid, labelID, milestoneID, assigneeID int64, isShowCl | |||
| 	} | ||||
| 	switch filterMode { | ||||
| 	case FM_ALL, FM_ASSIGN: | ||||
| 		resutls, _ := x.Query(queryStr+baseCond, repoID, false) | ||||
| 		stats.OpenCount = parseCountResult(resutls) | ||||
| 		resutls, _ = x.Query(queryStr+baseCond, repoID, true) | ||||
| 		stats.ClosedCount = parseCountResult(resutls) | ||||
| 		results, _ := x.Query(queryStr+baseCond, repoID, false) | ||||
| 		stats.OpenCount = parseCountResult(results) | ||||
| 		results, _ = x.Query(queryStr+baseCond, repoID, true) | ||||
| 		stats.ClosedCount = parseCountResult(results) | ||||
| 
 | ||||
| 	case FM_CREATE: | ||||
| 		baseCond += " AND poster_id=?" | ||||
| 		resutls, _ := x.Query(queryStr+baseCond, repoID, false, uid) | ||||
| 		stats.OpenCount = parseCountResult(resutls) | ||||
| 		resutls, _ = x.Query(queryStr+baseCond, repoID, true, uid) | ||||
| 		stats.ClosedCount = parseCountResult(resutls) | ||||
| 		results, _ := x.Query(queryStr+baseCond, repoID, false, uid) | ||||
| 		stats.OpenCount = parseCountResult(results) | ||||
| 		results, _ = x.Query(queryStr+baseCond, repoID, true, uid) | ||||
| 		stats.ClosedCount = parseCountResult(results) | ||||
| 
 | ||||
| 	case FM_MENTION: | ||||
| 		queryStr += " INNER JOIN `issue_user` ON `issue`.id=`issue_user`.issue_id" | ||||
| 		baseCond += " AND `issue_user`.uid=? AND `issue_user`.is_mentioned=?" | ||||
| 		resutls, _ := x.Query(queryStr+baseCond, repoID, false, uid, true) | ||||
| 		stats.OpenCount = parseCountResult(resutls) | ||||
| 		resutls, _ = x.Query(queryStr+baseCond, repoID, true, uid, true) | ||||
| 		stats.ClosedCount = parseCountResult(resutls) | ||||
| 		results, _ := x.Query(queryStr+baseCond, repoID, false, uid, true) | ||||
| 		stats.OpenCount = parseCountResult(results) | ||||
| 		results, _ = x.Query(queryStr+baseCond, repoID, true, uid, true) | ||||
| 		stats.ClosedCount = parseCountResult(results) | ||||
| 	} | ||||
| 	return stats | ||||
| } | ||||
| 
 | ||||
| // GetUserIssueStats returns issue statistic information for dashboard by given conditions.
 | ||||
| func GetUserIssueStats(uid int64, filterMode int) *IssueStats { | ||||
| func GetUserIssueStats(repoID, uid int64, filterMode int) *IssueStats { | ||||
| 	stats := &IssueStats{} | ||||
| 	issue := new(Issue) | ||||
| 	stats.AssignCount, _ = x.Where("assignee_id=?", uid).And("is_closed=?", false).Count(issue) | ||||
| 	stats.CreateCount, _ = x.Where("poster_id=?", uid).And("is_closed=?", false).Count(issue) | ||||
| 
 | ||||
| 	queryStr := "SELECT COUNT(*) FROM `issue` " | ||||
| 	baseCond := " WHERE issue.is_closed=?" | ||||
| 	if repoID > 0 { | ||||
| 		baseCond += " AND issue.repo_id=" + com.ToStr(repoID) | ||||
| 	} | ||||
| 	switch filterMode { | ||||
| 	case FM_ASSIGN: | ||||
| 		baseCond += " AND assignee_id=" + com.ToStr(uid) | ||||
| 
 | ||||
| 	case FM_CREATE: | ||||
| 		baseCond += " AND poster_id=" + com.ToStr(uid) | ||||
| 	} | ||||
| 
 | ||||
| 	results, _ := x.Query(queryStr+baseCond, false) | ||||
| 	stats.OpenCount = parseCountResult(results) | ||||
| 	results, _ = x.Query(queryStr+baseCond, true) | ||||
| 	stats.ClosedCount = parseCountResult(results) | ||||
| 	return stats | ||||
| } | ||||
| 
 | ||||
| // GetRepoIssueStats returns number of open and closed repository issues by given filter mode.
 | ||||
| func GetRepoIssueStats(repoID, uid int64, filterMode int) (numOpen int64, numClosed int64) { | ||||
| 	queryStr := "SELECT COUNT(*) FROM `issue` " | ||||
| 	baseCond := " WHERE issue.repo_id=? AND issue.is_closed=?" | ||||
| 	switch filterMode { | ||||
| 	case FM_ASSIGN: | ||||
| 		baseCond += " AND assignee_id=" + com.ToStr(uid) | ||||
| 
 | ||||
| 	case FM_CREATE: | ||||
| 		baseCond += " AND poster_id=" + com.ToStr(uid) | ||||
| 	} | ||||
| 
 | ||||
| 	results, _ := x.Query(queryStr+baseCond, repoID, false) | ||||
| 	numOpen = parseCountResult(results) | ||||
| 	results, _ = x.Query(queryStr+baseCond, repoID, true) | ||||
| 	numClosed = parseCountResult(results) | ||||
| 	return numOpen, numClosed | ||||
| } | ||||
| 
 | ||||
| func updateIssue(e Engine, issue *Issue) error { | ||||
| 	_, err := e.Id(issue.ID).AllCols().Update(issue) | ||||
| 	return err | ||||
|  |  | |||
|  | @ -221,6 +221,11 @@ func (repo *Repository) GetMilestoneByID(milestoneID int64) (*Milestone, error) | |||
| 	return GetRepoMilestoneByID(repo.ID, milestoneID) | ||||
| } | ||||
| 
 | ||||
| // IssueStats returns number of open and closed repository issues by given filter mode.
 | ||||
| func (repo *Repository) IssueStats(uid int64, filterMode int) (int64, int64) { | ||||
| 	return GetRepoIssueStats(repo.ID, uid, filterMode) | ||||
| } | ||||
| 
 | ||||
| func (repo *Repository) GetMirror() (err error) { | ||||
| 	repo.Mirror, err = GetMirror(repo.ID) | ||||
| 	return err | ||||
|  |  | |||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								public/css/gogs.min.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								public/css/gogs.min.css
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										20
									
								
								public/less/_dashboard.less
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								public/less/_dashboard.less
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | |||
| .dashboard { | ||||
| 	padding-top: 15px; | ||||
| 	padding-bottom: @footer-margin * 2; | ||||
| 
 | ||||
| 	&.issues { | ||||
| 		.context.user.menu { | ||||
| 			min-width: 200px; | ||||
| 			.ui.header { | ||||
| 		    font-size: 1rem; | ||||
| 		    text-transform: none; | ||||
| 			} | ||||
| 		} | ||||
| 		.filter.menu { | ||||
| 			.item.active { | ||||
| 		    background-color: #4183c4; | ||||
| 		    color: #FFF; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -79,47 +79,6 @@ | |||
| 	 	} | ||||
| 	} | ||||
| 
 | ||||
| 	.page.buttons { | ||||
| 		padding-top: 15px; | ||||
| 	} | ||||
| 
 | ||||
| 	.issue.list { | ||||
| 		list-style: none; | ||||
| 		padding-top: 15px; | ||||
| 		>.item { | ||||
| 			padding-top: 15px; | ||||
| 			padding-bottom: 10px; | ||||
| 			border-bottom: 1px dashed #AAA; | ||||
| 			.title { | ||||
| 				color: #444; | ||||
| 				font-size: 15px; | ||||
| 				font-weight: bold; | ||||
| 				margin: 0 6px; | ||||
| 				&:hover { | ||||
| 					color: #000; | ||||
| 				} | ||||
| 			} | ||||
| 			.comment { | ||||
| 				padding-right: 10px; | ||||
| 				color: #666; | ||||
| 			} | ||||
| 			.desc { | ||||
| 				padding-top: 5px; | ||||
| 				color: #999; | ||||
| 				a.milestone { | ||||
| 					padding-left: 5px; | ||||
| 					color: #999!important; | ||||
| 					&:hover { | ||||
| 						color: #000!important; | ||||
| 					} | ||||
| 				} | ||||
| 				.assignee { | ||||
| 					margin-top: -5px; | ||||
| 					margin-right: 5px; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	@comment-avatar-width: 3em; | ||||
| 	&.new.issue { | ||||
| 		.comment.form { | ||||
|  | @ -607,6 +566,48 @@ | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| .issue.list { | ||||
| 	list-style: none; | ||||
| 	padding-top: 15px; | ||||
| 	>.item { | ||||
| 		padding-top: 15px; | ||||
| 		padding-bottom: 10px; | ||||
| 		border-bottom: 1px dashed #AAA; | ||||
| 		.title { | ||||
| 			color: #444; | ||||
| 			font-size: 15px; | ||||
| 			font-weight: bold; | ||||
| 			margin: 0 6px; | ||||
| 			&:hover { | ||||
| 				color: #000; | ||||
| 			} | ||||
| 		} | ||||
| 		.comment { | ||||
| 			padding-right: 10px; | ||||
| 			color: #666; | ||||
| 		} | ||||
| 		.desc { | ||||
| 			padding-top: 5px; | ||||
| 			color: #999; | ||||
| 			a.milestone { | ||||
| 				padding-left: 5px; | ||||
| 				color: #999!important; | ||||
| 				&:hover { | ||||
| 					color: #000!important; | ||||
| 				} | ||||
| 			} | ||||
| 			.assignee { | ||||
| 				margin-top: -5px; | ||||
| 				margin-right: 5px; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| .page.buttons { | ||||
| 	padding-top: 15px; | ||||
| } | ||||
| 
 | ||||
| .ui.comments { | ||||
| 	.dropzone { | ||||
| 		width: 100%;  | ||||
|  |  | |||
|  | @ -6,4 +6,5 @@ | |||
| @import "_form"; | ||||
| @import "_repository"; | ||||
| @import "_user"; | ||||
| @import "_dashboard"; | ||||
| @import "_admin"; | ||||
|  | @ -79,11 +79,11 @@ func Issues(ctx *middleware.Context) { | |||
| 	filterMode := models.FM_ALL | ||||
| 	switch viewType { | ||||
| 	case "assigned": | ||||
| 		assigneeID = ctx.User.Id | ||||
| 		filterMode = models.FM_ASSIGN | ||||
| 		assigneeID = ctx.User.Id | ||||
| 	case "created_by": | ||||
| 		posterID = ctx.User.Id | ||||
| 		filterMode = models.FM_CREATE | ||||
| 		posterID = ctx.User.Id | ||||
| 	case "mentioned": | ||||
| 		filterMode = models.FM_MENTION | ||||
| 	} | ||||
|  | @ -97,7 +97,7 @@ func Issues(ctx *middleware.Context) { | |||
| 	selectLabels := ctx.Query("labels") | ||||
| 	milestoneID := ctx.QueryInt64("milestone") | ||||
| 	isShowClosed := ctx.Query("state") == "closed" | ||||
| 	issueStats := models.GetIssueStats(repo.ID, uid, com.StrTo(selectLabels).MustInt64(), milestoneID, assigneeID, isShowClosed, filterMode) | ||||
| 	issueStats := models.GetIssueStats(repo.ID, uid, com.StrTo(selectLabels).MustInt64(), milestoneID, assigneeID, filterMode) | ||||
| 
 | ||||
| 	page := ctx.QueryInt("page") | ||||
| 	if page <= 1 { | ||||
|  |  | |||
|  | @ -10,10 +10,10 @@ import ( | |||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/Unknwon/com" | ||||
| 	"github.com/Unknwon/paginater" | ||||
| 
 | ||||
| 	"github.com/gogits/gogs/models" | ||||
| 	"github.com/gogits/gogs/modules/base" | ||||
| 	"github.com/gogits/gogs/modules/log" | ||||
| 	"github.com/gogits/gogs/modules/middleware" | ||||
| 	"github.com/gogits/gogs/modules/setting" | ||||
| ) | ||||
|  | @ -21,18 +21,13 @@ import ( | |||
| const ( | ||||
| 	DASHBOARD base.TplName = "user/dashboard/dashboard" | ||||
| 	PULLS     base.TplName = "user/dashboard/pulls" | ||||
| 	ISSUES    base.TplName = "user/issues" | ||||
| 	ISSUES    base.TplName = "user/dashboard/issues" | ||||
| 	STARS     base.TplName = "user/stars" | ||||
| 	PROFILE   base.TplName = "user/profile" | ||||
| ) | ||||
| 
 | ||||
| func Dashboard(ctx *middleware.Context) { | ||||
| 	ctx.Data["Title"] = ctx.Tr("dashboard") | ||||
| 	ctx.Data["PageIsDashboard"] = true | ||||
| 	ctx.Data["PageIsNews"] = true | ||||
| 
 | ||||
| 	var ctxUser *models.User | ||||
| 	// Check context type.
 | ||||
| func getDashboardContextUser(ctx *middleware.Context) *models.User { | ||||
| 	ctxUser := ctx.User | ||||
| 	orgName := ctx.Params(":org") | ||||
| 	if len(orgName) > 0 { | ||||
| 		// Organization.
 | ||||
|  | @ -43,10 +38,33 @@ func Dashboard(ctx *middleware.Context) { | |||
| 			} else { | ||||
| 				ctx.Handle(500, "GetUserByName", err) | ||||
| 			} | ||||
| 			return | ||||
| 			return nil | ||||
| 		} | ||||
| 		ctxUser = org | ||||
| 	} else { | ||||
| 	} | ||||
| 	ctx.Data["ContextUser"] = ctxUser | ||||
| 
 | ||||
| 	if err := ctx.User.GetOrganizations(); err != nil { | ||||
| 		ctx.Handle(500, "GetOrganizations", err) | ||||
| 		return nil | ||||
| 	} | ||||
| 	ctx.Data["Orgs"] = ctx.User.Orgs | ||||
| 
 | ||||
| 	return ctxUser | ||||
| } | ||||
| 
 | ||||
| func Dashboard(ctx *middleware.Context) { | ||||
| 	ctx.Data["Title"] = ctx.Tr("dashboard") | ||||
| 	ctx.Data["PageIsDashboard"] = true | ||||
| 	ctx.Data["PageIsNews"] = true | ||||
| 
 | ||||
| 	ctxUser := getDashboardContextUser(ctx) | ||||
| 	if ctx.Written() { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// Check context type.
 | ||||
| 	if !ctxUser.IsOrganization() { | ||||
| 		// Normal user.
 | ||||
| 		ctxUser = ctx.User | ||||
| 		collaborates, err := ctx.User.GetAccessibleRepositories() | ||||
|  | @ -63,13 +81,6 @@ func Dashboard(ctx *middleware.Context) { | |||
| 		ctx.Data["CollaborateCount"] = len(repositories) | ||||
| 		ctx.Data["CollaborativeRepos"] = repositories | ||||
| 	} | ||||
| 	ctx.Data["ContextUser"] = ctxUser | ||||
| 
 | ||||
| 	if err := ctx.User.GetOrganizations(); err != nil { | ||||
| 		ctx.Handle(500, "GetOrganizations", err) | ||||
| 		return | ||||
| 	} | ||||
| 	ctx.Data["Orgs"] = ctx.User.Orgs | ||||
| 
 | ||||
| 	repos, err := models.GetRepositories(ctxUser.Id, true) | ||||
| 	if err != nil { | ||||
|  | @ -142,6 +153,138 @@ func Pulls(ctx *middleware.Context) { | |||
| 	ctx.HTML(200, PULLS) | ||||
| } | ||||
| 
 | ||||
| func Issues(ctx *middleware.Context) { | ||||
| 	ctx.Data["Title"] = ctx.Tr("issues") | ||||
| 	ctx.Data["PageIsIssues"] = true | ||||
| 
 | ||||
| 	ctxUser := getDashboardContextUser(ctx) | ||||
| 	if ctx.Written() { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// Organization does not have view type and filter mode.
 | ||||
| 	var ( | ||||
| 		viewType   string | ||||
| 		filterMode = models.FM_ALL | ||||
| 		assigneeID int64 | ||||
| 		posterID   int64 | ||||
| 	) | ||||
| 	if ctxUser.IsOrganization() { | ||||
| 		viewType = "all" | ||||
| 	} else { | ||||
| 		viewType = ctx.Query("type") | ||||
| 		types := []string{"assigned", "created_by"} | ||||
| 		if !com.IsSliceContainsStr(types, viewType) { | ||||
| 			viewType = "all" | ||||
| 		} | ||||
| 
 | ||||
| 		switch viewType { | ||||
| 		case "assigned": | ||||
| 			filterMode = models.FM_ASSIGN | ||||
| 			assigneeID = ctxUser.Id | ||||
| 		case "created_by": | ||||
| 			filterMode = models.FM_CREATE | ||||
| 			posterID = ctxUser.Id | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	repoID := ctx.QueryInt64("repo") | ||||
| 	isShowClosed := ctx.Query("state") == "closed" | ||||
| 	issueStats := models.GetUserIssueStats(repoID, ctxUser.Id, filterMode) | ||||
| 
 | ||||
| 	page := ctx.QueryInt("page") | ||||
| 	if page <= 1 { | ||||
| 		page = 1 | ||||
| 	} | ||||
| 
 | ||||
| 	var total int | ||||
| 	if !isShowClosed { | ||||
| 		total = int(issueStats.OpenCount) | ||||
| 	} else { | ||||
| 		total = int(issueStats.ClosedCount) | ||||
| 	} | ||||
| 	ctx.Data["Page"] = paginater.New(total, setting.IssuePagingNum, page, 5) | ||||
| 
 | ||||
| 	// Get repositories.
 | ||||
| 	repos, err := models.GetRepositories(ctxUser.Id, true) | ||||
| 	if err != nil { | ||||
| 		ctx.Handle(500, "GetRepositories", err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	repoIDs := make([]int64, 0, len(repos)) | ||||
| 	showRepos := make([]*models.Repository, 0, len(repos)) | ||||
| 	for _, repo := range repos { | ||||
| 		if repo.NumIssues == 0 { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		repoIDs = append(repoIDs, repo.ID) | ||||
| 		repo.NumOpenIssues = repo.NumIssues - repo.NumClosedIssues | ||||
| 		issueStats.AllCount += int64(repo.NumOpenIssues) | ||||
| 
 | ||||
| 		if repo.ID == repoID { | ||||
| 			repo.NumOpenIssues = int(issueStats.OpenCount) | ||||
| 			repo.NumClosedIssues = int(issueStats.ClosedCount) | ||||
| 		} else if filterMode != models.FM_ALL && repo.NumIssues > 0 { | ||||
| 			// Calculate repository issue count with filter mode.
 | ||||
| 			numOpen, numClosed := repo.IssueStats(ctxUser.Id, filterMode) | ||||
| 			repo.NumOpenIssues, repo.NumClosedIssues = int(numOpen), int(numClosed) | ||||
| 		} | ||||
| 
 | ||||
| 		if repo.ID == repoID || | ||||
| 			(isShowClosed && repo.NumClosedIssues > 0) || | ||||
| 			(!isShowClosed && repo.NumOpenIssues > 0) { | ||||
| 			showRepos = append(showRepos, repo) | ||||
| 		} | ||||
| 	} | ||||
| 	ctx.Data["Repos"] = showRepos | ||||
| 
 | ||||
| 	if repoID > 0 { | ||||
| 		repoIDs = []int64{repoID} | ||||
| 	} | ||||
| 
 | ||||
| 	// Get issues.
 | ||||
| 	issues, err := models.Issues(ctxUser.Id, assigneeID, repoID, posterID, 0, | ||||
| 		page, isShowClosed, false, "", "") | ||||
| 	if err != nil { | ||||
| 		ctx.Handle(500, "Issues: %v", err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// Get posters and repository.
 | ||||
| 	for i := range issues { | ||||
| 		issues[i].Repo, err = models.GetRepositoryByID(issues[i].RepoID) | ||||
| 		if err != nil { | ||||
| 			ctx.Handle(500, "GetRepositoryByID", fmt.Errorf("[#%d]%v", issues[i].ID, err)) | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 		if err = issues[i].Repo.GetOwner(); err != nil { | ||||
| 			ctx.Handle(500, "GetOwner", fmt.Errorf("[#%d]%v", issues[i].ID, err)) | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 		if err = issues[i].GetPoster(); err != nil { | ||||
| 			ctx.Handle(500, "GetPoster", fmt.Errorf("[#%d]%v", issues[i].ID, err)) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	ctx.Data["Issues"] = issues | ||||
| 
 | ||||
| 	ctx.Data["IssueStats"] = issueStats | ||||
| 	ctx.Data["ViewType"] = viewType | ||||
| 	ctx.Data["RepoID"] = repoID | ||||
| 	ctx.Data["IsShowClosed"] = isShowClosed | ||||
| 	if isShowClosed { | ||||
| 		ctx.Data["State"] = "closed" | ||||
| 	} else { | ||||
| 		ctx.Data["State"] = "open" | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.HTML(200, ISSUES) | ||||
| } | ||||
| 
 | ||||
| func ShowSSHKeys(ctx *middleware.Context, uid int64) { | ||||
| 	keys, err := models.ListPublicKeys(uid) | ||||
| 	if err != nil { | ||||
|  | @ -256,136 +399,3 @@ func Email2User(ctx *middleware.Context) { | |||
| 	} | ||||
| 	ctx.Redirect(setting.AppSubUrl + "/user/" + u.Name) | ||||
| } | ||||
| 
 | ||||
| func Issues(ctx *middleware.Context) { | ||||
| 	ctx.Data["Title"] = ctx.Tr("issues") | ||||
| 	ctx.Data["PageIsDashboard"] = true | ||||
| 	ctx.Data["PageIsIssues"] = true | ||||
| 
 | ||||
| 	viewType := ctx.Query("type") | ||||
| 	types := []string{"assigned", "created_by"} | ||||
| 	if !com.IsSliceContainsStr(types, viewType) { | ||||
| 		viewType = "all" | ||||
| 	} | ||||
| 
 | ||||
| 	isShowClosed := ctx.Query("state") == "closed" | ||||
| 
 | ||||
| 	var filterMode int | ||||
| 	switch viewType { | ||||
| 	case "assigned": | ||||
| 		filterMode = models.FM_ASSIGN | ||||
| 	case "created_by": | ||||
| 		filterMode = models.FM_CREATE | ||||
| 	} | ||||
| 
 | ||||
| 	repoId, _ := com.StrTo(ctx.Query("repoid")).Int64() | ||||
| 	issueStats := models.GetUserIssueStats(ctx.User.Id, filterMode) | ||||
| 
 | ||||
| 	// Get all repositories.
 | ||||
| 	repos, err := models.GetRepositories(ctx.User.Id, true) | ||||
| 	if err != nil { | ||||
| 		ctx.Handle(500, "user.Issues(GetRepositories)", err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	repoIds := make([]int64, 0, len(repos)) | ||||
| 	showRepos := make([]*models.Repository, 0, len(repos)) | ||||
| 	for _, repo := range repos { | ||||
| 		if repo.NumIssues == 0 { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		repoIds = append(repoIds, repo.ID) | ||||
| 		repo.NumOpenIssues = repo.NumIssues - repo.NumClosedIssues | ||||
| 		issueStats.AllCount += int64(repo.NumOpenIssues) | ||||
| 
 | ||||
| 		if isShowClosed { | ||||
| 			if repo.NumClosedIssues > 0 { | ||||
| 				if filterMode == models.FM_CREATE { | ||||
| 					repo.NumClosedIssues = int(models.GetIssueCountByPoster(ctx.User.Id, repo.ID, isShowClosed)) | ||||
| 				} | ||||
| 				showRepos = append(showRepos, repo) | ||||
| 			} | ||||
| 		} else { | ||||
| 			if repo.NumOpenIssues > 0 { | ||||
| 				if filterMode == models.FM_CREATE { | ||||
| 					repo.NumOpenIssues = int(models.GetIssueCountByPoster(ctx.User.Id, repo.ID, isShowClosed)) | ||||
| 				} | ||||
| 				showRepos = append(showRepos, repo) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if repoId > 0 { | ||||
| 		repoIds = []int64{repoId} | ||||
| 	} | ||||
| 
 | ||||
| 	page, _ := com.StrTo(ctx.Query("page")).Int() | ||||
| 
 | ||||
| 	// Get all issues.
 | ||||
| 	var ius []*models.IssueUser | ||||
| 	switch viewType { | ||||
| 	case "assigned": | ||||
| 		fallthrough | ||||
| 	case "created_by": | ||||
| 		ius, err = models.GetIssueUserPairsByMode(ctx.User.Id, repoId, isShowClosed, page, filterMode) | ||||
| 	default: | ||||
| 		ius, err = models.GetIssueUserPairsByRepoIds(repoIds, isShowClosed, page) | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		ctx.Handle(500, "user.Issues(GetAllIssueUserPairs)", err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	issues := make([]*models.Issue, len(ius)) | ||||
| 	for i := range ius { | ||||
| 		issues[i], err = models.GetIssueByID(ius[i].IssueID) | ||||
| 		if err != nil { | ||||
| 			if models.IsErrIssueNotExist(err) { | ||||
| 				log.Warn("user.Issues(GetIssueById #%d): issue not exist", ius[i].IssueID) | ||||
| 				continue | ||||
| 			} else { | ||||
| 				ctx.Handle(500, fmt.Sprintf("user.Issues(GetIssueById #%d)", ius[i].IssueID), err) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		issues[i].Repo, err = models.GetRepositoryByID(issues[i].RepoID) | ||||
| 		if err != nil { | ||||
| 			if models.IsErrRepoNotExist(err) { | ||||
| 				log.Warn("GetRepositoryById[%d]: repository not exist", issues[i].RepoID) | ||||
| 				continue | ||||
| 			} else { | ||||
| 				ctx.Handle(500, fmt.Sprintf("GetRepositoryById[%d]", issues[i].RepoID), err) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		if err = issues[i].Repo.GetOwner(); err != nil { | ||||
| 			ctx.Handle(500, "user.Issues(GetOwner)", err) | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 		if err = issues[i].GetPoster(); err != nil { | ||||
| 			ctx.Handle(500, "user.Issues(GetUserById)", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.Data["RepoId"] = repoId | ||||
| 	ctx.Data["Repos"] = showRepos | ||||
| 	ctx.Data["Issues"] = issues | ||||
| 	ctx.Data["ViewType"] = viewType | ||||
| 	ctx.Data["IssueStats"] = issueStats | ||||
| 	ctx.Data["IsShowClosed"] = isShowClosed | ||||
| 	if isShowClosed { | ||||
| 		ctx.Data["State"] = "closed" | ||||
| 		ctx.Data["ShowCount"] = issueStats.ClosedCount | ||||
| 	} else { | ||||
| 		ctx.Data["ShowCount"] = issueStats.OpenCount | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.Data["ContextUser"] = ctx.User | ||||
| 
 | ||||
| 	ctx.HTML(200, ISSUES) | ||||
| } | ||||
|  |  | |||
|  | @ -62,6 +62,7 @@ | |||
| 
 | ||||
| 							{{if .IsSigned}} | ||||
| 							<a class="item{{if .PageIsDashboard}} active{{end}}" href="{{AppSubUrl}}/">{{.i18n.Tr "dashboard"}}</a> | ||||
| 							<a class="item{{if .PageIsIssues}} active{{end}}" href="{{AppSubUrl}}/issues">{{.i18n.Tr "issues"}}</a> | ||||
| 							{{else}} | ||||
| 							<a class="item{{if .PageIsHome}} active{{end}}" href="{{AppSubUrl}}/">{{.i18n.Tr "home"}}</a> | ||||
| 							{{end}} | ||||
|  | @ -75,7 +76,6 @@ | |||
| 							</div> --> | ||||
| 
 | ||||
| 							{{if .IsSigned}} | ||||
| 							<a class="item{{if .PageIsIssues}} active{{end}}" href="{{AppSubUrl}}/issues">{{.i18n.Tr "issues"}}</a> | ||||
| 							<div class="right menu"> | ||||
| 								<div class="ui dropdown head link jump item poping up" data-content="{{.i18n.Tr "create_new"}}" data-variation="tiny inverted"> | ||||
| 									<span class="text"> | ||||
|  |  | |||
|  | @ -10,9 +10,11 @@ | |||
| 	  <a class="{{if .PageIsSettingsHooks}}active{{end}} item" href="{{.RepoLink}}/settings/hooks"> | ||||
| 	    {{.i18n.Tr "repo.settings.hooks"}} | ||||
| 	  </a> | ||||
| 	  {{if or .SignedUser.AllowGitHook .SignedUser.IsAdmin}} | ||||
| 	  <a class="{{if .PageIsSettingsGitHooks}}active{{end}} item" href="{{.RepoLink}}/settings/hooks/git"> | ||||
| 	    {{.i18n.Tr "repo.settings.githooks"}} | ||||
| 	  </a> | ||||
| 	  {{end}} | ||||
| 	  <a class="{{if .PageIsSettingsKeys}}active{{end}} item" href="{{.RepoLink}}/settings/keys"> | ||||
| 	    {{.i18n.Tr "repo.settings.deploy_keys"}} | ||||
| 	  </a> | ||||
|  |  | |||
							
								
								
									
										86
									
								
								templates/user/dashboard/issues.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								templates/user/dashboard/issues.tmpl
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,86 @@ | |||
| {{template "base/head" .}} | ||||
| <div class="dashboard issues"> | ||||
|   {{template "user/dashboard/navbar" .}} | ||||
|   <div class="ui container"> | ||||
|   	<div class="ui grid"> | ||||
|   		<div class="four wide column"> | ||||
|   			<div class="ui secondary vertical filter menu"> | ||||
|   				<a class="{{if eq .ViewType "all"}}active{{end}} item" href="{{.Link}}?repo={{.RepoID}}&state={{.State}}"> | ||||
|   					{{.i18n.Tr "home.issues.in_your_repos"}} | ||||
|   					<strong class="ui right">{{.IssueStats.AllCount}}</strong> | ||||
|   				</a> | ||||
|   				<a class="{{if eq .ViewType "assigned"}}active{{end}} item" href="{{.Link}}?type=assigned&repo={{.RepoID}}&state={{.State}}"> | ||||
|   					{{.i18n.Tr "repo.issues.filter_type.assigned_to_you"}} | ||||
|   					<strong class="ui right">{{.IssueStats.AssignCount}}</strong> | ||||
|   				</a> | ||||
|   				<a class="{{if eq .ViewType "created_by"}}active{{end}} item" href="{{.Link}}?type=created_by&repo={{.RepoID}}&state={{.State}}"> | ||||
|   					{{.i18n.Tr "repo.issues.filter_type.created_by_you"}} | ||||
|   					<strong class="ui right">{{.IssueStats.CreateCount}}</strong> | ||||
|   				</a> | ||||
|   				<div class="ui divider"></div> | ||||
|           {{range .Repos}} | ||||
|           <a class="{{if eq $.RepoID .ID}}active{{end}} item" href="{{$.Link}}?type={{$.ViewType}}{{if not (eq $.RepoID .ID)}}&repo={{.ID}}{{end}}&state={{$.State}}">{{$.SignedUser.Name}}/{{.Name}} <strong class="ui right">{{if $.IsShowClosed}}{{.NumClosedIssues}}{{else}}{{.NumOpenIssues}}{{end}}</strong></a> | ||||
|           {{end}} | ||||
|   			</div> | ||||
|   		</div> | ||||
| 			<div class="twelve wide column content"> | ||||
| 				<div class="ui tiny buttons"> | ||||
| 				  <a class="ui green basic button {{if not .IsShowClosed}}active{{end}}" href="{{.Link}}?type={{$.ViewType}}&repo={{.RepoID}}&state=open"> | ||||
| 				  	<i class="octicon octicon-issue-opened"></i> | ||||
| 				  	{{.i18n.Tr "repo.issues.open_tab" .IssueStats.OpenCount}} | ||||
| 				  </a> | ||||
| 				  <a class="ui red basic button {{if .IsShowClosed}}active{{end}}" href="{{.Link}}?type={{$.ViewType}}&repo={{.RepoID}}&state=closed"> | ||||
| 				  	<i class="octicon octicon-issue-closed"></i> | ||||
| 				  	{{.i18n.Tr "repo.issues.close_tab" .IssueStats.ClosedCount}} | ||||
| 				  </a> | ||||
| 				</div> | ||||
| 
 | ||||
| 				<div class="issue list"> | ||||
| 					{{range .Issues}} | ||||
| 					{{ $timeStr:= TimeSince .Created $.Lang }} | ||||
| 		      <li class="item"> | ||||
| 		      	<div class="ui label">#{{.ID}}</div> | ||||
| 		      	<a class="title" href="{{AppSubUrl}}/{{.Repo.Owner.Name}}/{{.Repo.Name}}/issues/{{.Index}}">{{.Name}}</a> | ||||
| 
 | ||||
| 		      	{{if .NumComments}} | ||||
| 		      	<span class="comment ui right"><i class="octicon octicon-comment"></i> {{.NumComments}}</span> | ||||
| 		      	{{end}} | ||||
| 
 | ||||
| 		        <p class="desc"> | ||||
| 		        	{{$.i18n.Tr "repo.issues.opened_by" $timeStr .Poster.Name | Safe}} | ||||
| 			        {{if .Assignee}} | ||||
| 							<a class="ui right assignee poping up" href="{{.Assignee.HomeLink}}" data-content="{{.Assignee.Name}}" data-variation="inverted" data-position="left center"> | ||||
| 								<img class="ui avatar image" src="{{.Assignee.AvatarLink}}"> | ||||
| 							</a> | ||||
| 			        {{end}} | ||||
| 		        </p> | ||||
| 		      </li> | ||||
| 		      {{end}} | ||||
| 					 | ||||
| 					{{with .Page}} | ||||
| 					{{if gt .TotalPages 1}} | ||||
| 					<div class="center page buttons"> | ||||
| 						<div class="ui borderless pagination menu"> | ||||
| 						  <a class="{{if not .HasPrevious}}disabled{{end}} item" {{if .HasPrevious}}href="{{$.Link}}?type={{$.ViewType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}&page={{.Previous}}"{{end}}> | ||||
| 						    <i class="left arrow icon"></i> {{$.i18n.Tr "repo.issues.previous"}} | ||||
| 						  </a> | ||||
| 							{{range .Pages}} | ||||
| 							{{if eq .Num -1}} | ||||
| 							<a class="disabled item">...</a> | ||||
| 							{{else}} | ||||
| 							<a class="{{if .IsCurrent}}active{{end}} item" {{if not .IsCurrent}}href="{{$.Link}}?type={{$.ViewType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}&page={{.Num}}"{{end}}>{{.Num}}</a> | ||||
| 							{{end}} | ||||
| 							{{end}} | ||||
| 						  <a class="{{if not .HasNext}}disabled{{end}} item" {{if .HasNext}}href="{{$.Link}}?type={{$.ViewType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}&page={{.Next}}"{{end}}> | ||||
| 						    {{$.i18n.Tr "repo.issues.next"}} <i class="icon right arrow"></i> | ||||
| 						  </a> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 					{{end}} | ||||
| 					{{end}} | ||||
| 				</div> | ||||
| 		  </div> | ||||
|   	</div> | ||||
| 	</div> | ||||
| </div> | ||||
| {{template "base/footer" .}} | ||||
							
								
								
									
										30
									
								
								templates/user/dashboard/navbar.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								templates/user/dashboard/navbar.tmpl
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,30 @@ | |||
| <div class="ui container"> | ||||
|   <div class="ui floating dropdown link jump"> | ||||
|     <span class="text"> | ||||
|     	<img class="ui avatar image" src="{{.ContextUser.AvatarLink}}"> | ||||
|   		{{.ContextUser.Name}} | ||||
|     	<i class="dropdown icon"></i> | ||||
|   	</span> | ||||
|     <div class="context user menu" tabindex="-1"> | ||||
| 			<div class="ui header"> | ||||
| 				{{.i18n.Tr "home.switch_dashboard_context"}} | ||||
| 			</div> | ||||
|     	<a class="{{if eq .ContextUser.Id .SignedUser.Id}}active selected{{end}} item" href="{{AppSubUrl}}/issues"> | ||||
|       	<img class="ui image" src="{{.SignedUser.AvatarLink}}"> | ||||
|     		{{.SignedUser.Name}} | ||||
|     	</a> | ||||
|     	{{range .Orgs}} | ||||
|     	{{if .IsOwnedBy $.SignedUser.Id}} | ||||
|     	<a class="{{if eq $.ContextUser.Id .Id}}active selected{{end}} item" href="{{AppSubUrl}}/org/{{.Name}}/issues"> | ||||
|       	<img class="ui image" src="{{.AvatarLink}}"> | ||||
|     		{{.Name}} | ||||
|     	</a> | ||||
|     	{{end}} | ||||
|     	{{end}} | ||||
|     	<a class="item" href="{{AppSubUrl}}/org/create"> | ||||
|     		<i class="octicon octicon-repo-create"></i>   {{.i18n.Tr "new_org"}} | ||||
|     	</a> | ||||
|     </div> | ||||
|   </div> | ||||
| </div> | ||||
| <div class="ui divider"></div> | ||||
|  | @ -1,44 +0,0 @@ | |||
| {{template "ng/base/head" .}} | ||||
| {{template "ng/base/header" .}} | ||||
| {{template "user/dashboard/nav" .}} | ||||
| <div id="dashboard-wrapper"> | ||||
| 	<div id="dashboard" class="container" data-page="user"> | ||||
| 		{{if .HasInfo}}<div class="alert alert-info">{{.InfoMsg}}</div>{{end}} | ||||
| 		<div id="issue"> | ||||
| 		    <div class="left grid-1-5 filter-list"> | ||||
| 		        <ul class="list-unstyled menu menu-vertical"> | ||||
| 		            <li><a href="{{AppSubUrl}}/issues?state={{.State}}&repoid={{.RepoId}}" class="radius{{if eq .ViewType "all"}} active{{end}}" >In your repositories <strong class="pull-right">{{.IssueStats.AllCount}}</strong></a></li> | ||||
| 		            <li><a href="{{AppSubUrl}}/issues?type=assigned&repoid={{.RepoId}}&state={{.State}}" class="radius{{if eq .ViewType "assigned"}} active{{end}}">Assigned to you <strong class="pull-right">{{.IssueStats.AssignCount}}</strong></a></li> | ||||
| 		            <li><a href="{{AppSubUrl}}/issues?type=created_by&repoid={{.RepoId}}&state={{.State}}" class="radius{{if eq .ViewType "created_by"}} active{{end}}">Created by you <strong class="pull-right">{{.IssueStats.CreateCount}}</strong></a></li> | ||||
| 		            <li><hr/></li> | ||||
| 		            {{range .Repos}} | ||||
| 		            <li><a href="{{AppSubUrl}}/issues?type={{$.ViewType}}{{if eq $.RepoId .ID}}{{else}}&repoid={{.ID}}{{end}}&state={{$.State}}" class="radius{{if eq $.RepoId .ID}} active{{end}}">{{$.SignedUser.Name}}/{{.Name}} <strong class="pull-right">{{if $.IsShowClosed}}{{.NumClosedIssues}}{{else}}{{.NumOpenIssues}}{{end}}</strong></a></li> | ||||
| 		            {{end}} | ||||
| 		        </ul> | ||||
| 		    </div> | ||||
| 		    <div class="right grid-3-4"> | ||||
| 		        <div class="filter-option"> | ||||
| 		            <div class="btn-group"> | ||||
| 		                <a class="btn btn-white btn-small issue-open{{if not .IsShowClosed}} active{{end}}" href="{{AppSubUrl}}/issues?type={{.ViewType}}&repoid={{.RepoId}}">Open</a> | ||||
| 		                <a class="btn btn-white btn-small issue-close{{if .IsShowClosed}} active{{end}}" href="{{AppSubUrl}}/issues?type={{.ViewType}}&repoid={{.RepoId}}&state=closed">Closed</a> | ||||
| 		            </div> | ||||
| 		        </div> | ||||
| 		        <div class="issues list-group"> | ||||
| 		            {{range .Issues}}{{if .}} | ||||
| 		            <div class="list-group-item issue-item" id="issue-{{.ID}}" onclick="window.location.href='{{AppSubUrl}}/{{.Repo.Owner.Name}}/{{.Repo.Name}}/issues/{{.Index}}'"> | ||||
| 		                <span class="number pull-right">#{{.Index}}</span> | ||||
| 		                <h5 class="title"><a href="{{AppSubUrl}}/{{.Repo.Owner.Name}}/{{.Repo.Name}}/issues/{{.Index}}">{{.Name}}</a></h5> | ||||
| 		                <p class="info"> | ||||
| 		                    <span class="author"><img class="avatar" src="{{.Poster.AvatarLink}}" alt="" width="20"/> | ||||
| 		                    <a href="{{AppSubUrl}}/{{.Poster.Name}}">{{.Poster.Name}}</a></span> | ||||
| 		                    <span class="time">{{TimeSince .Created $.Lang}}</span> | ||||
| 		                    <span class="comment"><i class="fa fa-comments"></i> {{.NumComments}}</span> | ||||
| 		                </p> | ||||
| 		            </div> | ||||
| 		            {{end}}{{end}} | ||||
| 		        </div> | ||||
| 		    </div> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
| {{template "ng/base/footer" .}} | ||||
		Loading…
	
		Reference in a new issue