1
1
mirror of https://github.com/go-gitea/gitea synced 2024-12-23 09:04:26 +00:00
gitea/web_src/js/components/RepoRecentCommits.vue

151 lines
3.6 KiB
Vue
Raw Normal View History

<script lang="ts" setup>
import {SvgIcon} from '../svg.ts';
import {
Chart,
Tooltip,
BarElement,
LinearScale,
TimeScale,
type ChartOptions,
type ChartData,
} from 'chart.js';
import {GET} from '../modules/fetch.ts';
import {Bar} from 'vue-chartjs';
import {
startDaysBetween,
firstStartDateAfterDate,
fillEmptyStartDaysWithZeroes,
type DayData,
type DayDataObject,
} from '../utils/time.ts';
import {chartJsColors} from '../utils/color.ts';
import {sleep} from '../utils.ts';
import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm';
import {onMounted, ref} from 'vue';
const {pageData} = window.config;
Chart.defaults.color = chartJsColors.text;
Chart.defaults.borderColor = chartJsColors.border;
Chart.register(
TimeScale,
LinearScale,
BarElement,
Tooltip,
);
defineProps<{
locale: {
loadingTitle: string;
loadingTitleFailed: string;
loadingInfo: string;
};
}>();
const isLoading = ref(false);
const errorText = ref('');
const repoLink = ref(pageData.repoLink || []);
const data = ref<DayData[]>([]);
onMounted(() => {
fetchGraphData();
});
async function fetchGraphData() {
isLoading.value = true;
try {
let response: Response;
do {
response = await GET(`${repoLink.value}/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 dayDataObj: DayDataObject = await response.json();
const start = Object.values(dayDataObj)[0].week;
const end = firstStartDateAfterDate(new Date());
const startDays = startDaysBetween(start, end);
data.value = fillEmptyStartDaysWithZeroes(startDays, dayDataObj).slice(-52);
errorText.value = '';
} else {
errorText.value = response.statusText;
}
} catch (err) {
errorText.value = err.message;
} finally {
isLoading.value = false;
}
}
function toGraphData(data: DayData[]): ChartData<'bar'> {
return {
datasets: [
{
// @ts-expect-error -- bar chart expects one-dimensional data, but apparently x/y still works
data: data.map((i) => ({x: i.week, y: i.commits})),
label: 'Commits',
backgroundColor: chartJsColors['commits'],
borderWidth: 0,
tension: 0.3,
},
],
};
}
const options: ChartOptions<'bar'> = {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
type: 'time',
grid: {
display: false,
},
time: {
minUnit: 'week',
},
ticks: {
maxRotation: 0,
maxTicksLimit: 52,
},
},
y: {
ticks: {
maxTicksLimit: 6,
},
},
},
} satisfies ChartOptions;
</script>
<template>
<div>
<div class="ui header tw-flex tw-items-center tw-justify-between">
{{ isLoading ? locale.loadingTitle : errorText ? locale.loadingTitleFailed: "Number of commits in the past year" }}
</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>
<Bar
v-memo="data" v-if="data.length !== 0"
:data="toGraphData(data)" :options="options"
/>
</div>
</div>
</template>
<style scoped>
.main-graph {
height: 250px;
}
</style>