Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement recent commits graph #29210

Merged
merged 16 commits into from
Feb 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
41 changes: 41 additions & 0 deletions routers/web/repo/recent_commits.go
Original file line number Diff line number Diff line change
@@ -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
sahinakkaya marked this conversation as resolved.
Show resolved Hide resolved

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)
}
}
4 changes: 4 additions & 0 deletions routers/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
1 change: 1 addition & 0 deletions templates/repo/activity.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
3 changes: 3 additions & 0 deletions templates/repo/navbar.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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>
9 changes: 9 additions & 0 deletions templates/repo/recent_commits.tmpl
Original file line number Diff line number Diff line change
@@ -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}}
149 changes: 149 additions & 0 deletions web_src/js/components/RepoRecentCommits.vue
Original file line number Diff line number Diff line change
@@ -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>
sahinakkaya marked this conversation as resolved.
Show resolved Hide resolved
<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;
}
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved
</style>
21 changes: 21 additions & 0 deletions web_src/js/features/recent-commits.js
Original file line number Diff line number Diff line change
@@ -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');
sahinakkaya marked this conversation as resolved.
Show resolved Hide resolved
}
}
2 changes: 2 additions & 0 deletions web_src/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -176,6 +177,7 @@ onDomReady(() => {
initRepositoryActionView();
initRepoContributors();
initRepoCodeFrequency();
initRepoRecentCommits();

initCommitStatuses();
initCaptcha();
Expand Down
Loading