code.gitea.io/gitea@v1.22.3/web_src/js/components/RepoRecentCommits.vue (about) 1 <script> 2 import {SvgIcon} from '../svg.js'; 3 import { 4 Chart, 5 Tooltip, 6 BarElement, 7 LinearScale, 8 TimeScale, 9 } from 'chart.js'; 10 import {GET} from '../modules/fetch.js'; 11 import {Bar} from 'vue-chartjs'; 12 import { 13 startDaysBetween, 14 firstStartDateAfterDate, 15 fillEmptyStartDaysWithZeroes, 16 } from '../utils/time.js'; 17 import {chartJsColors} from '../utils/color.js'; 18 import {sleep} from '../utils.js'; 19 import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm'; 20 21 const {pageData} = window.config; 22 23 Chart.defaults.color = chartJsColors.text; 24 Chart.defaults.borderColor = chartJsColors.border; 25 26 Chart.register( 27 TimeScale, 28 LinearScale, 29 BarElement, 30 Tooltip, 31 ); 32 33 export default { 34 components: {Bar, SvgIcon}, 35 props: { 36 locale: { 37 type: Object, 38 required: true, 39 }, 40 }, 41 data: () => ({ 42 isLoading: false, 43 errorText: '', 44 repoLink: pageData.repoLink || [], 45 data: [], 46 }), 47 mounted() { 48 this.fetchGraphData(); 49 }, 50 methods: { 51 async fetchGraphData() { 52 this.isLoading = true; 53 try { 54 let response; 55 do { 56 response = await GET(`${this.repoLink}/activity/recent-commits/data`); 57 if (response.status === 202) { 58 await sleep(1000); // wait for 1 second before retrying 59 } 60 } while (response.status === 202); 61 if (response.ok) { 62 const data = await response.json(); 63 const start = Object.values(data)[0].week; 64 const end = firstStartDateAfterDate(new Date()); 65 const startDays = startDaysBetween(start, end); 66 this.data = fillEmptyStartDaysWithZeroes(startDays, data).slice(-52); 67 this.errorText = ''; 68 } else { 69 this.errorText = response.statusText; 70 } 71 } catch (err) { 72 this.errorText = err.message; 73 } finally { 74 this.isLoading = false; 75 } 76 }, 77 78 toGraphData(data) { 79 return { 80 datasets: [ 81 { 82 data: data.map((i) => ({x: i.week, y: i.commits})), 83 label: 'Commits', 84 backgroundColor: chartJsColors['commits'], 85 borderWidth: 0, 86 tension: 0.3, 87 }, 88 ], 89 }; 90 }, 91 92 getOptions() { 93 return { 94 responsive: true, 95 maintainAspectRatio: false, 96 animation: true, 97 scales: { 98 x: { 99 type: 'time', 100 grid: { 101 display: false, 102 }, 103 time: { 104 minUnit: 'week', 105 }, 106 ticks: { 107 maxRotation: 0, 108 maxTicksLimit: 52, 109 }, 110 }, 111 y: { 112 ticks: { 113 maxTicksLimit: 6, 114 }, 115 }, 116 }, 117 }; 118 }, 119 }, 120 }; 121 </script> 122 <template> 123 <div> 124 <div class="ui header tw-flex tw-items-center tw-justify-between"> 125 {{ isLoading ? locale.loadingTitle : errorText ? locale.loadingTitleFailed: "Number of commits in the past year" }} 126 </div> 127 <div class="tw-flex ui segment main-graph"> 128 <div v-if="isLoading || errorText !== ''" class="gt-tc tw-m-auto"> 129 <div v-if="isLoading"> 130 <SvgIcon name="octicon-sync" class="tw-mr-2 job-status-rotate"/> 131 {{ locale.loadingInfo }} 132 </div> 133 <div v-else class="text red"> 134 <SvgIcon name="octicon-x-circle-fill"/> 135 {{ errorText }} 136 </div> 137 </div> 138 <Bar 139 v-memo="data" v-if="data.length !== 0" 140 :data="toGraphData(data)" :options="getOptions()" 141 /> 142 </div> 143 </div> 144 </template> 145 <style scoped> 146 .main-graph { 147 height: 250px; 148 } 149 </style>