diff --git a/.cspell.json b/.cspell.json index 419c4e0db..03d9921d5 100644 --- a/.cspell.json +++ b/.cspell.json @@ -48,6 +48,7 @@ "dind", "Dockle", "doublestar", + "emojify", "envsubst", "errgroup", "estree", diff --git a/web/src/components/repo/pipeline/PipelineLog.vue b/web/src/components/repo/pipeline/PipelineLog.vue index 5d0d1d01a..10f72c83c 100644 --- a/web/src/components/repo/pipeline/PipelineLog.vue +++ b/web/src/components/repo/pipeline/PipelineLog.vue @@ -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(); }); diff --git a/web/src/compositions/useDate.ts b/web/src/compositions/useDate.ts index 0c1a75821..9b109c00c 100644 --- a/web/src/compositions/useDate.ts +++ b/web/src/compositions/useDate.ts @@ -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, }; } diff --git a/web/src/compositions/useI18n.ts b/web/src/compositions/useI18n.ts index cf7042cc8..3301b23cd 100644 --- a/web/src/compositions/useI18n.ts +++ b/web/src/compositions/useI18n.ts @@ -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 => { 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); diff --git a/web/src/compositions/usePipeline.ts b/web/src/compositions/usePipeline.ts index b4c8a6804..ec288657b 100644 --- a/web/src/compositions/usePipeline.ts +++ b/web/src/compositions/usePipeline.ts @@ -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) => { 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(() => { diff --git a/web/src/store/pipelines.ts b/web/src/store/pipelines.ts index 22a80500b..6507453b9 100644 --- a/web/src/store/pipelines.ts +++ b/web/src/store/pipelines.ts @@ -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(); diff --git a/web/src/utils/emoji.ts b/web/src/utils/emoji.ts deleted file mode 100644 index a507308b5..000000000 --- a/web/src/utils/emoji.ts +++ /dev/null @@ -1,6 +0,0 @@ -// cSpell:ignore emojify -import { emojify } from 'node-emoji'; - -export function convertEmojis(input: string): string { - return emojify(input); -} diff --git a/web/src/utils/helpers.ts b/web/src/utils/helpers.ts deleted file mode 100644 index 2efb9818b..000000000 --- a/web/src/utils/helpers.ts +++ /dev/null @@ -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}`; -} diff --git a/web/src/utils/locale.ts b/web/src/utils/locale.ts deleted file mode 100644 index 2e68c8311..000000000 --- a/web/src/utils/locale.ts +++ /dev/null @@ -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)}...`; -}