Merge branch 'origin/main' into 'next-release/main'

This commit is contained in:
oauth 2025-01-02 13:31:24 +00:00
commit e8328512ab
9 changed files with 69 additions and 117 deletions

View file

@ -48,6 +48,7 @@
"dind",
"Dockle",
"doublestar",
"emojify",
"envsubst",
"errgroup",
"estree",

View file

@ -121,8 +121,7 @@ import IconButton from '~/components/atomic/IconButton.vue';
import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vue';
import useApiClient from '~/compositions/useApiClient';
import useNotifications from '~/compositions/useNotifications';
import type { Pipeline, Repo, RepoPermissions } from '~/lib/api/types';
import { findStep, isStepFinished, isStepRunning } from '~/utils/helpers';
import type { Pipeline, PipelineStep, PipelineWorkflow, Repo, RepoPermissions } from '~/lib/api/types';
interface LogLine {
index: number;
@ -303,12 +302,12 @@ async function loadLogs() {
return;
}
if (isStepFinished(step.value)) {
if (step.value.state !== 'running' && step.value.state !== 'pending') {
loadedStepSlug.value = stepSlug.value;
const logs = await apiClient.getLogs(repo.value.id, pipeline.value.number, step.value.id);
logs?.forEach((line) => writeLog({ index: line.line, text: line.data, time: line.time }));
flushLogs(false);
} else if (step.value.state === 'pending' || isStepRunning(step.value)) {
} else {
loadedStepSlug.value = stepSlug.value;
stream.value = apiClient.streamLogs(repo.value.id, pipeline.value.number, step.value.id, (line) => {
writeLog({ index: line.line, text: line.data, time: line.time });
@ -336,6 +335,29 @@ async function deleteLogs() {
}
}
function findStep(workflows: PipelineWorkflow[], pid: number): PipelineStep | undefined {
return workflows.reduce(
(prev, workflow) => {
const result = workflow.children.reduce(
(prevChild, step) => {
if (step.pid === pid) {
return step;
}
return prevChild;
},
undefined as PipelineStep | undefined,
);
if (result) {
return result;
}
return prev;
},
undefined as PipelineStep | undefined,
);
}
onMounted(async () => {
await loadLogs();
});

View file

@ -87,7 +87,7 @@ function durationAsNumber(durationMs: number): string {
}
export function useDate() {
async function setDayjsLocale(locale: string) {
async function setDateLocale(locale: string) {
currentLocale = locale;
}
@ -95,7 +95,7 @@ export function useDate() {
toLocaleString,
timeAgo,
prettyDuration,
setDayjsLocale,
setDateLocale,
durationAsNumber,
};
}

View file

@ -1,11 +1,17 @@
import { useStorage } from '@vueuse/core';
import { nextTick } from 'vue';
import { createI18n } from 'vue-i18n';
import { getUserLanguage } from '~/utils/locale';
import { useDate } from './useDate';
const { setDayjsLocale } = useDate();
export function getUserLanguage(): string {
const browserLocale = navigator.language.split('-')[0];
const selectedLocale = useStorage('woodpecker:locale', browserLocale).value;
return selectedLocale;
}
const { setDateLocale } = useDate();
const userLanguage = getUserLanguage();
const fallbackLocale = 'en';
export const i18n = createI18n({
@ -28,9 +34,9 @@ export const setI18nLanguage = async (lang: string): Promise<void> => {
await loadLocaleMessages(lang);
}
i18n.global.locale.value = lang;
await setDayjsLocale(lang);
await setDateLocale(lang);
};
loadLocaleMessages(fallbackLocale).catch(console.error);
loadLocaleMessages(userLanguage).catch(console.error);
setDayjsLocale(userLanguage).catch(console.error);
setDateLocale(userLanguage).catch(console.error);

View file

@ -1,10 +1,10 @@
import { emojify } from 'node-emoji';
import { computed, type Ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useDate } from '~/compositions/useDate';
import { useElapsedTime } from '~/compositions/useElapsedTime';
import type { Pipeline } from '~/lib/api/types';
import { convertEmojis } from '~/utils/emoji';
const { toLocaleString, timeAgo, prettyDuration } = useDate();
@ -74,10 +74,10 @@ export default (pipeline: Ref<Pipeline | undefined>) => {
return prettyDuration(durationElapsed.value);
});
const message = computed(() => convertEmojis(pipeline.value?.message ?? ''));
const message = computed(() => emojify(pipeline.value?.message ?? ''));
const shortMessage = computed(() => message.value.split('\n')[0]);
const prTitleWithDescription = computed(() => convertEmojis(pipeline.value?.title ?? ''));
const prTitleWithDescription = computed(() => emojify(pipeline.value?.title ?? ''));
const prTitle = computed(() => prTitleWithDescription.value.split('\n')[0]);
const prettyRef = computed(() => {

View file

@ -4,7 +4,29 @@ import { computed, reactive, ref, type Ref } from 'vue';
import useApiClient from '~/compositions/useApiClient';
import type { Pipeline, PipelineFeed, PipelineWorkflow } from '~/lib/api/types';
import { useRepoStore } from '~/store/repos';
import { comparePipelines, comparePipelinesWithStatus, isPipelineActive } from '~/utils/helpers';
/**
* Compare two pipelines by creation timestamp.
* @param {object} a - A pipeline.
* @param {object} b - A pipeline.
* @returns {number} 0 if created at the same time, < 0 if b was create before a, > 0 otherwise
*/
function comparePipelines(a: Pipeline, b: Pipeline): number {
return (b.created || -1) - (a.created || -1);
}
/**
* Compare two pipelines by the status.
* Giving pending, running, or started higher priority than other status
* @param {object} a - A pipeline.
* @param {object} b - A pipeline.
* @returns {number} 0 if status same priority, < 0 if b has higher priority, > 0 otherwise
*/
function comparePipelinesWithStatus(a: Pipeline, b: Pipeline): number {
const bPriority = ['pending', 'running', 'started'].includes(b.status) ? 1 : 0;
const aPriority = ['pending', 'running', 'started'].includes(a.status) ? 1 : 0;
return bPriority - aPriority || comparePipelines(a, b);
}
export const usePipelineStore = defineStore('pipelines', () => {
const apiClient = useApiClient();
@ -87,7 +109,9 @@ export const usePipelineStore = defineStore('pipelines', () => {
.filter((pipeline) => repoStore.ownedRepoIds.includes(pipeline.repo_id)),
);
const activePipelines = computed(() => pipelineFeed.value.filter(isPipelineActive));
const activePipelines = computed(() =>
pipelineFeed.value.filter((pipeline) => ['pending', 'running', 'started'].includes(pipeline.status)),
);
async function loadPipelineFeed() {
await repoStore.loadRepos();

View file

@ -1,6 +0,0 @@
// cSpell:ignore emojify
import { emojify } from 'node-emoji';
export function convertEmojis(input: string): string {
return emojify(input);
}

View file

@ -1,79 +0,0 @@
import type { Pipeline, PipelineStep, PipelineWorkflow, Repo } from '~/lib/api/types';
export function findStep(workflows: PipelineWorkflow[], pid: number): PipelineStep | undefined {
return workflows.reduce(
(prev, workflow) => {
const result = workflow.children.reduce(
(prevChild, step) => {
if (step.pid === pid) {
return step;
}
return prevChild;
},
undefined as PipelineStep | undefined,
);
if (result) {
return result;
}
return prev;
},
undefined as PipelineStep | undefined,
);
}
/**
* @param {object} step - The process object.
* @returns {boolean} true if the process is in a completed state
*/
export function isStepFinished(step: PipelineStep): boolean {
return step.state !== 'running' && step.state !== 'pending';
}
/**
* @param {object} step - The process object.
* @returns {boolean} true if the process is running
*/
export function isStepRunning(step: PipelineStep): boolean {
return step.state === 'running';
}
/**
* Compare two pipelines by creation timestamp.
* @param {object} a - A pipeline.
* @param {object} b - A pipeline.
* @returns {number} 0 if created at the same time, < 0 if b was create before a, > 0 otherwise
*/
export function comparePipelines(a: Pipeline, b: Pipeline): number {
return (b.created || -1) - (a.created || -1);
}
/**
* Compare two pipelines by the status.
* Giving pending, running, or started higher priority than other status
* @param {object} a - A pipeline.
* @param {object} b - A pipeline.
* @returns {number} 0 if status same priority, < 0 if b has higher priority, > 0 otherwise
*/
export function comparePipelinesWithStatus(a: Pipeline, b: Pipeline): number {
const bPriority = ['pending', 'running', 'started'].includes(b.status) ? 1 : 0;
const aPriority = ['pending', 'running', 'started'].includes(a.status) ? 1 : 0;
return bPriority - aPriority || comparePipelines(a, b);
}
export function isPipelineActive(pipeline: Pipeline): boolean {
return ['pending', 'running', 'started'].includes(pipeline.status);
}
export function repoSlug(ownerOrRepo: string | Repo, name?: string): string {
if (typeof ownerOrRepo === 'string') {
if (name === undefined) {
throw new Error('Please provide a name as well');
}
return `${ownerOrRepo}/${name}`;
}
return `${ownerOrRepo.owner}/${ownerOrRepo.name}`;
}

View file

@ -1,16 +0,0 @@
import { useStorage } from '@vueuse/core';
export function getUserLanguage(): string {
const browserLocale = navigator.language.split('-')[0];
const selectedLocale = useStorage('woodpecker:locale', browserLocale).value;
return selectedLocale;
}
export function truncate(text: string, length: number): string {
if (text.length <= length) {
return text;
}
return `${text.slice(0, length)}...`;
}