mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-31 03:18:24 +00:00 
			
		
		
		
	Implement recent commits graph (#29210)
This is the implementation of Recent Commits page. This feature was mentioned on #18262. It adds another tab to Activity page called Recent Commits. Recent Commits tab shows number of commits since last year for the repository.
This commit is contained in:
		
							
								
								
									
										149
									
								
								web_src/js/components/RepoRecentCommits.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								web_src/js/components/RepoRecentCommits.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,149 @@ | ||||
| <script> | ||||
| import {SvgIcon} from '../svg.js'; | ||||
| import { | ||||
|   Chart, | ||||
|   Tooltip, | ||||
|   BarElement, | ||||
|   LinearScale, | ||||
|   TimeScale, | ||||
| } from 'chart.js'; | ||||
| import {GET} from '../modules/fetch.js'; | ||||
| import {Bar} 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, | ||||
|   BarElement, | ||||
|   Tooltip, | ||||
| ); | ||||
|  | ||||
| export default { | ||||
|   components: {Bar, 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/recent-commits/data`); | ||||
|           if (response.status === 202) { | ||||
|             await sleep(1000); // wait for 1 second before retrying | ||||
|           } | ||||
|         } while (response.status === 202); | ||||
|         if (response.ok) { | ||||
|           const data = await response.json(); | ||||
|           const start = Object.values(data)[0].week; | ||||
|           const end = firstStartDateAfterDate(new Date()); | ||||
|           const startDays = startDaysBetween(new Date(start), new Date(end)); | ||||
|           this.data = fillEmptyStartDaysWithZeroes(startDays, data).slice(-52); | ||||
|           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.commits})), | ||||
|             label: 'Commits', | ||||
|             backgroundColor: chartJsColors['commits'], | ||||
|             borderWidth: 0, | ||||
|             tension: 0.3, | ||||
|           }, | ||||
|         ], | ||||
|       }; | ||||
|     }, | ||||
|  | ||||
|     getOptions() { | ||||
|       return { | ||||
|         responsive: true, | ||||
|         maintainAspectRatio: false, | ||||
|         animation: true, | ||||
|         scales: { | ||||
|           x: { | ||||
|             type: 'time', | ||||
|             grid: { | ||||
|               display: false, | ||||
|             }, | ||||
|             time: { | ||||
|               minUnit: 'week', | ||||
|             }, | ||||
|             ticks: { | ||||
|               maxRotation: 0, | ||||
|               maxTicksLimit: 52 | ||||
|             }, | ||||
|           }, | ||||
|           y: { | ||||
|             ticks: { | ||||
|               maxTicksLimit: 6 | ||||
|             }, | ||||
|           }, | ||||
|         }, | ||||
|       }; | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
| <template> | ||||
|   <div> | ||||
|     <div class="ui header gt-df gt-ac gt-sb"> | ||||
|       {{ isLoading ? locale.loadingTitle : errorText ? locale.loadingTitleFailed: "Number of commits in the past year" }} | ||||
|     </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> | ||||
|       <Bar | ||||
|         v-memo="data" v-if="data.length !== 0" | ||||
|         :data="toGraphData(data)" :options="getOptions()" | ||||
|       /> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| <style scoped> | ||||
| .main-graph { | ||||
|   height: 250px; | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										21
									
								
								web_src/js/features/recent-commits.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								web_src/js/features/recent-commits.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| import {createApp} from 'vue'; | ||||
|  | ||||
| export async function initRepoRecentCommits() { | ||||
|   const el = document.getElementById('repo-recent-commits-chart'); | ||||
|   if (!el) return; | ||||
|  | ||||
|   const {default: RepoRecentCommits} = await import(/* webpackChunkName: "recent-commits-graph" */'../components/RepoRecentCommits.vue'); | ||||
|   try { | ||||
|     const View = createApp(RepoRecentCommits, { | ||||
|       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('RepoRecentCommits failed to load', err); | ||||
|     el.textContent = el.getAttribute('data-locale-component-failed-to-load'); | ||||
|   } | ||||
| } | ||||
| @@ -85,6 +85,7 @@ 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 {initRepoRecentCommits} from './features/recent-commits.js'; | ||||
| import {initRepoDiffCommitBranchesAndTags} from './features/repo-diff-commit.js'; | ||||
| import {initDirAuto} from './modules/dirauto.js'; | ||||
|  | ||||
| @@ -176,6 +177,7 @@ onDomReady(() => { | ||||
|   initRepositoryActionView(); | ||||
|   initRepoContributors(); | ||||
|   initRepoCodeFrequency(); | ||||
|   initRepoRecentCommits(); | ||||
|  | ||||
|   initCommitStatuses(); | ||||
|   initCaptcha(); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user