diff options
author | Şahin Akkaya <sahin@sahinakkaya.dev> | 2024-02-24 13:22:51 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-24 10:22:51 +0000 |
commit | d3982bcd814bac93e3cbce1c7eb749b17e413fbd (patch) | |
tree | efac566be7e2d432017a58012954d77a159bce9f | |
parent | 0a426cc575734e5eff410d6a790f40473117f753 (diff) | |
download | gitea-d3982bcd814bac93e3cbce1c7eb749b17e413fbd.tar.gz gitea-d3982bcd814bac93e3cbce1c7eb749b17e413fbd.zip |
Implement recent commits graph (#29210)
This is the implementation of Recent Commits page. This feature was
mentioned on #18262.
It adds another tab to Activity page called Recent Commits. Recent
Commits tab shows number of commits since last year for the repository.
-rw-r--r-- | options/locale/locale_en-US.ini | 4 | ||||
-rw-r--r-- | routers/web/repo/recent_commits.go | 41 | ||||
-rw-r--r-- | routers/web/web.go | 4 | ||||
-rw-r--r-- | templates/repo/activity.tmpl | 1 | ||||
-rw-r--r-- | templates/repo/navbar.tmpl | 3 | ||||
-rw-r--r-- | templates/repo/recent_commits.tmpl | 9 | ||||
-rw-r--r-- | web_src/js/components/RepoRecentCommits.vue | 149 | ||||
-rw-r--r-- | web_src/js/features/recent-commits.js | 21 | ||||
-rw-r--r-- | web_src/js/index.js | 2 |
9 files changed, 233 insertions, 1 deletions
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 2c92f40a17..ff6a3f1b8e 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1915,8 +1915,9 @@ wiki.original_git_entry_tooltip = View original Git file instead of using friend activity = Activity activity.navbar.pulse = Pulse -activity.navbar.contributors = Contributors activity.navbar.code_frequency = Code Frequency +activity.navbar.contributors = Contributors +activity.navbar.recent_commits = Recent Commits activity.period.filter_label = Period: activity.period.daily = 1 day activity.period.halfweekly = 3 days @@ -2597,6 +2598,7 @@ component_loading_info = This might take a bit… component_failed_to_load = An unexpected error happened. code_frequency.what = code frequency contributors.what = contributions +recent_commits.what = recent commits [org] org_name_holder = Organization Name diff --git a/routers/web/repo/recent_commits.go b/routers/web/repo/recent_commits.go new file mode 100644 index 0000000000..3507cb8752 --- /dev/null +++ b/routers/web/repo/recent_commits.go @@ -0,0 +1,41 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +import ( + "errors" + "net/http" + + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/context" + contributors_service "code.gitea.io/gitea/services/repository" +) + +const ( + tplRecentCommits base.TplName = "repo/activity" +) + +// RecentCommits renders the page to show recent commit frequency on repository +func RecentCommits(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("repo.activity.navbar.recent_commits") + + ctx.Data["PageIsActivity"] = true + ctx.Data["PageIsRecentCommits"] = true + ctx.PageData["repoLink"] = ctx.Repo.RepoLink + + ctx.HTML(http.StatusOK, tplRecentCommits) +} + +// RecentCommitsData returns JSON of recent commits data +func RecentCommitsData(ctx *context.Context) { + if contributorStats, err := contributors_service.GetContributorStats(ctx, ctx.Cache, ctx.Repo.Repository, ctx.Repo.CommitID); err != nil { + if errors.Is(err, contributors_service.ErrAwaitGeneration) { + ctx.Status(http.StatusAccepted) + return + } + ctx.ServerError("RecentCommitsData", err) + } else { + ctx.JSON(http.StatusOK, contributorStats["total"].Weeks) + } +} diff --git a/routers/web/web.go b/routers/web/web.go index a76b444e4f..8505417c88 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1402,6 +1402,10 @@ func registerRoutes(m *web.Route) { m.Get("", repo.CodeFrequency) m.Get("/data", repo.CodeFrequencyData) }) + m.Group("/recent-commits", func() { + m.Get("", repo.RecentCommits) + m.Get("/data", repo.RecentCommitsData) + }) }, context.RepoRef(), repo.MustBeNotEmpty, context.RequireRepoReaderOr(unit.TypePullRequests, unit.TypeIssues, unit.TypeReleases)) m.Group("/activity_author_data", func() { diff --git a/templates/repo/activity.tmpl b/templates/repo/activity.tmpl index 94f52b0e26..a19fb66261 100644 --- a/templates/repo/activity.tmpl +++ b/templates/repo/activity.tmpl @@ -9,6 +9,7 @@ {{if .PageIsPulse}}{{template "repo/pulse" .}}{{end}} {{if .PageIsContributors}}{{template "repo/contributors" .}}{{end}} {{if .PageIsCodeFrequency}}{{template "repo/code_frequency" .}}{{end}} + {{if .PageIsRecentCommits}}{{template "repo/recent_commits" .}}{{end}} </div> </div> </div> diff --git a/templates/repo/navbar.tmpl b/templates/repo/navbar.tmpl index aa5021e73a..b2471dc17e 100644 --- a/templates/repo/navbar.tmpl +++ b/templates/repo/navbar.tmpl @@ -8,4 +8,7 @@ <a class="{{if .PageIsCodeFrequency}}active{{end}} item" href="{{.RepoLink}}/activity/code-frequency"> {{ctx.Locale.Tr "repo.activity.navbar.code_frequency"}} </a> + <a class="{{if .PageIsRecentCommits}}active{{end}} item" href="{{.RepoLink}}/activity/recent-commits"> + {{ctx.Locale.Tr "repo.activity.navbar.recent_commits"}} + </a> </div> diff --git a/templates/repo/recent_commits.tmpl b/templates/repo/recent_commits.tmpl new file mode 100644 index 0000000000..5c241d635c --- /dev/null +++ b/templates/repo/recent_commits.tmpl @@ -0,0 +1,9 @@ +{{if .Permission.CanRead $.UnitTypeCode}} + <div id="repo-recent-commits-chart" + data-locale-loading-title="{{ctx.Locale.Tr "graphs.component_loading" (ctx.Locale.Tr "graphs.recent_commits.what")}}" + data-locale-loading-title-failed="{{ctx.Locale.Tr "graphs.component_loading_failed" (ctx.Locale.Tr "graphs.recent_commits.what")}}" + data-locale-loading-info="{{ctx.Locale.Tr "graphs.component_loading_info"}}" + data-locale-component-failed-to-load="{{ctx.Locale.Tr "graphs.component_failed_to_load"}}" + > + </div> +{{end}} diff --git a/web_src/js/components/RepoRecentCommits.vue b/web_src/js/components/RepoRecentCommits.vue new file mode 100644 index 0000000000..77697cd413 --- /dev/null +++ b/web_src/js/components/RepoRecentCommits.vue @@ -0,0 +1,149 @@ +<script> +import {SvgIcon} from '../svg.js'; +import { + Chart, + Tooltip, + BarElement, + LinearScale, + TimeScale, +} from 'chart.js'; +import {GET} from '../modules/fetch.js'; +import {Bar} from 'vue-chartjs'; +import { + startDaysBetween, + firstStartDateAfterDate, + fillEmptyStartDaysWithZeroes, +} from '../utils/time.js'; +import {chartJsColors} from '../utils/color.js'; +import {sleep} from '../utils.js'; +import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm'; + +const {pageData} = window.config; + +Chart.defaults.color = chartJsColors.text; +Chart.defaults.borderColor = chartJsColors.border; + +Chart.register( + TimeScale, + LinearScale, + BarElement, + Tooltip, +); + +export default { + components: {Bar, SvgIcon}, + props: { + locale: { + type: Object, + required: true + }, + }, + data: () => ({ + isLoading: false, + errorText: '', + repoLink: pageData.repoLink || [], + data: [], + }), + mounted() { + this.fetchGraphData(); + }, + methods: { + async fetchGraphData() { + this.isLoading = true; + try { + let response; + do { + response = await GET(`${this.repoLink}/activity/recent-commits/data`); + if (response.status === 202) { + await sleep(1000); // wait for 1 second before retrying + } + } while (response.status === 202); + if (response.ok) { + const data = await response.json(); + const start = Object.values(data)[0].week; + const end = firstStartDateAfterDate(new Date()); + const startDays = startDaysBetween(new Date(start), new Date(end)); + this.data = fillEmptyStartDaysWithZeroes(startDays, data).slice(-52); + this.errorText = ''; + } else { + this.errorText = response.statusText; + } + } catch (err) { + this.errorText = err.message; + } finally { + this.isLoading = false; + } + }, + + toGraphData(data) { + return { + datasets: [ + { + data: data.map((i) => ({x: i.week, y: i.commits})), + label: 'Commits', + backgroundColor: chartJsColors['commits'], + borderWidth: 0, + tension: 0.3, + }, + ], + }; + }, + + getOptions() { + return { + responsive: true, + maintainAspectRatio: false, + animation: true, + scales: { + x: { + type: 'time', + grid: { + display: false, + }, + time: { + minUnit: 'week', + }, + ticks: { + maxRotation: 0, + maxTicksLimit: 52 + }, + }, + y: { + ticks: { + maxTicksLimit: 6 + }, + }, + }, + }; + }, + }, +}; +</script> +<template> + <div> + <div class="ui header gt-df gt-ac gt-sb"> + {{ isLoading ? locale.loadingTitle : errorText ? locale.loadingTitleFailed: "Number of commits in the past year" }} + </div> + <div class="gt-df ui segment main-graph"> + <div v-if="isLoading || errorText !== ''" class="gt-tc gt-m-auto"> + <div v-if="isLoading"> + <SvgIcon name="octicon-sync" class="gt-mr-3 job-status-rotate"/> + {{ locale.loadingInfo }} + </div> + <div v-else class="text red"> + <SvgIcon name="octicon-x-circle-fill"/> + {{ errorText }} + </div> + </div> + <Bar + v-memo="data" v-if="data.length !== 0" + :data="toGraphData(data)" :options="getOptions()" + /> + </div> + </div> +</template> +<style scoped> +.main-graph { + height: 250px; +} +</style> diff --git a/web_src/js/features/recent-commits.js b/web_src/js/features/recent-commits.js new file mode 100644 index 0000000000..ded10d39be --- /dev/null +++ b/web_src/js/features/recent-commits.js @@ -0,0 +1,21 @@ +import {createApp} from 'vue'; + +export async function initRepoRecentCommits() { + const el = document.getElementById('repo-recent-commits-chart'); + if (!el) return; + + const {default: RepoRecentCommits} = await import(/* webpackChunkName: "recent-commits-graph" */'../components/RepoRecentCommits.vue'); + try { + const View = createApp(RepoRecentCommits, { + locale: { + loadingTitle: el.getAttribute('data-locale-loading-title'), + loadingTitleFailed: el.getAttribute('data-locale-loading-title-failed'), + loadingInfo: el.getAttribute('data-locale-loading-info'), + } + }); + View.mount(el); + } catch (err) { + console.error('RepoRecentCommits failed to load', err); + el.textContent = el.getAttribute('data-locale-component-failed-to-load'); + } +} diff --git a/web_src/js/index.js b/web_src/js/index.js index d9cfff4084..b7f3ba99a0 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -85,6 +85,7 @@ import {initRepoIssueList} from './features/repo-issue-list.js'; import {initCommonIssueListQuickGoto} from './features/common-issue-list.js'; import {initRepoContributors} from './features/contributors.js'; import {initRepoCodeFrequency} from './features/code-frequency.js'; +import {initRepoRecentCommits} from './features/recent-commits.js'; import {initRepoDiffCommitBranchesAndTags} from './features/repo-diff-commit.js'; import {initDirAuto} from './modules/dirauto.js'; @@ -176,6 +177,7 @@ onDomReady(() => { initRepositoryActionView(); initRepoContributors(); initRepoCodeFrequency(); + initRepoRecentCommits(); initCommitStatuses(); initCaptcha(); |