mirror of
https://github.com/go-gitea/gitea.git
synced 2026-09-02 19:13:25 +09:00
refactor: improve types in the frontend, misc fixes (#39142)
This commit is contained in:
@@ -25,6 +25,17 @@ function isLogElementInViewport(el: Element, {extraViewPortHeight}={extraViewPor
|
||||
return 0 <= rect.bottom && rect.bottom <= window.innerHeight + extraViewPortHeight;
|
||||
}
|
||||
|
||||
export type ActionRunJobViewLocale = {
|
||||
status: Record<ActionsStatus, string>,
|
||||
showTimeStamps: string,
|
||||
showLogSeconds: string,
|
||||
showFullScreen: string,
|
||||
logsAlwaysAutoScroll: string,
|
||||
logsAlwaysExpandRunning: string,
|
||||
downloadLogs: string,
|
||||
copyOutput: string,
|
||||
};
|
||||
|
||||
type Step = {
|
||||
summary: string,
|
||||
duration: string,
|
||||
@@ -84,7 +95,7 @@ const props = defineProps<{
|
||||
store: ActionRunViewStore,
|
||||
jobId: number;
|
||||
actionsViewUrl: string;
|
||||
locale: Record<string, any>;
|
||||
locale: ActionRunJobViewLocale;
|
||||
}>();
|
||||
const store = props.store;
|
||||
const {currentRun: run} = toRefs(store.viewData);
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import WorkflowGraph from './WorkflowGraph.vue';
|
||||
import WorkflowGraph, {type WorkflowGraphLocale} from './WorkflowGraph.vue';
|
||||
import type {ActionRunViewStore} from './ActionRunView.ts';
|
||||
import {computed, onBeforeUnmount, onMounted, toRefs} from 'vue';
|
||||
import {trString} from '../modules/i18n.ts';
|
||||
import type {ActionsStatus} from '../modules/gitea-actions.ts';
|
||||
|
||||
defineOptions({
|
||||
name: 'ActionRunSummaryView',
|
||||
});
|
||||
|
||||
export type ActionRunSummaryViewLocale = WorkflowGraphLocale & {
|
||||
status: Record<ActionsStatus, string>,
|
||||
statusLabel: string,
|
||||
totalDuration: string,
|
||||
artifactsTitle: string,
|
||||
triggeredVia: string,
|
||||
rerunTriggered: string,
|
||||
};
|
||||
|
||||
const props = defineProps<{
|
||||
store: ActionRunViewStore;
|
||||
locale: Record<string, any>;
|
||||
locale: ActionRunSummaryViewLocale;
|
||||
artifactCount: number;
|
||||
}>();
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import {getIssueColorClass, getIssueIcon} from '../features/issue.ts';
|
||||
import {computed} from 'vue';
|
||||
import type {Issue} from '../types.ts';
|
||||
|
||||
const {appSubUrl} = window.config;
|
||||
|
||||
const props = defineProps<{
|
||||
issue?: Issue | null,
|
||||
renderedLabels?: string,
|
||||
@@ -26,7 +28,7 @@ const body = computed(() => {
|
||||
<div class="tw-p-4">
|
||||
<div v-if="issue" class="tw-flex tw-flex-col tw-gap-2">
|
||||
<div class="tw-text-12">
|
||||
<a :href="issue.repository.html_url" class="muted">{{ issue.repository.full_name }}</a>
|
||||
<a :href="`${appSubUrl}/${issue.repository.full_name}`" class="muted">{{ issue.repository.full_name }}</a>
|
||||
on {{ createdAt }}
|
||||
</div>
|
||||
<div class="flex-text-block">
|
||||
|
||||
@@ -24,6 +24,15 @@ type DashboardRepo = {
|
||||
|
||||
type CommitStatus = 'pending' | 'success' | 'error' | 'failure' | 'warning' | 'skipped';
|
||||
|
||||
type WebSearchRepo = {
|
||||
repository: DashboardRepo,
|
||||
latest_commit_status: {
|
||||
State: CommitStatus,
|
||||
TargetURL: string,
|
||||
} | null,
|
||||
locale_latest_commit_status: string,
|
||||
};
|
||||
|
||||
type CommitStatusMap = {
|
||||
[status in CommitStatus]: {
|
||||
name: SvgName,
|
||||
@@ -263,7 +272,7 @@ async function searchRepos() {
|
||||
const searchedURL = searchURL.value;
|
||||
const searchedQuery = searchQuery.value;
|
||||
|
||||
let response: Response, json: any;
|
||||
let response: Response, json: {data: WebSearchRepo[]};
|
||||
try {
|
||||
const firstLoad = reposTotalCount.value === null;
|
||||
// independent of the search, so both requests go out together
|
||||
@@ -293,7 +302,7 @@ async function searchRepos() {
|
||||
}
|
||||
|
||||
if (searchedURL === searchURL.value) {
|
||||
repos.value = json.data.map((webSearchRepo: any) => {
|
||||
repos.value = json.data.map((webSearchRepo) => {
|
||||
return {
|
||||
...webSearchRepo.repository,
|
||||
latest_commit_status_state: webSearchRepo.latest_commit_status?.State, // if latest_commit_status is null, it means there is no commit status
|
||||
|
||||
@@ -3,27 +3,55 @@ import {computed, onMounted, onUnmounted, shallowRef, watch} from 'vue';
|
||||
import SvgIcon from './SvgIcon.vue';
|
||||
import {toggleElem} from '../utils/dom.ts';
|
||||
|
||||
type MergeStyle = {
|
||||
name: string,
|
||||
allowed: boolean,
|
||||
textDoMerge: string,
|
||||
mergeTitleFieldText?: string,
|
||||
mergeMessageFieldText?: string,
|
||||
hideMergeMessageTexts?: boolean,
|
||||
hideAutoMerge: boolean,
|
||||
};
|
||||
|
||||
type MergeForm = {
|
||||
allOverridableChecksOk: boolean,
|
||||
baseLink: string,
|
||||
canMergeNow: boolean,
|
||||
defaultDeleteBranchAfterMerge: boolean,
|
||||
defaultMergeMessage: string,
|
||||
defaultMergeStyle: string,
|
||||
emptyCommit: boolean,
|
||||
hasPendingPullRequestMerge: boolean,
|
||||
hasPendingPullRequestMergeTip: string,
|
||||
isPullBranchDeletable: boolean,
|
||||
mergeMessageFieldPlaceHolder: string,
|
||||
mergeStyles: MergeStyle[],
|
||||
pullHeadCommitID: string,
|
||||
textAutoMergeButtonWhenSucceed: string,
|
||||
textAutoMergeCancelSchedule: string,
|
||||
textAutoMergeWhenSucceed: string,
|
||||
textCancel: string,
|
||||
textClearMergeMessage: string,
|
||||
textClearMergeMessageHint: string,
|
||||
textDeleteBranch: string,
|
||||
textMergeCommitId: string,
|
||||
};
|
||||
|
||||
const props = defineProps<{
|
||||
mergeFormProps: any, // TODO: this is a huge object, need to be refactored in the future
|
||||
mergeFormProps: MergeForm,
|
||||
}>();
|
||||
|
||||
const mergeStyleManuallyMerged = 'manually-merged';
|
||||
|
||||
const mergeForm = props.mergeFormProps;
|
||||
|
||||
const mergeTitleFieldValue = shallowRef('');
|
||||
const mergeMessageFieldValue = shallowRef('');
|
||||
const mergeTitleFieldValue = shallowRef<string | undefined>('');
|
||||
const mergeMessageFieldValue = shallowRef<string | undefined>('');
|
||||
const deleteBranchAfterMerge = shallowRef(false);
|
||||
const autoMergeWhenSucceed = shallowRef(false);
|
||||
|
||||
const mergeStyle = shallowRef('');
|
||||
const mergeStyleDetail = shallowRef({
|
||||
hideMergeMessageTexts: false,
|
||||
textDoMerge: '',
|
||||
mergeTitleFieldText: '',
|
||||
mergeMessageFieldText: '',
|
||||
hideAutoMerge: false,
|
||||
});
|
||||
const mergeStyleDetail = shallowRef<MergeStyle>({name: '', allowed: false, textDoMerge: '', hideAutoMerge: false});
|
||||
|
||||
const mergeStyleAllowedCount = shallowRef(0);
|
||||
|
||||
@@ -48,18 +76,18 @@ const forceMerge = computed(() => {
|
||||
});
|
||||
|
||||
watch(mergeStyle, (val) => {
|
||||
mergeStyleDetail.value = mergeForm.mergeStyles.find((e: any) => e.name === val);
|
||||
mergeStyleDetail.value = mergeForm.mergeStyles.find((e) => e.name === val)!;
|
||||
for (const elem of document.querySelectorAll('[data-pull-merge-style]')) {
|
||||
toggleElem(elem, elem.getAttribute('data-pull-merge-style') === val);
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
mergeStyleAllowedCount.value = mergeForm.mergeStyles.reduce((v: any, msd: any) => v + (msd.allowed ? 1 : 0), 0);
|
||||
mergeStyleAllowedCount.value = mergeForm.mergeStyles.reduce((v, msd) => v + (msd.allowed ? 1 : 0), 0);
|
||||
|
||||
let mergeStyle = mergeForm.mergeStyles.find((e: any) => e.allowed && e.name === mergeForm.defaultMergeStyle)?.name;
|
||||
if (!mergeStyle) mergeStyle = mergeForm.mergeStyles.find((e: any) => e.allowed)?.name;
|
||||
switchMergeStyle(mergeStyle, !mergeForm.canMergeNow);
|
||||
let mergeStyle = mergeForm.mergeStyles.find((e) => e.allowed && e.name === mergeForm.defaultMergeStyle)?.name;
|
||||
if (!mergeStyle) mergeStyle = mergeForm.mergeStyles.find((e) => e.allowed)?.name;
|
||||
if (mergeStyle) switchMergeStyle(mergeStyle, !mergeForm.canMergeNow);
|
||||
|
||||
document.addEventListener('mouseup', hideMergeStyleMenu);
|
||||
});
|
||||
|
||||
@@ -4,8 +4,8 @@ import ActionStatusIcon from './ActionStatusIcon.vue';
|
||||
import {computed, onBeforeUnmount, ref, toRefs, watch} from 'vue';
|
||||
import {resetActionFavicon, syncActionRunFavicon} from '../modules/favicon-status.ts';
|
||||
import {POST, DELETE} from '../modules/fetch.ts';
|
||||
import ActionRunSummaryView from './ActionRunSummaryView.vue';
|
||||
import ActionRunJobView from './ActionRunJobView.vue';
|
||||
import ActionRunSummaryView, {type ActionRunSummaryViewLocale} from './ActionRunSummaryView.vue';
|
||||
import ActionRunJobView, {type ActionRunJobViewLocale} from './ActionRunJobView.vue';
|
||||
import type {ActionsJob, ActionsRunAttempt} from '../modules/gitea-actions.ts';
|
||||
import {buildJobsByParentJobID, createActionRunViewStore} from './ActionRunView.ts';
|
||||
import {buildArtifactTooltipHtml} from './ActionRunArtifacts.ts';
|
||||
@@ -15,10 +15,35 @@ defineOptions({
|
||||
name: 'RepoActionView',
|
||||
});
|
||||
|
||||
type RepoActionViewLocale = ActionRunSummaryViewLocale & ActionRunJobViewLocale & {
|
||||
approve: string,
|
||||
cancel: string,
|
||||
rerun: string,
|
||||
rerun_all: string,
|
||||
rerun_failed: string,
|
||||
latest: string,
|
||||
latestAttempt: string,
|
||||
attempt: string,
|
||||
summary: string,
|
||||
allJobs: string,
|
||||
jobSummaries: string,
|
||||
expandCallerJobs: string,
|
||||
collapseCallerJobs: string,
|
||||
backToPullRequest: string,
|
||||
backToWorkflow: string,
|
||||
artifactExpired: string,
|
||||
artifactExpiresAt: string,
|
||||
artifactExpiredAt: string,
|
||||
confirmDeleteArtifact: string,
|
||||
workflowFile: string,
|
||||
workflowFileNoPermission: string,
|
||||
runDetails: string,
|
||||
};
|
||||
|
||||
const props = defineProps<{
|
||||
jobId: number;
|
||||
actionsViewUrl: string;
|
||||
locale: Record<string, any>;
|
||||
locale: RepoActionViewLocale;
|
||||
}>();
|
||||
|
||||
const locale = props.locale;
|
||||
|
||||
@@ -15,15 +15,7 @@ const colors = shallowRef({
|
||||
textAltColor: 'white',
|
||||
});
|
||||
|
||||
type ActivityAuthorData = {
|
||||
avatar_link: string;
|
||||
commits: number;
|
||||
home_link: string;
|
||||
login: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
const activityTopAuthors: Array<ActivityAuthorData> = window.config.pageData.repoActivityTopAuthors || [];
|
||||
const activityTopAuthors = window.config.pageData.repoActivityTopAuthors || [];
|
||||
|
||||
const graphWidth = activityTopAuthors.length * barSlotWidth;
|
||||
const maxCommits = Math.max(...activityTopAuthors.map((author) => author.commits));
|
||||
|
||||
@@ -20,6 +20,8 @@ import {
|
||||
startDaysBetween,
|
||||
firstStartDateAfterDate,
|
||||
fillEmptyStartDaysWithZeroes,
|
||||
type DayData,
|
||||
type DayDataObject,
|
||||
} from '../utils/time.ts';
|
||||
import {errorMessage} from '../modules/errors.ts';
|
||||
import {sleep} from '../utils.ts';
|
||||
@@ -67,12 +69,22 @@ function roundUpMax(maxValue: number) {
|
||||
return Math.ceil(coefficient) * 10 ** exp;
|
||||
}
|
||||
|
||||
type ContributorsData = {
|
||||
total: {
|
||||
weeks: Record<string, any>,
|
||||
},
|
||||
[other: string]: Record<string, Record<string, any>>,
|
||||
}
|
||||
type ContributorInfo = {
|
||||
name: string,
|
||||
avatar_link: string,
|
||||
home_link: string,
|
||||
};
|
||||
|
||||
type ContributorStats = ContributorInfo & {weeks: DayData[]};
|
||||
|
||||
type Contributor = ContributorStats & {
|
||||
email: string,
|
||||
total_commits: number,
|
||||
total_additions: number,
|
||||
total_deletions: number,
|
||||
max_contribution_type: number,
|
||||
};
|
||||
type ContributorsData = Record<string, ContributorInfo & {weeks: DayDataObject}>;
|
||||
|
||||
const props = defineProps<{
|
||||
locale: {
|
||||
@@ -89,10 +101,10 @@ const props = defineProps<{
|
||||
|
||||
const isLoading = shallowRef(false);
|
||||
const errorText = shallowRef('');
|
||||
const totalStats = shallowRef<Record<string, any>>({});
|
||||
const sortedContributors = shallowRef<Array<Record<string, any>>>([]);
|
||||
const totalStats = shallowRef<DayData[]>([]);
|
||||
const sortedContributors = shallowRef<Contributor[]>([]);
|
||||
const type = shallowRef<ContributionType>('commits');
|
||||
let contributorsStats: Record<string, any> = {};
|
||||
let contributorsStats: Record<string, ContributorStats> = {};
|
||||
// plain values, so the main chart options do not follow the zoomed range
|
||||
let xAxisStart: number | null = null;
|
||||
let xAxisEnd: number | null = null;
|
||||
@@ -113,7 +125,7 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
function sortContributors() {
|
||||
const criteria = `total_${type.value}`;
|
||||
const criteria = `total_${type.value}` as const;
|
||||
sortedContributors.value = filterContributorWeeksByDateRange()
|
||||
.filter((contributor) => contributor[criteria] !== 0)
|
||||
.sort((a, b) => b[criteria] - a[criteria])
|
||||
@@ -142,23 +154,22 @@ async function fetchGraphData() {
|
||||
}
|
||||
} while (response.status === 202);
|
||||
if (response.ok) {
|
||||
const data = await response.json() as ContributorsData;
|
||||
const data: ContributorsData = await response.json();
|
||||
const {total, ...other} = data;
|
||||
// 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());
|
||||
const totalWeeks = Object.fromEntries(Object.entries(total.weeks).sort());
|
||||
|
||||
const weekValues = Object.values(total.weeks);
|
||||
const weekValues = Object.values(totalWeeks);
|
||||
xAxisStart = weekValues[0].week;
|
||||
xAxisEnd = firstStartDateAfterDate(new Date());
|
||||
const startDays = startDaysBetween(xAxisStart, xAxisEnd);
|
||||
total.weeks = fillEmptyStartDaysWithZeroes(startDays, total.weeks);
|
||||
xAxisMin.value = xAxisStart;
|
||||
xAxisMax.value = xAxisEnd;
|
||||
contributorsStats = Object.fromEntries(Object.entries(other).map(([email, user]) => {
|
||||
return [email, {...user, weeks: fillEmptyStartDaysWithZeroes(startDays, user.weeks)}];
|
||||
}));
|
||||
sortContributors();
|
||||
totalStats.value = total;
|
||||
totalStats.value = fillEmptyStartDaysWithZeroes(startDays, totalWeeks);
|
||||
errorText.value = '';
|
||||
} else {
|
||||
errorText.value = response.statusText;
|
||||
@@ -171,22 +182,22 @@ async function fetchGraphData() {
|
||||
}
|
||||
|
||||
function filterContributorWeeksByDateRange() {
|
||||
const filteredData: Array<Record<string, any>> = [];
|
||||
const filteredData: Contributor[] = [];
|
||||
const minTime = xAxisMin.value! - oneWeek;
|
||||
const maxTime = xAxisMax.value! + oneWeek;
|
||||
const contributionType = type.value;
|
||||
for (const [key, user] of Object.entries(contributorsStats)) {
|
||||
user.total_commits = 0;
|
||||
user.total_additions = 0;
|
||||
user.total_deletions = 0;
|
||||
user.max_contribution_type = 0;
|
||||
const filteredWeeks = user.weeks.filter((week: Record<string, number>) => {
|
||||
let totalCommits = 0;
|
||||
let totalAdditions = 0;
|
||||
let totalDeletions = 0;
|
||||
let maxContributionType = 0;
|
||||
const filteredWeeks = user.weeks.filter((week) => {
|
||||
if (week.week >= minTime && week.week <= maxTime) {
|
||||
user.total_commits += week.commits;
|
||||
user.total_additions += week.additions;
|
||||
user.total_deletions += week.deletions;
|
||||
if (week[contributionType] > user.max_contribution_type) {
|
||||
user.max_contribution_type = week[contributionType];
|
||||
totalCommits += week.commits;
|
||||
totalAdditions += week.additions;
|
||||
totalDeletions += week.deletions;
|
||||
if (week[contributionType] > maxContributionType) {
|
||||
maxContributionType = week[contributionType];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -194,24 +205,32 @@ function filterContributorWeeksByDateRange() {
|
||||
});
|
||||
// this line is required. See https://github.com/sahinakkaya/gitea/pull/3#discussion_r1396495722
|
||||
// for details.
|
||||
user.max_contribution_type += 1;
|
||||
maxContributionType += 1;
|
||||
|
||||
filteredData.push({...user, weeks: filteredWeeks, email: key});
|
||||
filteredData.push({
|
||||
...user,
|
||||
weeks: filteredWeeks,
|
||||
total_commits: totalCommits,
|
||||
total_additions: totalAdditions,
|
||||
total_deletions: totalDeletions,
|
||||
max_contribution_type: maxContributionType,
|
||||
email: key,
|
||||
});
|
||||
}
|
||||
|
||||
return filteredData;
|
||||
}
|
||||
|
||||
const maxMainGraph = computed(() => {
|
||||
return roundUpMax(Math.max(...totalStats.value.weeks.map((o: Record<string, any>) => o[type.value])));
|
||||
return roundUpMax(Math.max(...totalStats.value.map((o) => o[type.value])));
|
||||
});
|
||||
|
||||
// one shared maximum, otherwise the contributor graphs cannot be compared
|
||||
const maxContributorGraph = computed(() => {
|
||||
return roundUpMax(Math.max(...sortedContributors.value.map((c: Record<string, any>) => c.max_contribution_type)));
|
||||
return roundUpMax(Math.max(...sortedContributors.value.map((c) => c.max_contribution_type)));
|
||||
});
|
||||
|
||||
function toGraphData(data: Array<Record<string, any>>): ChartData<'line'> {
|
||||
function toGraphData(data: DayData[]): ChartData<'line'> {
|
||||
const contributionType = type.value;
|
||||
return {
|
||||
datasets: [
|
||||
@@ -319,7 +338,7 @@ function getOptions(chartType: ChartType): LineOptions {
|
||||
}
|
||||
|
||||
const mainChart = computed(() => ({
|
||||
graphData: toGraphData(totalStats.value.weeks),
|
||||
graphData: toGraphData(totalStats.value),
|
||||
chartOptions: getOptions('main'),
|
||||
}));
|
||||
|
||||
@@ -390,7 +409,7 @@ const contributorCharts = computed(() => sortedContributors.value.map((contribut
|
||||
</div>
|
||||
</div>
|
||||
<ChartCanvas
|
||||
v-if="Object.keys(totalStats).length !== 0"
|
||||
v-if="totalStats.length"
|
||||
type="line" :data="mainChart.graphData" :options="mainChart.chartOptions"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -18,6 +18,18 @@ import {
|
||||
type RoutedEdge,
|
||||
} from './WorkflowGraph.utils.ts';
|
||||
|
||||
export type WorkflowGraphLocale = {
|
||||
graphJobsCount1: string,
|
||||
graphJobsCountN: string,
|
||||
graphDependenciesCount1: string,
|
||||
graphDependenciesCountN: string,
|
||||
graphSuccessRate: string,
|
||||
graphZoomIn: string,
|
||||
graphZoomMax: string,
|
||||
graphZoomOut: string,
|
||||
graphResetView: string,
|
||||
};
|
||||
|
||||
interface StoredState {
|
||||
scale: number;
|
||||
translateX: number;
|
||||
@@ -32,7 +44,7 @@ const props = defineProps<{
|
||||
workflowId: string;
|
||||
workflowLink?: string;
|
||||
triggerEvent?: string;
|
||||
locale: Record<string, string>;
|
||||
locale: WorkflowGraphLocale;
|
||||
}>();
|
||||
|
||||
const settingKeyStates = 'actions-graph-states';
|
||||
|
||||
Reference in New Issue
Block a user