mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2025-01-16 12:35:26 +00:00
Merge branch 'origin/main' into 'next-release/main'
This commit is contained in:
commit
e8328512ab
9 changed files with 69 additions and 117 deletions
|
@ -48,6 +48,7 @@
|
||||||
"dind",
|
"dind",
|
||||||
"Dockle",
|
"Dockle",
|
||||||
"doublestar",
|
"doublestar",
|
||||||
|
"emojify",
|
||||||
"envsubst",
|
"envsubst",
|
||||||
"errgroup",
|
"errgroup",
|
||||||
"estree",
|
"estree",
|
||||||
|
|
|
@ -121,8 +121,7 @@ import IconButton from '~/components/atomic/IconButton.vue';
|
||||||
import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vue';
|
import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vue';
|
||||||
import useApiClient from '~/compositions/useApiClient';
|
import useApiClient from '~/compositions/useApiClient';
|
||||||
import useNotifications from '~/compositions/useNotifications';
|
import useNotifications from '~/compositions/useNotifications';
|
||||||
import type { Pipeline, Repo, RepoPermissions } from '~/lib/api/types';
|
import type { Pipeline, PipelineStep, PipelineWorkflow, Repo, RepoPermissions } from '~/lib/api/types';
|
||||||
import { findStep, isStepFinished, isStepRunning } from '~/utils/helpers';
|
|
||||||
|
|
||||||
interface LogLine {
|
interface LogLine {
|
||||||
index: number;
|
index: number;
|
||||||
|
@ -303,12 +302,12 @@ async function loadLogs() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isStepFinished(step.value)) {
|
if (step.value.state !== 'running' && step.value.state !== 'pending') {
|
||||||
loadedStepSlug.value = stepSlug.value;
|
loadedStepSlug.value = stepSlug.value;
|
||||||
const logs = await apiClient.getLogs(repo.value.id, pipeline.value.number, step.value.id);
|
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 }));
|
logs?.forEach((line) => writeLog({ index: line.line, text: line.data, time: line.time }));
|
||||||
flushLogs(false);
|
flushLogs(false);
|
||||||
} else if (step.value.state === 'pending' || isStepRunning(step.value)) {
|
} else {
|
||||||
loadedStepSlug.value = stepSlug.value;
|
loadedStepSlug.value = stepSlug.value;
|
||||||
stream.value = apiClient.streamLogs(repo.value.id, pipeline.value.number, step.value.id, (line) => {
|
stream.value = apiClient.streamLogs(repo.value.id, pipeline.value.number, step.value.id, (line) => {
|
||||||
writeLog({ index: line.line, text: line.data, time: line.time });
|
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 () => {
|
onMounted(async () => {
|
||||||
await loadLogs();
|
await loadLogs();
|
||||||
});
|
});
|
||||||
|
|
|
@ -87,7 +87,7 @@ function durationAsNumber(durationMs: number): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useDate() {
|
export function useDate() {
|
||||||
async function setDayjsLocale(locale: string) {
|
async function setDateLocale(locale: string) {
|
||||||
currentLocale = locale;
|
currentLocale = locale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ export function useDate() {
|
||||||
toLocaleString,
|
toLocaleString,
|
||||||
timeAgo,
|
timeAgo,
|
||||||
prettyDuration,
|
prettyDuration,
|
||||||
setDayjsLocale,
|
setDateLocale,
|
||||||
durationAsNumber,
|
durationAsNumber,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
|
import { useStorage } from '@vueuse/core';
|
||||||
import { nextTick } from 'vue';
|
import { nextTick } from 'vue';
|
||||||
import { createI18n } from 'vue-i18n';
|
import { createI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import { getUserLanguage } from '~/utils/locale';
|
|
||||||
|
|
||||||
import { useDate } from './useDate';
|
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 userLanguage = getUserLanguage();
|
||||||
const fallbackLocale = 'en';
|
const fallbackLocale = 'en';
|
||||||
export const i18n = createI18n({
|
export const i18n = createI18n({
|
||||||
|
@ -28,9 +34,9 @@ export const setI18nLanguage = async (lang: string): Promise<void> => {
|
||||||
await loadLocaleMessages(lang);
|
await loadLocaleMessages(lang);
|
||||||
}
|
}
|
||||||
i18n.global.locale.value = lang;
|
i18n.global.locale.value = lang;
|
||||||
await setDayjsLocale(lang);
|
await setDateLocale(lang);
|
||||||
};
|
};
|
||||||
|
|
||||||
loadLocaleMessages(fallbackLocale).catch(console.error);
|
loadLocaleMessages(fallbackLocale).catch(console.error);
|
||||||
loadLocaleMessages(userLanguage).catch(console.error);
|
loadLocaleMessages(userLanguage).catch(console.error);
|
||||||
setDayjsLocale(userLanguage).catch(console.error);
|
setDateLocale(userLanguage).catch(console.error);
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
|
import { emojify } from 'node-emoji';
|
||||||
import { computed, type Ref } from 'vue';
|
import { computed, type Ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import { useDate } from '~/compositions/useDate';
|
import { useDate } from '~/compositions/useDate';
|
||||||
import { useElapsedTime } from '~/compositions/useElapsedTime';
|
import { useElapsedTime } from '~/compositions/useElapsedTime';
|
||||||
import type { Pipeline } from '~/lib/api/types';
|
import type { Pipeline } from '~/lib/api/types';
|
||||||
import { convertEmojis } from '~/utils/emoji';
|
|
||||||
|
|
||||||
const { toLocaleString, timeAgo, prettyDuration } = useDate();
|
const { toLocaleString, timeAgo, prettyDuration } = useDate();
|
||||||
|
|
||||||
|
@ -74,10 +74,10 @@ export default (pipeline: Ref<Pipeline | undefined>) => {
|
||||||
return prettyDuration(durationElapsed.value);
|
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 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 prTitle = computed(() => prTitleWithDescription.value.split('\n')[0]);
|
||||||
|
|
||||||
const prettyRef = computed(() => {
|
const prettyRef = computed(() => {
|
||||||
|
|
|
@ -4,7 +4,29 @@ import { computed, reactive, ref, type Ref } from 'vue';
|
||||||
import useApiClient from '~/compositions/useApiClient';
|
import useApiClient from '~/compositions/useApiClient';
|
||||||
import type { Pipeline, PipelineFeed, PipelineWorkflow } from '~/lib/api/types';
|
import type { Pipeline, PipelineFeed, PipelineWorkflow } from '~/lib/api/types';
|
||||||
import { useRepoStore } from '~/store/repos';
|
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', () => {
|
export const usePipelineStore = defineStore('pipelines', () => {
|
||||||
const apiClient = useApiClient();
|
const apiClient = useApiClient();
|
||||||
|
@ -87,7 +109,9 @@ export const usePipelineStore = defineStore('pipelines', () => {
|
||||||
.filter((pipeline) => repoStore.ownedRepoIds.includes(pipeline.repo_id)),
|
.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() {
|
async function loadPipelineFeed() {
|
||||||
await repoStore.loadRepos();
|
await repoStore.loadRepos();
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
// cSpell:ignore emojify
|
|
||||||
import { emojify } from 'node-emoji';
|
|
||||||
|
|
||||||
export function convertEmojis(input: string): string {
|
|
||||||
return emojify(input);
|
|
||||||
}
|
|
|
@ -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}`;
|
|
||||||
}
|
|
|
@ -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)}...`;
|
|
||||||
}
|
|
Loading…
Reference in a new issue