Fix typescript errors in Vue files, fix regression in "Recent Commits" chart (#32649)

- Fix all typescript errors in `.vue` files
- Fix regression from https://github.com/go-gitea/gitea/pull/32329 where
"Recent Commits" chart would not render.

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
silverwind 2024-12-08 03:58:18 +01:00 committed by GitHub
parent 96d3a03a08
commit 1518f4ed12
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 59 additions and 37 deletions

View File

@ -6,8 +6,17 @@ import {fomanticQuery} from '../modules/fomantic/base.ts';
const {appSubUrl, assetUrlPrefix, pageData} = window.config; const {appSubUrl, assetUrlPrefix, pageData} = window.config;
type CommitStatus = 'pending' | 'success' | 'error' | 'failure' | 'warning';
type CommitStatusMap = {
[status in CommitStatus]: {
name: string,
color: string,
};
};
// make sure this matches templates/repo/commit_status.tmpl // make sure this matches templates/repo/commit_status.tmpl
const commitStatus = { const commitStatus: CommitStatusMap = {
pending: {name: 'octicon-dot-fill', color: 'yellow'}, pending: {name: 'octicon-dot-fill', color: 'yellow'},
success: {name: 'octicon-check', color: 'green'}, success: {name: 'octicon-check', color: 'green'},
error: {name: 'gitea-exclamation', color: 'red'}, error: {name: 'gitea-exclamation', color: 'red'},
@ -281,18 +290,18 @@ const sfc = {
return 'octicon-repo'; return 'octicon-repo';
}, },
statusIcon(status) { statusIcon(status: CommitStatus) {
return commitStatus[status].name; return commitStatus[status].name;
}, },
statusColor(status) { statusColor(status: CommitStatus) {
return commitStatus[status].color; return commitStatus[status].color;
}, },
reposFilterKeyControl(e) { reposFilterKeyControl(e) {
switch (e.key) { switch (e.key) {
case 'Enter': case 'Enter':
document.querySelector('.repo-owner-name-list li.active a')?.click(); document.querySelector<HTMLAnchorElement>('.repo-owner-name-list li.active a')?.click();
break; break;
case 'ArrowUp': case 'ArrowUp':
if (this.activeIndex > 0) { if (this.activeIndex > 0) {

View File

@ -14,7 +14,7 @@ export default {
issueLink: el.getAttribute('data-issuelink'), issueLink: el.getAttribute('data-issuelink'),
locale: { locale: {
filter_changes_by_commit: el.getAttribute('data-filter_changes_by_commit'), filter_changes_by_commit: el.getAttribute('data-filter_changes_by_commit'),
}, } as Record<string, string>,
commits: [], commits: [],
hoverActivated: false, hoverActivated: false,
lastReviewCommitSha: null, lastReviewCommitSha: null,
@ -41,16 +41,16 @@ export default {
this.$el.removeEventListener('keyup', this.onKeyUp); this.$el.removeEventListener('keyup', this.onKeyUp);
}, },
methods: { methods: {
onBodyClick(event) { onBodyClick(event: MouseEvent) {
// close this menu on click outside of this element when the dropdown is currently visible opened // close this menu on click outside of this element when the dropdown is currently visible opened
if (this.$el.contains(event.target)) return; if (this.$el.contains(event.target)) return;
if (this.menuVisible) { if (this.menuVisible) {
this.toggleMenu(); this.toggleMenu();
} }
}, },
onKeyDown(event) { onKeyDown(event: KeyboardEvent) {
if (!this.menuVisible) return; if (!this.menuVisible) return;
const item = document.activeElement; const item = document.activeElement as HTMLElement;
if (!this.$el.contains(item)) return; if (!this.$el.contains(item)) return;
switch (event.key) { switch (event.key) {
case 'ArrowDown': // select next element case 'ArrowDown': // select next element
@ -73,7 +73,7 @@ export default {
if (commitIdx) this.highlight(this.commits[commitIdx]); if (commitIdx) this.highlight(this.commits[commitIdx]);
} }
}, },
onKeyUp(event) { onKeyUp(event: KeyboardEvent) {
if (!this.menuVisible) return; if (!this.menuVisible) return;
const item = document.activeElement; const item = document.activeElement;
if (!this.$el.contains(item)) return; if (!this.$el.contains(item)) return;
@ -95,7 +95,7 @@ export default {
} }
}, },
/** Focus given element */ /** Focus given element */
focusElem(elem, prevElem) { focusElem(elem: HTMLElement, prevElem: HTMLElement) {
if (elem) { if (elem) {
elem.tabIndex = 0; elem.tabIndex = 0;
if (prevElem) prevElem.tabIndex = -1; if (prevElem) prevElem.tabIndex = -1;
@ -149,7 +149,7 @@ export default {
window.location.assign(`${this.issueLink}/files/${this.lastReviewCommitSha}..${this.commits.at(-1).id}${this.queryParams}`); window.location.assign(`${this.issueLink}/files/${this.lastReviewCommitSha}..${this.commits.at(-1).id}${this.queryParams}`);
}, },
/** Clicking on a single commit opens this specific commit */ /** Clicking on a single commit opens this specific commit */
commitClicked(commitId, newWindow = false) { commitClicked(commitId: string, newWindow = false) {
const url = `${this.issueLink}/commits/${commitId}${this.queryParams}`; const url = `${this.issueLink}/commits/${commitId}${this.queryParams}`;
if (newWindow) { if (newWindow) {
window.open(url); window.open(url);

View File

@ -8,6 +8,8 @@ import {
PointElement, PointElement,
LineElement, LineElement,
Filler, Filler,
type ChartOptions,
type ChartData,
} from 'chart.js'; } from 'chart.js';
import {GET} from '../modules/fetch.ts'; import {GET} from '../modules/fetch.ts';
import {Line as ChartLine} from 'vue-chartjs'; import {Line as ChartLine} from 'vue-chartjs';
@ -16,6 +18,7 @@ import {
firstStartDateAfterDate, firstStartDateAfterDate,
fillEmptyStartDaysWithZeroes, fillEmptyStartDaysWithZeroes,
type DayData, type DayData,
type DayDataObject,
} from '../utils/time.ts'; } from '../utils/time.ts';
import {chartJsColors} from '../utils/color.ts'; import {chartJsColors} from '../utils/color.ts';
import {sleep} from '../utils.ts'; import {sleep} from '../utils.ts';
@ -64,12 +67,12 @@ async function fetchGraphData() {
} }
} while (response.status === 202); } while (response.status === 202);
if (response.ok) { if (response.ok) {
data.value = await response.json(); const dayDataObject: DayDataObject = await response.json();
const weekValues = Object.values(data.value); const weekValues = Object.values(dayDataObject);
const start = weekValues[0].week; const start = weekValues[0].week;
const end = firstStartDateAfterDate(new Date()); const end = firstStartDateAfterDate(new Date());
const startDays = startDaysBetween(start, end); const startDays = startDaysBetween(start, end);
data.value = fillEmptyStartDaysWithZeroes(startDays, data.value); data.value = fillEmptyStartDaysWithZeroes(startDays, dayDataObject);
errorText.value = ''; errorText.value = '';
} else { } else {
errorText.value = response.statusText; errorText.value = response.statusText;
@ -81,7 +84,7 @@ async function fetchGraphData() {
} }
} }
function toGraphData(data) { function toGraphData(data: Array<Record<string, any>>): ChartData<'line'> {
return { return {
datasets: [ datasets: [
{ {
@ -108,10 +111,9 @@ function toGraphData(data) {
}; };
} }
const options = { const options: ChartOptions<'line'> = {
responsive: true, responsive: true,
maintainAspectRatio: false, maintainAspectRatio: false,
animation: true,
plugins: { plugins: {
legend: { legend: {
display: true, display: true,

View File

@ -9,6 +9,9 @@ import {
PointElement, PointElement,
LineElement, LineElement,
Filler, Filler,
type ChartOptions,
type ChartData,
type Plugin,
} from 'chart.js'; } from 'chart.js';
import {GET} from '../modules/fetch.ts'; import {GET} from '../modules/fetch.ts';
import zoomPlugin from 'chartjs-plugin-zoom'; import zoomPlugin from 'chartjs-plugin-zoom';
@ -22,8 +25,9 @@ import {chartJsColors} from '../utils/color.ts';
import {sleep} from '../utils.ts'; import {sleep} from '../utils.ts';
import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm'; import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm';
import {fomanticQuery} from '../modules/fomantic/base.ts'; import {fomanticQuery} from '../modules/fomantic/base.ts';
import type {Entries} from 'type-fest';
const customEventListener = { const customEventListener: Plugin = {
id: 'customEventListener', id: 'customEventListener',
afterEvent: (chart, args, opts) => { afterEvent: (chart, args, opts) => {
// event will be replayed from chart.update when reset zoom, // event will be replayed from chart.update when reset zoom,
@ -65,10 +69,10 @@ export default {
data: () => ({ data: () => ({
isLoading: false, isLoading: false,
errorText: '', errorText: '',
totalStats: {}, totalStats: {} as Record<string, any>,
sortedContributors: {}, sortedContributors: {} as Record<string, any>,
type: 'commits', type: 'commits',
contributorsStats: [], contributorsStats: {} as Record<string, any>,
xAxisStart: null, xAxisStart: null,
xAxisEnd: null, xAxisEnd: null,
xAxisMin: null, xAxisMin: null,
@ -99,7 +103,7 @@ export default {
async fetchGraphData() { async fetchGraphData() {
this.isLoading = true; this.isLoading = true;
try { try {
let response; let response: Response;
do { do {
response = await GET(`${this.repoLink}/activity/contributors/data`); response = await GET(`${this.repoLink}/activity/contributors/data`);
if (response.status === 202) { if (response.status === 202) {
@ -112,7 +116,7 @@ export default {
// below line might be deleted if we are sure go produces map always sorted by keys // below line might be deleted if we are sure go produces map always sorted by keys
total.weeks = Object.fromEntries(Object.entries(total.weeks).sort()); total.weeks = Object.fromEntries(Object.entries(total.weeks).sort());
const weekValues = Object.values(total.weeks); const weekValues = Object.values(total.weeks) as any;
this.xAxisStart = weekValues[0].week; this.xAxisStart = weekValues[0].week;
this.xAxisEnd = firstStartDateAfterDate(new Date()); this.xAxisEnd = firstStartDateAfterDate(new Date());
const startDays = startDaysBetween(this.xAxisStart, this.xAxisEnd); const startDays = startDaysBetween(this.xAxisStart, this.xAxisEnd);
@ -120,7 +124,7 @@ export default {
this.xAxisMin = this.xAxisStart; this.xAxisMin = this.xAxisStart;
this.xAxisMax = this.xAxisEnd; this.xAxisMax = this.xAxisEnd;
this.contributorsStats = {}; this.contributorsStats = {};
for (const [email, user] of Object.entries(rest)) { for (const [email, user] of Object.entries(rest) as Entries<Record<string, Record<string, any>>>) {
user.weeks = fillEmptyStartDaysWithZeroes(startDays, user.weeks); user.weeks = fillEmptyStartDaysWithZeroes(startDays, user.weeks);
this.contributorsStats[email] = user; this.contributorsStats[email] = user;
} }
@ -146,7 +150,7 @@ export default {
user.total_additions = 0; user.total_additions = 0;
user.total_deletions = 0; user.total_deletions = 0;
user.max_contribution_type = 0; user.max_contribution_type = 0;
const filteredWeeks = user.weeks.filter((week) => { const filteredWeeks = user.weeks.filter((week: Record<string, number>) => {
const oneWeek = 7 * 24 * 60 * 60 * 1000; const oneWeek = 7 * 24 * 60 * 60 * 1000;
if (week.week >= this.xAxisMin - oneWeek && week.week <= this.xAxisMax + oneWeek) { if (week.week >= this.xAxisMin - oneWeek && week.week <= this.xAxisMax + oneWeek) {
user.total_commits += week.commits; user.total_commits += week.commits;
@ -195,7 +199,7 @@ export default {
return (1 - (coefficient % 1)) * 10 ** exp + maxValue; return (1 - (coefficient % 1)) * 10 ** exp + maxValue;
}, },
toGraphData(data) { toGraphData(data: Array<Record<string, any>>): ChartData<'line'> {
return { return {
datasets: [ datasets: [
{ {
@ -211,9 +215,9 @@ export default {
}; };
}, },
updateOtherCharts(event, reset) { updateOtherCharts({chart}: {chart: Chart}, reset?: boolean = false) {
const minVal = event.chart.options.scales.x.min; const minVal = chart.options.scales.x.min;
const maxVal = event.chart.options.scales.x.max; const maxVal = chart.options.scales.x.max;
if (reset) { if (reset) {
this.xAxisMin = this.xAxisStart; this.xAxisMin = this.xAxisStart;
this.xAxisMax = this.xAxisEnd; this.xAxisMax = this.xAxisEnd;
@ -225,7 +229,7 @@ export default {
} }
}, },
getOptions(type) { getOptions(type: string): ChartOptions<'line'> {
return { return {
responsive: true, responsive: true,
maintainAspectRatio: false, maintainAspectRatio: false,
@ -238,6 +242,7 @@ export default {
position: 'top', position: 'top',
align: 'center', align: 'center',
}, },
// @ts-expect-error: bug in chart.js types
customEventListener: { customEventListener: {
chartType: type, chartType: type,
instance: this, instance: this,

View File

@ -7,6 +7,7 @@ import {
LinearScale, LinearScale,
TimeScale, TimeScale,
type ChartOptions, type ChartOptions,
type ChartData,
} from 'chart.js'; } from 'chart.js';
import {GET} from '../modules/fetch.ts'; import {GET} from '../modules/fetch.ts';
import {Bar} from 'vue-chartjs'; import {Bar} from 'vue-chartjs';
@ -15,6 +16,7 @@ import {
firstStartDateAfterDate, firstStartDateAfterDate,
fillEmptyStartDaysWithZeroes, fillEmptyStartDaysWithZeroes,
type DayData, type DayData,
type DayDataObject,
} from '../utils/time.ts'; } from '../utils/time.ts';
import {chartJsColors} from '../utils/color.ts'; import {chartJsColors} from '../utils/color.ts';
import {sleep} from '../utils.ts'; import {sleep} from '../utils.ts';
@ -61,11 +63,11 @@ async function fetchGraphData() {
} }
} while (response.status === 202); } while (response.status === 202);
if (response.ok) { if (response.ok) {
const data = await response.json(); const dayDataObj: DayDataObject = await response.json();
const start = Object.values(data)[0].week; const start = Object.values(dayDataObj)[0].week;
const end = firstStartDateAfterDate(new Date()); const end = firstStartDateAfterDate(new Date());
const startDays = startDaysBetween(start, end); const startDays = startDaysBetween(start, end);
data.value = fillEmptyStartDaysWithZeroes(startDays, data).slice(-52); data.value = fillEmptyStartDaysWithZeroes(startDays, dayDataObj).slice(-52);
errorText.value = ''; errorText.value = '';
} else { } else {
errorText.value = response.statusText; errorText.value = response.statusText;
@ -77,10 +79,11 @@ async function fetchGraphData() {
} }
} }
function toGraphData(data) { function toGraphData(data: DayData[]): ChartData<'bar'> {
return { return {
datasets: [ 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})), data: data.map((i) => ({x: i.week, y: i.commits})),
label: 'Commits', label: 'Commits',
backgroundColor: chartJsColors['commits'], backgroundColor: chartJsColors['commits'],
@ -91,10 +94,9 @@ function toGraphData(data) {
}; };
} }
const options = { const options: ChartOptions<'bar'> = {
responsive: true, responsive: true,
maintainAspectRatio: false, maintainAspectRatio: false,
animation: true,
scales: { scales: {
x: { x: {
type: 'time', type: 'time',

View File

@ -49,7 +49,11 @@ export type DayData = {
commits: number, commits: number,
} }
export function fillEmptyStartDaysWithZeroes(startDays: number[], data: DayData[]): DayData[] { export type DayDataObject = {
[timestamp: string]: DayData,
}
export function fillEmptyStartDaysWithZeroes(startDays: number[], data: DayDataObject): DayData[] {
const result = {}; const result = {};
for (const startDay of startDays) { for (const startDay of startDays) {