-
-
Notifications
You must be signed in to change notification settings - Fork 5.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement code frequency graph (#29191)
### Overview This is the implementation of Code Frequency page. This feature was mentioned on these issues: #18262, #7392. It adds another tab to Activity page called Code Frequency. Code Frequency tab shows additions and deletions over time since the repository existed. Before: <img width="1296" alt="image" src="https://github.com/go-gitea/gitea/assets/32161460/2603504f-aee7-4929-a8c4-fb3412a7a0f6"> After: <img width="1296" alt="image" src="https://github.com/go-gitea/gitea/assets/32161460/58c03721-729f-4536-a663-9f337f240963"> --- #### Features - See additions deletions over time since repository existed - Click on "Additions" or "Deletions" legend to show only one type of contribution - Use the same cache from Contributors page so that the loading of data will be fast once it is cached by visiting either one of the pages --------- Co-authored-by: Giteabot <teabot@gitea.io>
- Loading branch information
1 parent
6f6120d
commit 875f5ea
Showing
13 changed files
with
277 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 ( | ||
tplCodeFrequency base.TplName = "repo/activity" | ||
) | ||
|
||
// CodeFrequency renders the page to show repository code frequency | ||
func CodeFrequency(ctx *context.Context) { | ||
ctx.Data["Title"] = ctx.Tr("repo.activity.navbar.code_frequency") | ||
|
||
ctx.Data["PageIsActivity"] = true | ||
ctx.Data["PageIsCodeFrequency"] = true | ||
ctx.PageData["repoLink"] = ctx.Repo.RepoLink | ||
|
||
ctx.HTML(http.StatusOK, tplCodeFrequency) | ||
} | ||
|
||
// CodeFrequencyData returns JSON of code frequency data | ||
func CodeFrequencyData(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("GetCodeFrequencyData", err) | ||
} else { | ||
ctx.JSON(http.StatusOK, contributorStats["total"].Weeks) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{{if .Permission.CanRead $.UnitTypeCode}} | ||
<div id="repo-code-frequency-chart" | ||
data-locale-loading-title="{{ctx.Locale.Tr "graphs.component_loading" (ctx.Locale.Tr "graphs.code_frequency.what")}}" | ||
data-locale-loading-title-failed="{{ctx.Locale.Tr "graphs.component_loading_failed" (ctx.Locale.Tr "graphs.code_frequency.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}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
<script> | ||
import {SvgIcon} from '../svg.js'; | ||
import { | ||
Chart, | ||
Legend, | ||
LinearScale, | ||
TimeScale, | ||
PointElement, | ||
LineElement, | ||
Filler, | ||
} from 'chart.js'; | ||
import {GET} from '../modules/fetch.js'; | ||
import {Line as ChartLine} 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, | ||
Legend, | ||
PointElement, | ||
LineElement, | ||
Filler, | ||
); | ||
export default { | ||
components: {ChartLine, 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/code-frequency/data`); | ||
if (response.status === 202) { | ||
await sleep(1000); // wait for 1 second before retrying | ||
} | ||
} while (response.status === 202); | ||
if (response.ok) { | ||
this.data = await response.json(); | ||
const weekValues = Object.values(this.data); | ||
const start = weekValues[0].week; | ||
const end = firstStartDateAfterDate(new Date()); | ||
const startDays = startDaysBetween(new Date(start), new Date(end)); | ||
this.data = fillEmptyStartDaysWithZeroes(startDays, this.data); | ||
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.additions})), | ||
pointRadius: 0, | ||
pointHitRadius: 0, | ||
fill: true, | ||
label: 'Additions', | ||
backgroundColor: chartJsColors['additions'], | ||
borderWidth: 0, | ||
tension: 0.3, | ||
}, | ||
{ | ||
data: data.map((i) => ({x: i.week, y: -i.deletions})), | ||
pointRadius: 0, | ||
pointHitRadius: 0, | ||
fill: true, | ||
label: 'Deletions', | ||
backgroundColor: chartJsColors['deletions'], | ||
borderWidth: 0, | ||
tension: 0.3, | ||
}, | ||
], | ||
}; | ||
}, | ||
getOptions() { | ||
return { | ||
responsive: true, | ||
maintainAspectRatio: false, | ||
animation: true, | ||
plugins: { | ||
legend: { | ||
display: true, | ||
}, | ||
}, | ||
scales: { | ||
x: { | ||
type: 'time', | ||
grid: { | ||
display: false, | ||
}, | ||
time: { | ||
minUnit: 'month', | ||
}, | ||
ticks: { | ||
maxRotation: 0, | ||
maxTicksLimit: 12 | ||
}, | ||
}, | ||
y: { | ||
ticks: { | ||
maxTicksLimit: 6 | ||
}, | ||
}, | ||
}, | ||
}; | ||
}, | ||
}, | ||
}; | ||
</script> | ||
<template> | ||
<div> | ||
<div class="ui header gt-df gt-ac gt-sb"> | ||
{{ isLoading ? locale.loadingTitle : errorText ? locale.loadingTitleFailed: `Code frequency over the history of ${repoLink.slice(1)}` }} | ||
</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> | ||
<ChartLine | ||
v-memo="data" v-if="data.length !== 0" | ||
:data="toGraphData(data)" :options="getOptions()" | ||
/> | ||
</div> | ||
</div> | ||
</template> | ||
<style scoped> | ||
.main-graph { | ||
height: 440px; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.