diff options
Diffstat (limited to 'web_src/js/components/RepoCodeFrequency.vue')
-rw-r--r-- | web_src/js/components/RepoCodeFrequency.vue | 172 |
1 files changed, 172 insertions, 0 deletions
diff --git a/web_src/js/components/RepoCodeFrequency.vue b/web_src/js/components/RepoCodeFrequency.vue new file mode 100644 index 00000000..1d40d6d4 --- /dev/null +++ b/web_src/js/components/RepoCodeFrequency.vue @@ -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(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> |