mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-09-08 04:38:27 +02:00
Before this patch, we were using `Date` getter/setter methods that worked with local time to get a list of Sundays that are in the range of some start date and end date. The problem with this was that the Sundays are in Unix epoch time and when we changed the "startDate" argument that was passed to make sure it is on a Sunday, this change would be reflected when we convert it to Unix epoch time. More specifically, I observed that we may get different Unix epochs depending on your timezone when the returned list should rather be timezone-agnostic. This led to issues in US timezones that caused the contributor, code frequency, and recent commit charts to not show any chart data. This fix resolves this by using getter/setter methods that work with UTC since it isn't dependent on timezones. Fixes #30851. --------- Co-authored-by: Sam Fisher <fisher@3echelon.local> (cherry picked from commit 22c7b3a74459833b86783e84d4708c8934d34e58)
172 lines
4.2 KiB
Vue
172 lines
4.2 KiB
Vue
<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(start, 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 tw-flex tw-items-center tw-justify-between">
|
|
{{ isLoading ? locale.loadingTitle : errorText ? locale.loadingTitleFailed: `Code frequency over the history of ${repoLink.slice(1)}` }}
|
|
</div>
|
|
<div class="tw-flex ui segment main-graph">
|
|
<div v-if="isLoading || errorText !== ''" class="gt-tc tw-m-auto">
|
|
<div v-if="isLoading">
|
|
<SvgIcon name="octicon-sync" class="tw-mr-2 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>
|