You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

repo_dashbord.go 17 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. package repo
  2. import (
  3. "encoding/csv"
  4. "fmt"
  5. "io/ioutil"
  6. "net/http"
  7. "os"
  8. "path"
  9. "strconv"
  10. "time"
  11. "code.gitea.io/gitea/models"
  12. "code.gitea.io/gitea/modules/log"
  13. "code.gitea.io/gitea/modules/context"
  14. "code.gitea.io/gitea/modules/setting"
  15. )
  16. const DEFAULT_PAGE_SIZE = 10
  17. const DATE_FORMAT = "2006-01-02"
  18. type ProjectsPeriodData struct {
  19. RecordBeginTime string `json:"recordBeginTime"`
  20. LastUpdatedTime string `json:"lastUpdatedTime"`
  21. PageSize int `json:"pageSize"`
  22. TotalPage int `json:"totalPage"`
  23. TotalCount int64 `json:"totalCount"`
  24. PageRecords []*models.RepoStatistic `json:"pageRecords"`
  25. }
  26. type UserInfo struct {
  27. User string `json:"user"`
  28. Mode int `json:"mode"`
  29. PR int64 `json:"pr"`
  30. Commit int `json:"commit"`
  31. }
  32. type ProjectLatestData struct {
  33. RecordBeginTime string `json:"recordBeginTime"`
  34. LastUpdatedTime string `json:"lastUpdatedTime"`
  35. CreatTime string `json:"creatTime"`
  36. OpenI float64 `json:"openi"`
  37. Comment int64 `json:"comment"`
  38. View int64 `json:"view"`
  39. Download int64 `json:"download"`
  40. IssueClosedRatio float32 `json:"issueClosedRatio"`
  41. Impact float64 `json:"impact"`
  42. Completeness float64 `json:"completeness"`
  43. Liveness float64 `json:"liveness"`
  44. ProjectHealth float64 `json:"projectHealth"`
  45. TeamHealth float64 `json:"teamHealth"`
  46. Growth float64 `json:"growth"`
  47. Description string `json:"description"`
  48. Top10 []UserInfo `json:"top10"`
  49. }
  50. func RestoreForkNumber(ctx *context.Context) {
  51. repos, err := models.GetAllRepositories()
  52. if err != nil {
  53. log.Error("GetAllRepositories failed: %v", err.Error())
  54. return
  55. }
  56. for _, repo := range repos {
  57. models.RestoreRepoStatFork(int64(repo.NumForks), repo.ID)
  58. }
  59. ctx.JSON(http.StatusOK, struct{}{})
  60. }
  61. func GetAllProjectsPeriodStatistics(ctx *context.Context) {
  62. recordBeginTime, err := getRecordBeginTime()
  63. if err != nil {
  64. log.Error("Can not get record begin time", err)
  65. ctx.Error(http.StatusBadRequest, ctx.Tr("repo.record_begintime_get_err"))
  66. return
  67. }
  68. beginTime, endTime, err := getTimePeroid(ctx, recordBeginTime)
  69. if err != nil {
  70. log.Error("Parameter is wrong", err)
  71. ctx.Error(http.StatusBadRequest, ctx.Tr("repo.parameter_is_wrong"))
  72. return
  73. }
  74. q := ctx.QueryTrim("q")
  75. page := ctx.QueryInt("page")
  76. if page <= 0 {
  77. page = 1
  78. }
  79. pageSize := ctx.QueryInt("pagesize")
  80. if pageSize <= 0 {
  81. pageSize = DEFAULT_PAGE_SIZE
  82. }
  83. orderBy := getOrderBy(ctx)
  84. latestUpdatedTime, latestDate, err := models.GetRepoStatLastUpdatedTime()
  85. if err != nil {
  86. log.Error("Can not query the last updated time.", err)
  87. ctx.Error(http.StatusBadRequest, ctx.Tr("repo.last_update_time_error"))
  88. return
  89. }
  90. countSql := generateCountSql(beginTime, endTime, latestDate, q)
  91. total, err := models.CountRepoStatByRawSql(countSql)
  92. if err != nil {
  93. log.Error("Can not query total count.", err)
  94. ctx.Error(http.StatusBadRequest, ctx.Tr("repo.total_count_get_error"))
  95. return
  96. }
  97. projectsPeriodData := ProjectsPeriodData{
  98. RecordBeginTime: recordBeginTime.Format(DATE_FORMAT),
  99. PageSize: pageSize,
  100. TotalPage: getTotalPage(total, pageSize),
  101. TotalCount: total,
  102. LastUpdatedTime: latestUpdatedTime,
  103. PageRecords: models.GetRepoStatisticByRawSql(generatePageSql(beginTime, endTime, latestDate, q, orderBy, page, pageSize)),
  104. }
  105. ctx.JSON(http.StatusOK, projectsPeriodData)
  106. }
  107. func ServeAllProjectsPeriodStatisticsFile(ctx *context.Context) {
  108. recordBeginTime, err := getRecordBeginTime()
  109. if err != nil {
  110. log.Error("Can not get record begin time", err)
  111. ctx.Error(http.StatusBadRequest, ctx.Tr("repo.record_begintime_get_err"))
  112. return
  113. }
  114. beginTime, endTime, err := getTimePeroid(ctx, recordBeginTime)
  115. if err != nil {
  116. log.Error("Parameter is wrong", err)
  117. ctx.Error(http.StatusBadRequest, ctx.Tr("repo.parameter_is_wrong"))
  118. return
  119. }
  120. q := ctx.QueryTrim("q")
  121. page := ctx.QueryInt("page")
  122. if page <= 0 {
  123. page = 1
  124. }
  125. pageSize := 1000
  126. orderBy := getOrderBy(ctx)
  127. _, latestDate, err := models.GetRepoStatLastUpdatedTime()
  128. if err != nil {
  129. log.Error("Can not query the last updated time.", err)
  130. ctx.Error(http.StatusBadRequest, ctx.Tr("repo.last_update_time_error"))
  131. return
  132. }
  133. countSql := generateCountSql(beginTime, endTime, latestDate, q)
  134. total, err := models.CountRepoStatByRawSql(countSql)
  135. if err != nil {
  136. log.Error("Can not query total count.", err)
  137. ctx.Error(http.StatusBadRequest, ctx.Tr("repo.total_count_get_error"))
  138. return
  139. }
  140. fileName := getFileName(ctx, beginTime, endTime)
  141. if err := os.MkdirAll(setting.RadarMap.Path, os.ModePerm); err != nil {
  142. ctx.Error(http.StatusBadRequest, fmt.Errorf("Failed to create dir %s: %v", setting.AvatarUploadPath, err).Error())
  143. }
  144. totalPage := getTotalPage(total, pageSize)
  145. f, e := os.Create(fileName)
  146. defer f.Close()
  147. if e != nil {
  148. log.Warn("Failed to create file", e)
  149. }
  150. writer := csv.NewWriter(f)
  151. writer.Write(allProjectsPeroidHeader(ctx))
  152. for i := 0; i <= totalPage; i++ {
  153. pageRecords := models.GetRepoStatisticByRawSql(generatePageSql(beginTime, endTime, latestDate, q, orderBy, i+1, pageSize))
  154. for _, record := range pageRecords {
  155. e = writer.Write(allProjectsPeroidValues(record, ctx))
  156. if e != nil {
  157. log.Warn("Failed to write record", e)
  158. }
  159. }
  160. writer.Flush()
  161. }
  162. ctx.ServeFile(fileName)
  163. }
  164. func getFileName(ctx *context.Context, beginTime time.Time, endTime time.Time) string {
  165. baseName := setting.RadarMap.Path + "/"
  166. if ctx.QueryTrim("type") != "" {
  167. baseName = baseName + ctx.QueryTrim("type") + "_"
  168. }
  169. if ctx.QueryTrim("q") != "" {
  170. baseName = baseName + ctx.QueryTrim("q") + "_"
  171. }
  172. baseName = baseName + beginTime.Format(DATE_FORMAT) + "_to_" + endTime.Format(DATE_FORMAT) + "_" + strconv.FormatInt(time.Now().Unix(), 10) + ".csv"
  173. return baseName
  174. }
  175. func ClearUnusedStatisticsFile() {
  176. fileInfos, err := ioutil.ReadDir(setting.RadarMap.Path)
  177. if err != nil {
  178. log.Warn("can not read dir: "+setting.RadarMap.Path, err)
  179. return
  180. }
  181. for _, fileInfo := range fileInfos {
  182. if !fileInfo.IsDir() && fileInfo.ModTime().Before(time.Now().AddDate(0, 0, -1)) {
  183. os.Remove(path.Join(setting.RadarMap.Path, fileInfo.Name()))
  184. }
  185. }
  186. }
  187. func allProjectsPeroidHeader(ctx *context.Context) []string {
  188. return []string{ctx.Tr("repos.id"), ctx.Tr("repos.projectName"), ctx.Tr("repos.isPrivate"), ctx.Tr("repos.openi"), ctx.Tr("repos.visit"), ctx.Tr("repos.download"), ctx.Tr("repos.pr"), ctx.Tr("repos.commit"),
  189. ctx.Tr("repos.watches"), ctx.Tr("repos.stars"), ctx.Tr("repos.forks"), ctx.Tr("repos.issues"), ctx.Tr("repos.closedIssues"), ctx.Tr("repos.contributor")}
  190. }
  191. func allProjectsPeroidValues(rs *models.RepoStatistic, ctx *context.Context) []string {
  192. return []string{strconv.FormatInt(rs.RepoID, 10), rs.Name, getIsPrivateDisplay(rs.IsPrivate, ctx), strconv.FormatFloat(rs.RadarTotal, 'f', 2, 64),
  193. strconv.FormatInt(rs.NumVisits, 10), strconv.FormatInt(rs.NumDownloads, 10), strconv.FormatInt(rs.NumPulls, 10), strconv.FormatInt(rs.NumCommits, 10),
  194. strconv.FormatInt(rs.NumWatches, 10), strconv.FormatInt(rs.NumStars, 10), strconv.FormatInt(rs.NumForks, 10), strconv.FormatInt(rs.NumIssues, 10),
  195. strconv.FormatInt(rs.NumClosedIssues, 10), strconv.FormatInt(rs.NumContributor, 10),
  196. }
  197. }
  198. func getIsPrivateDisplay(private bool, ctx *context.Context) string {
  199. if private {
  200. return ctx.Tr("repos.yes")
  201. } else {
  202. return ctx.Tr("repos.no")
  203. }
  204. }
  205. func GetProjectLatestStatistics(ctx *context.Context) {
  206. repoId := ctx.Params(":id")
  207. recordBeginTime, err := getRecordBeginTime()
  208. if err != nil {
  209. log.Error("Can not get record begin time", err)
  210. ctx.Error(http.StatusBadRequest, ctx.Tr("repo.record_begintime_get_err"))
  211. return
  212. }
  213. latestUpdatedTime, latestDate, err := models.GetRepoStatLastUpdatedTime(repoId)
  214. repoIdInt, _ := strconv.ParseInt(repoId, 10, 64)
  215. repoStat, err := models.GetRepoStatisticByDateAndRepoId(latestDate, repoIdInt)
  216. if err != nil {
  217. log.Error("Can not get the repo statistics "+repoId, err)
  218. ctx.Error(http.StatusBadRequest, ctx.Tr("repo.get_repo_stat_error"))
  219. return
  220. }
  221. repository, err := models.GetRepositoryByID(repoIdInt)
  222. if err != nil {
  223. log.Error("Can not get the repo info "+repoId, err)
  224. ctx.Error(http.StatusBadRequest, ctx.Tr("repo.get_repo_info_error"))
  225. return
  226. }
  227. projectLatestData := ProjectLatestData{
  228. RecordBeginTime: recordBeginTime.Format(DATE_FORMAT),
  229. CreatTime: time.Unix(int64(repository.CreatedUnix), 0).Format(DATE_FORMAT),
  230. LastUpdatedTime: latestUpdatedTime,
  231. OpenI: repoStat.RadarTotal,
  232. Comment: repoStat.NumComments,
  233. View: repoStat.NumVisits,
  234. Download: repoStat.NumDownloads,
  235. IssueClosedRatio: repoStat.IssueFixedRate,
  236. Impact: repoStat.Impact,
  237. Completeness: repoStat.Completeness,
  238. Liveness: repoStat.Liveness,
  239. ProjectHealth: repoStat.ProjectHealth,
  240. TeamHealth: repoStat.TeamHealth,
  241. Growth: repoStat.Growth,
  242. Description: repository.Description,
  243. }
  244. contributors, err := models.GetTop10Contributor(repository.RepoPath())
  245. if err != nil {
  246. log.Error("can not get contributors", err)
  247. }
  248. users := make([]UserInfo, 0)
  249. for _, contributor := range contributors {
  250. mode := repository.GetCollaboratorMode(contributor.UserId)
  251. if mode == -1 {
  252. if contributor.IsAdmin {
  253. mode = int(models.AccessModeAdmin)
  254. }
  255. if contributor.UserId == repository.OwnerID {
  256. mode = int(models.AccessModeOwner)
  257. }
  258. }
  259. pr := models.GetPullCountByUserAndRepoId(repoIdInt, contributor.UserId)
  260. userInfo := UserInfo{
  261. User: contributor.Committer,
  262. Commit: contributor.CommitCnt,
  263. Mode: mode,
  264. PR: pr,
  265. }
  266. users = append(users, userInfo)
  267. }
  268. projectLatestData.Top10 = users
  269. ctx.JSON(http.StatusOK, projectLatestData)
  270. }
  271. func GetProjectPeriodStatistics(ctx *context.Context) {
  272. repoId := ctx.Params(":id")
  273. recordBeginTime, err := getRecordBeginTime()
  274. if err != nil {
  275. log.Error("Can not get record begin time", err)
  276. ctx.Error(http.StatusBadRequest, ctx.Tr("repo.record_begintime_get_err"))
  277. return
  278. }
  279. repoIdInt, _ := strconv.ParseInt(repoId, 10, 64)
  280. if err != nil {
  281. log.Error("Can not get record begin time", err)
  282. ctx.Error(http.StatusBadRequest, ctx.Tr("repo.record_begintime_get_err"))
  283. return
  284. }
  285. beginTime, endTime, err := getTimePeroid(ctx, recordBeginTime)
  286. isOpenI := ctx.QueryBool("openi")
  287. var repositorys []*models.RepoStatistic
  288. if isOpenI {
  289. repositorys = models.GetRepoStatisticByRawSql(generateRadarSql(beginTime, endTime, repoIdInt))
  290. } else {
  291. repositorys = models.GetRepoStatisticByRawSql(generateTargetSql(beginTime, endTime, repoIdInt))
  292. }
  293. ctx.JSON(http.StatusOK, repositorys)
  294. }
  295. func generateRadarSql(beginTime time.Time, endTime time.Time, repoId int64) string {
  296. sql := "SELECT date, impact, completeness, liveness, project_health, team_health, growth, radar_total FROM repo_statistic" +
  297. " where repo_id=" + strconv.FormatInt(repoId, 10) + " and created_unix >=" + strconv.FormatInt(beginTime.Unix(), 10) +
  298. " and created_unix<" + strconv.FormatInt(endTime.Unix(), 10)
  299. return sql
  300. }
  301. func generateTargetSql(beginTime time.Time, endTime time.Time, repoId int64) string {
  302. sql := "SELECT date, num_visits,num_downloads,num_commits FROM repo_statistic" +
  303. " where repo_id=" + strconv.FormatInt(repoId, 10) + " and created_unix >=" + strconv.FormatInt(beginTime.Unix(), 10) +
  304. " and created_unix<" + strconv.FormatInt(endTime.Unix(), 10)
  305. return sql
  306. }
  307. func generateCountSql(beginTime time.Time, endTime time.Time, yesterday string, q string) string {
  308. countSql := "SELECT count(*) FROM " +
  309. "(SELECT repo_id FROM repo_statistic where created_unix >=" + strconv.FormatInt(beginTime.Unix(), 10) +
  310. " and created_unix<" + strconv.FormatInt(endTime.Unix(), 10) + " group by repo_id) A," +
  311. "(SELECT repo_id,name,is_private,radar_total from public.repo_statistic where date='" + yesterday + "') B" +
  312. " where A.repo_id=B.repo_id"
  313. if q != "" {
  314. countSql = countSql + " and B.name like '%" + q + "%'"
  315. }
  316. return countSql
  317. }
  318. func generatePageSql(beginTime time.Time, endTime time.Time, yesterday string, q string, orderBy string, page int, pageSize int) string {
  319. countSql := "SELECT A.repo_id,name,is_private,radar_total,num_watches,num_visits,num_downloads,num_pulls,num_commits,num_stars,num_forks,num_issues,num_closed_issues,num_contributor FROM " +
  320. "(SELECT repo_id,sum(num_watches_added) as num_watches,sum(num_visits) as num_visits, sum(num_downloads_added) as num_downloads,sum(num_pulls_added) as num_pulls,sum(num_commits_added) as num_commits,sum(num_stars_added) as num_stars,sum(num_forks_added) num_forks,sum(num_issues_added) as num_issues,sum(num_closed_issues_added) as num_closed_issues,sum(num_contributor_added) as num_contributor " +
  321. " FROM repo_statistic where created_unix >=" + strconv.FormatInt(beginTime.Unix(), 10) +
  322. " and created_unix<" + strconv.FormatInt(endTime.Unix(), 10) + " group by repo_id) A," +
  323. "(SELECT repo_id,name,is_private,radar_total from public.repo_statistic where date='" + yesterday + "') B" +
  324. " where A.repo_id=B.repo_id"
  325. if q != "" {
  326. countSql = countSql + " and B.name like '%" + q + "%'"
  327. }
  328. countSql = countSql + " order by " + orderBy + " desc,A.repo_id" + " limit " + strconv.Itoa(pageSize) + " offset " + strconv.Itoa((page-1)*pageSize)
  329. return countSql
  330. }
  331. func getOrderBy(ctx *context.Context) string {
  332. orderBy := ""
  333. switch ctx.Query("sort") {
  334. case "openi":
  335. orderBy = "B.radar_total"
  336. case "view":
  337. orderBy = "A.num_visits"
  338. case "download":
  339. orderBy = "A.num_downloads"
  340. case "pr":
  341. orderBy = "A.num_pulls"
  342. case "commit":
  343. orderBy = "A.num_commits"
  344. case "watch":
  345. orderBy = "A.num_watches"
  346. case "star":
  347. orderBy = "A.num_stars"
  348. case "fork":
  349. orderBy = "A.num_forks"
  350. case "issue":
  351. orderBy = "A.num_issues"
  352. case "issue_closed":
  353. orderBy = "A.num_closed_issues"
  354. case "contributor":
  355. orderBy = "A.num_contributor"
  356. default:
  357. orderBy = "B.radar_total"
  358. }
  359. return orderBy
  360. }
  361. func getTimePeroid(ctx *context.Context, recordBeginTime time.Time) (time.Time, time.Time, error) {
  362. queryType := ctx.QueryTrim("type")
  363. now := time.Now()
  364. recordBeginTimeTemp := recordBeginTime.AddDate(0, 0, 1)
  365. beginTimeStr := ctx.QueryTrim("beginTime")
  366. endTimeStr := ctx.QueryTrim("endTime")
  367. var beginTime time.Time
  368. var endTime time.Time
  369. if queryType != "" {
  370. if queryType == "all" {
  371. beginTime = recordBeginTimeTemp
  372. endTime = now
  373. } else if queryType == "yesterday" {
  374. endTime = now
  375. beginTime = time.Date(endTime.Year(), endTime.Month(), endTime.Day(), 0, 0, 0, 0, now.Location())
  376. } else if queryType == "current_week" {
  377. beginTime = now.AddDate(0, 0, -int(time.Now().Weekday())+1)
  378. beginTime = time.Date(beginTime.Year(), beginTime.Month(), beginTime.Day(), 0, 0, 0, 0, now.Location())
  379. endTime = now
  380. } else if queryType == "current_month" {
  381. endTime = now
  382. beginTime = time.Date(endTime.Year(), endTime.Month(), 2, 0, 0, 0, 0, now.Location())
  383. } else if queryType == "monthly" {
  384. endTime = now
  385. beginTime = now.AddDate(0, -1, 1)
  386. beginTime = time.Date(beginTime.Year(), beginTime.Month(), beginTime.Day(), 0, 0, 0, 0, now.Location())
  387. } else if queryType == "current_year" {
  388. endTime = now
  389. beginTime = time.Date(endTime.Year(), 1, 2, 0, 0, 0, 0, now.Location())
  390. } else if queryType == "last_month" {
  391. lastMonthTime := now.AddDate(0, -1, 0)
  392. beginTime = time.Date(lastMonthTime.Year(), lastMonthTime.Month(), 2, 0, 0, 0, 0, now.Location())
  393. endTime = time.Date(now.Year(), now.Month(), 2, 0, 0, 0, 0, now.Location())
  394. } else {
  395. return now, now, fmt.Errorf("The value of type parameter is wrong.")
  396. }
  397. } else {
  398. if beginTimeStr == "" || endTimeStr == "" {
  399. //如果查询类型和开始时间结束时间都未设置,按queryType=all处理
  400. beginTime = recordBeginTimeTemp
  401. endTime = now
  402. } else {
  403. beginTime, err := time.Parse("2006-01-02", beginTimeStr)
  404. if err != nil {
  405. return now, now, err
  406. }
  407. endTime, err := time.Parse("2006-01-02", endTimeStr)
  408. if err != nil {
  409. return now, now, err
  410. }
  411. beginTime = beginTime.AddDate(0, 0, 1)
  412. endTime = endTime.AddDate(0, 0, 1)
  413. }
  414. }
  415. if beginTime.Before(recordBeginTimeTemp) {
  416. beginTime = recordBeginTimeTemp
  417. }
  418. return beginTime, endTime, nil
  419. }
  420. func getRecordBeginTime() (time.Time, error) {
  421. return time.Parse(DATE_FORMAT, setting.RadarMap.RecordBeginTime)
  422. }
  423. func getTotalPage(total int64, pageSize int) int {
  424. another := 0
  425. if int(total)%pageSize != 0 {
  426. another = 1
  427. }
  428. return int(total)/pageSize + another
  429. }