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 code frequency graph #29191

Merged
merged 21 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
8 changes: 8 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1914,6 +1914,7 @@ 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.period.filter_label = Period:
activity.period.daily = 1 day
activity.period.halfweekly = 3 days
Expand Down Expand Up @@ -1989,6 +1990,13 @@ contributors.loading_title_failed = Could not load contributions
contributors.loading_info = This might take a bit…
contributors.component_failed_to_load = An unexpected error happened.


code_frequency = Code Frequency
code_frequency.loading_title = Loading code frequency...
code_frequency.loading_title_failed = Could not load code frequency
code_frequency.loading_info = This might take a bit…
code_frequency.component_failed_to_load = An unexpected error happened.
sahinakkaya marked this conversation as resolved.
Show resolved Hide resolved

search = Search
search.search_repo = Search repository
search.type.tooltip = Search type
Expand Down
41 changes: 41 additions & 0 deletions routers/web/repo/code_frequency.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 (
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.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)
}
}
4 changes: 4 additions & 0 deletions routers/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -1397,6 +1397,10 @@ func registerRoutes(m *web.Route) {
m.Get("", repo.Contributors)
m.Get("/data", repo.ContributorsData)
})
m.Group("/code-frequency", func() {
m.Get("", repo.CodeFrequency)
m.Get("/data", repo.CodeFrequencyData)
})
}, 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 @@ -8,6 +8,7 @@
<div class="flex-container-main">
{{if .PageIsPulse}}{{template "repo/pulse" .}}{{end}}
{{if .PageIsContributors}}{{template "repo/contributors" .}}{{end}}
{{if .PageIsCodeFrequency}}{{template "repo/code_frequency" .}}{{end}}
</div>
</div>
</div>
Expand Down
9 changes: 9 additions & 0 deletions templates/repo/code_frequency.tmpl
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 "repo.code_frequency.loading_title"}}"
data-locale-loading-title-failed="{{ctx.Locale.Tr "repo.code_frequency.loading_title_failed"}}"
data-locale-loading-info="{{ctx.Locale.Tr "repo.code_frequency.loading_info"}}"
data-locale-component-failed-to-load="{{ctx.Locale.Tr "repo.code_frequency.component_failed_to_load"}}"
>
</div>
{{end}}
3 changes: 3 additions & 0 deletions templates/repo/navbar.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@
<a class="{{if .PageIsContributors}}active {{end}}item" href="{{.RepoLink}}/activity/contributors">
{{ctx.Locale.Tr "repo.activity.navbar.contributors"}}
</a>
<a class="{{if .PageIsCodeFrequency}}active{{end}} item" href="{{.RepoLink}}/activity/code-frequency">
{{ctx.Locale.Tr "repo.activity.navbar.code_frequency"}}
</a>
</div>
182 changes: 182 additions & 0 deletions web_src/js/components/RepoCodeFrequency.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
<script>
import {SvgIcon} from '../svg.js';
import {
Chart,
Title,
Tooltip,
Legend,
BarElement,
CategoryScale,
LinearScale,
TimeScale,
PointElement,
LineElement,
Filler,
} from 'chart.js';
import {GET} from '../modules/fetch.js';
import zoomPlugin from 'chartjs-plugin-zoom';
import {Line as ChartLine} from 'vue-chartjs';
import {
startDaysBetween,
firstStartDateAfterDate,
fillEmptyStartDaysWithZeroes,
} from '../utils/time.js';
import { colors } 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 = colors.text;
Chart.defaults.borderColor = colors.border;

Chart.register(
TimeScale,
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
PointElement,
LineElement,
Filler,
zoomPlugin
);

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) {
sahinakkaya marked this conversation as resolved.
Show resolved Hide resolved
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: colors['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: colors['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>
20 changes: 3 additions & 17 deletions web_src/js/components/RepoContributors.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,13 @@ import {
firstStartDateAfterDate,
fillEmptyStartDaysWithZeroes,
} from '../utils/time.js';
import { colors } from '../utils/color.js'
import { sleep } from '../utils.js'
import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm';
import $ from 'jquery';

const {pageData} = window.config;

const colors = {
text: '--color-text',
border: '--color-secondary-alpha-60',
commits: '--color-primary-alpha-60',
additions: '--color-green',
deletions: '--color-red',
title: '--color-secondary-dark-4',
};

const styles = window.getComputedStyle(document.documentElement);
const getColor = (name) => styles.getPropertyValue(name).trim();

for (const [key, value] of Object.entries(colors)) {
colors[key] = getColor(value);
}

const customEventListener = {
id: 'customEventListener',
afterEvent: (chart, args, opts) => {
Expand Down Expand Up @@ -122,7 +108,7 @@ export default {
do {
response = await GET(`${this.repoLink}/activity/contributors/data`);
if (response.status === 202) {
await new Promise((resolve) => setTimeout(resolve, 1000)); // wait for 1 second before retrying
await sleep(1000); // wait for 1 second before retrying
}
} while (response.status === 202);
if (response.ok) {
Expand Down
21 changes: 21 additions & 0 deletions web_src/js/features/code-frequency.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {createApp} from 'vue';

export async function initRepoCodeFrequency() {
const el = document.getElementById('repo-code-frequency-chart');
if (!el) return;

const {default: RepoCodeFrequency} = await import(/* webpackChunkName: "code-frequency-graph" */'../components/RepoCodeFrequency.vue');
try {
const View = createApp(RepoCodeFrequency, {
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('RepoCodeFrequency failed to load', err);
el.textContent = el.getAttribute('data-locale-component-failed-to-load');
}
}
2 changes: 2 additions & 0 deletions web_src/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ import {onDomReady} from './utils/dom.js';
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 {initRepoDiffCommitBranchesAndTags} from './features/repo-diff-commit.js';
import {initDirAuto} from './modules/dirauto.js';

Expand Down Expand Up @@ -174,6 +175,7 @@ onDomReady(() => {
initRepository();
initRepositoryActionView();
initRepoContributors();
initRepoCodeFrequency();

initCommitStatuses();
initCaptcha();
Expand Down
2 changes: 2 additions & 0 deletions web_src/js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,5 @@ export function parseDom(text, contentType) {
export function serializeXml(node) {
return xmlSerializer.serializeToString(node);
}

export const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
16 changes: 16 additions & 0 deletions web_src/js/utils/color.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,19 @@ function getLuminance(r, g, b) {
export function useLightTextOnBackground(r, g, b) {
return getLuminance(r, g, b) < 0.453;
}


export const colors = {
text: '--color-text',
border: '--color-secondary-alpha-60',
commits: '--color-primary-alpha-60',
additions: '--color-green',
deletions: '--color-red',
};

const styles = window.getComputedStyle(document.documentElement);
const getColor = (name) => styles.getPropertyValue(name).trim();

for (const [key, value] of Object.entries(colors)) {
colors[key] = getColor(value);
}
sahinakkaya marked this conversation as resolved.
Show resolved Hide resolved
Loading