Enhance linter and errors (#1572)

Co-authored-by: 6543 <m.huber@kithara.com>
Co-authored-by: qwerty287 <80460567+qwerty287@users.noreply.github.com>
This commit is contained in:
Anbraten
2023-11-03 11:44:03 +01:00
committed by GitHub
parent 4c4fdff5f7
commit 5ff006614f
55 changed files with 912 additions and 342 deletions

View File

@@ -230,7 +230,6 @@
"config": "Config",
"files": "Changed files ({files})",
"no_files": "No files have been changed.",
"execution_error": "Execution error",
"no_pipelines": "No pipelines have been started yet.",
"no_pipeline_steps": "No pipeline steps available!",
"step_not_started": "This step hasn't started yet.",
@@ -281,7 +280,11 @@
"error": "error",
"failure": "failure",
"killed": "killed"
}
},
"errors": "Errors ({count})",
"warnings": "Warnings ({count})",
"show_errors": "Show errors",
"we_got_some_errors": "Oh no, we got some errors!"
}
},
"org": {

View File

@@ -9,12 +9,10 @@ import { computed, onMounted, ref } from 'vue';
import { Tab, useTabsClient } from '~/compositions/useTabs';
export interface Props {
const props = defineProps<{
id?: string;
title: string;
}
const props = defineProps<Props>();
}>();
const { tabs, activeTab } = useTabsClient();
const tab = ref<Tab>();

View File

@@ -1,9 +0,0 @@
<template>
<div class="w-full md:px-4 text-wp-text-100">
<div
class="flex flex-col px-4 py-8 gap-4 justify-center items-center text-center flex-shrink-0 rounded-md border bg-wp-background-100 border-wp-background-400 dark:bg-wp-background-200"
>
<slot />
</div>
</div>
</template>

View File

@@ -1,5 +1,12 @@
import { WebhookEvents } from './webhook';
export type PipelineError = {
type: string;
message: string;
data?: unknown;
is_warning: boolean;
};
// A pipeline for a repository.
export type Pipeline = {
id: number;
@@ -15,7 +22,7 @@ export type Pipeline = {
// The current status of the pipeline.
status: PipelineStatus;
error: string;
errors?: PipelineError[];
// When the pipeline request was received.
created_at: number;

View File

@@ -88,6 +88,12 @@ const routes: RouteRecordRaw[] = [
component: (): Component => import('~/views/repo/pipeline/PipelineConfig.vue'),
props: true,
},
{
path: 'errors',
name: 'repo-pipeline-errors',
component: (): Component => import('~/views/repo/pipeline/PipelineErrors.vue'),
props: true,
},
],
},
{

View File

@@ -2,54 +2,74 @@
<Container full-width class="flex flex-col flex-grow md:min-h-xs">
<div class="flex w-full min-h-0 flex-grow">
<PipelineStepList
v-if="pipeline?.workflows?.length || 0 > 0"
v-if="pipeline?.workflows && pipeline?.workflows?.length > 0"
v-model:selected-step-id="selectedStepId"
:class="{ 'hidden md:flex': pipeline.status === 'blocked' }"
:pipeline="pipeline"
/>
<div class="flex flex-grow relative">
<PipelineInfo v-if="error">
<Icon name="status-error" class="w-16 h-16 text-wp-state-error-100" />
<div class="flex flex-wrap items-center justify-center gap-2 text-xl">
<span class="capitalize">{{ $t('repo.pipeline.execution_error') }}:</span>
<span>{{ error }}</span>
</div>
</PipelineInfo>
<div class="flex items-start justify-center flex-grow relative">
<Container v-if="selectedStep?.error" class="py-0">
<Panel>
<div class="flex flex-col items-center gap-4">
<Icon name="status-error" class="w-16 h-16 text-wp-state-error-100" />
<span class="text-xl">{{ $t('repo.pipeline.we_got_some_errors') }}</span>
<span class="whitespace-pre">{{ selectedStep?.error }}</span>
</div>
</Panel>
</Container>
<PipelineInfo v-else-if="pipeline.status === 'blocked'">
<Icon name="status-blocked" class="w-16 h-16" />
<span class="text-xl">{{ $t('repo.pipeline.protected.awaits') }}</span>
<div v-if="repoPermissions.push" class="flex gap-2 flex-wrap items-center justify-center">
<Button
color="blue"
:start-icon="forge ?? 'repo'"
:text="$t('repo.pipeline.protected.review')"
:to="pipeline.link_url"
:title="message"
/>
<Button
color="green"
:text="$t('repo.pipeline.protected.approve')"
:is-loading="isApprovingPipeline"
@click="approvePipeline"
/>
<Button
color="red"
:text="$t('repo.pipeline.protected.decline')"
:is-loading="isDecliningPipeline"
@click="declinePipeline"
/>
</div>
</PipelineInfo>
<Container v-else-if="pipeline.errors?.some((e) => !e.is_warning)" class="py-0">
<Panel>
<div class="flex flex-col items-center gap-4">
<Icon name="status-error" class="w-16 h-16 text-wp-state-error-100" />
<span class="text-xl">{{ $t('repo.pipeline.we_got_some_errors') }}</span>
<Button color="red" :text="$t('repo.pipeline.show_errors')" :to="{ name: 'repo-pipeline-errors' }" />
</div>
</Panel>
</Container>
<PipelineInfo v-else-if="pipeline.status === 'declined'">
<Icon name="status-blocked" class="w-16 h-16" />
<p class="text-xl">{{ $t('repo.pipeline.protected.declined') }}</p>
</PipelineInfo>
<Container v-else-if="pipeline.status === 'blocked'" class="py-0">
<Panel>
<div class="flex flex-col items-center gap-4">
<Icon name="status-blocked" class="w-16 h-16" />
<span class="text-xl">{{ $t('repo.pipeline.protected.awaits') }}</span>
<div v-if="repoPermissions.push" class="flex gap-2 flex-wrap items-center justify-center">
<Button
color="blue"
:start-icon="forge ?? 'repo'"
:text="$t('repo.pipeline.protected.review')"
:to="pipeline.link_url"
:title="message"
/>
<Button
color="green"
:text="$t('repo.pipeline.protected.approve')"
:is-loading="isApprovingPipeline"
@click="approvePipeline"
/>
<Button
color="red"
:text="$t('repo.pipeline.protected.decline')"
:is-loading="isDecliningPipeline"
@click="declinePipeline"
/>
</div>
</div>
</Panel>
</Container>
<Container v-else-if="pipeline.status === 'declined'" class="py-0">
<Panel>
<div class="flex flex-col items-center gap-4">
<Icon name="status-declined" class="w-16 h-16 text-wp-state-error-100" />
<p class="text-xl">{{ $t('repo.pipeline.protected.declined') }}</p>
</div>
</Panel>
</Container>
<PipelineLog
v-else-if="selectedStepId"
v-else-if="selectedStepId !== null"
v-model:step-id="selectedStepId"
:pipeline="pipeline"
class="fixed top-0 left-0 w-full h-full md:absolute"
@@ -74,7 +94,7 @@ import { useAsyncAction } from '~/compositions/useAsyncAction';
import useConfig from '~/compositions/useConfig';
import useNotifications from '~/compositions/useNotifications';
import usePipeline from '~/compositions/usePipeline';
import { Pipeline, PipelineStep, Repo, RepoPermissions } from '~/lib/api/types';
import { Pipeline, Repo, RepoPermissions } from '~/lib/api/types';
import { findStep } from '~/utils/helpers';
const props = defineProps<{
@@ -96,22 +116,13 @@ if (!repo || !repoPermissions || !pipeline) {
const stepId = toRef(props, 'stepId');
const defaultStepId = computed(() => {
if (!pipeline.value || !pipeline.value.workflows || !pipeline.value.workflows[0].children) {
return null;
}
return pipeline.value.workflows[0].children[0].pid;
});
const defaultStepId = computed(() => pipeline.value?.workflows?.[0].children?.[0].pid ?? null);
const selectedStepId = computed({
get() {
if (stepId.value !== '' && stepId.value !== null && stepId.value !== undefined) {
const id = parseInt(stepId.value, 10);
const step = pipeline.value?.workflows?.reduce(
(prev, p) => prev || p.children?.find((c) => c.pid === id),
undefined as PipelineStep | undefined,
);
const step = pipeline.value?.workflows?.find((p) => p.children?.find((c) => c.pid === id));
if (step) {
return step.pid;
}
@@ -128,7 +139,7 @@ const selectedStepId = computed({
return null;
},
set(_selectedStepId: number | null) {
if (!_selectedStepId) {
if (_selectedStepId === null) {
router.replace({ params: { ...route.params, stepId: '' } });
return;
}
@@ -141,7 +152,6 @@ const { forge } = useConfig();
const { message } = usePipeline(pipeline);
const selectedStep = computed(() => findStep(pipeline.value.workflows || [], selectedStepId.value || -1));
const error = computed(() => pipeline.value?.error || selectedStep.value?.error);
const { doSubmit: approvePipeline, isLoading: isApprovingPipeline } = useAsyncAction(async () => {
if (!repo) {

View File

@@ -0,0 +1,31 @@
<template>
<Panel>
<div class="grid justify-center gap-2 text-left grid-3-1">
<template v-for="(error, i) in pipeline.errors" :key="i">
<span>{{ error.is_warning ? '⚠️' : '❌' }}</span>
<span>[{{ error.type }}]</span>
<span v-if="error.type === 'linter'" class="underline">{{ (error.data as any)?.field }}</span>
<span v-else />
<span class="ml-4">{{ error.message }}</span>
</template>
</div>
</Panel>
</template>
<script lang="ts" setup>
import { inject, Ref } from 'vue';
import Panel from '~/components/layout/Panel.vue';
import { Pipeline } from '~/lib/api/types';
const pipeline = inject<Ref<Pipeline>>('pipeline');
if (!pipeline) {
throw new Error('Unexpected: "pipeline" should be provided at this place');
}
</script>
<style scoped>
.grid-3-1 {
grid-template-columns: auto auto auto 1fr;
}
</style>

View File

@@ -1,84 +1,97 @@
<template>
<template v-if="pipeline && repo">
<Scaffold
v-model:activeTab="activeTab"
enable-tabs
disable-hash-mode
:go-back="goBack"
:fluid-content="activeTab === 'tasks'"
full-width-header
>
<template #title>{{ repo.full_name }}</template>
<Scaffold
v-if="pipeline && repo"
v-model:activeTab="activeTab"
enable-tabs
disable-hash-mode
:go-back="goBack"
:fluid-content="activeTab === 'tasks'"
full-width-header
>
<template #title>{{ repo.full_name }}</template>
<template #titleActions>
<div class="flex md:items-center flex-col gap-2 md:flex-row md:justify-between min-w-0">
<div class="flex content-start gap-2 min-w-0">
<PipelineStatusIcon :status="pipeline.status" class="flex flex-shrink-0" />
<span class="flex-shrink-0 text-center">{{ $t('repo.pipeline.pipeline', { pipelineId }) }}</span>
<span class="hidden md:inline-block">-</span>
<span class="min-w-0 whitespace-nowrap overflow-hidden overflow-ellipsis" :title="message">{{
title
}}</span>
</div>
<template v-if="repoPermissions.push && pipeline.status !== 'declined' && pipeline.status !== 'blocked'">
<div class="flex content-start gap-x-2">
<Button
v-if="pipeline.status === 'pending' || pipeline.status === 'running'"
class="flex-shrink-0"
:text="$t('repo.pipeline.actions.cancel')"
:is-loading="isCancelingPipeline"
@click="cancelPipeline"
/>
<Button
class="flex-shrink-0"
:text="$t('repo.pipeline.actions.restart')"
:is-loading="isRestartingPipeline"
@click="restartPipeline"
/>
<Button
v-if="pipeline.status === 'success'"
class="flex-shrink-0"
:text="$t('repo.pipeline.actions.deploy')"
@click="showDeployPipelinePopup = true"
/>
<DeployPipelinePopup
:pipeline-number="pipelineId"
:open="showDeployPipelinePopup"
@close="showDeployPipelinePopup = false"
/>
</div>
</template>
<template #titleActions>
<div class="flex md:items-center flex-col gap-2 md:flex-row md:justify-between min-w-0">
<div class="flex content-start gap-2 min-w-0">
<PipelineStatusIcon :status="pipeline.status" class="flex flex-shrink-0" />
<span class="flex-shrink-0 text-center">{{ $t('repo.pipeline.pipeline', { pipelineId }) }}</span>
<span class="hidden md:inline-block">-</span>
<span class="min-w-0 whitespace-nowrap overflow-hidden overflow-ellipsis" :title="message">{{ title }}</span>
</div>
</template>
<template #tabActions>
<div class="flex gap-x-4">
<div class="flex space-x-1 items-center flex-shrink-0" :title="created">
<Icon name="since" />
<span>{{ since }}</span>
</div>
<div class="flex space-x-1 items-center flex-shrink-0">
<Icon name="duration" />
<span>{{ duration }}</span>
<template v-if="repoPermissions.push && pipeline.status !== 'declined' && pipeline.status !== 'blocked'">
<div class="flex content-start gap-x-2">
<Button
v-if="pipeline.status === 'pending' || pipeline.status === 'running'"
class="flex-shrink-0"
:text="$t('repo.pipeline.actions.cancel')"
:is-loading="isCancelingPipeline"
@click="cancelPipeline"
/>
<Button
class="flex-shrink-0"
:text="$t('repo.pipeline.actions.restart')"
:is-loading="isRestartingPipeline"
@click="restartPipeline"
/>
<Button
v-if="pipeline.status === 'success'"
class="flex-shrink-0"
:text="$t('repo.pipeline.actions.deploy')"
@click="showDeployPipelinePopup = true"
/>
<DeployPipelinePopup
:pipeline-number="pipelineId"
:open="showDeployPipelinePopup"
@close="showDeployPipelinePopup = false"
/>
</div>
</template>
</div>
</template>
<template #tabActions>
<div class="flex gap-x-4">
<div class="flex space-x-1 items-center flex-shrink-0" :title="created">
<Icon name="since" />
<span>{{ since }}</span>
</div>
</template>
<div class="flex space-x-1 items-center flex-shrink-0">
<Icon name="duration" />
<span>{{ duration }}</span>
</div>
</div>
</template>
<Tab id="tasks" :title="$t('repo.pipeline.tasks')" />
<Tab id="config" :title="$t('repo.pipeline.config')" />
<Tab
v-if="
(pipeline.event === 'push' || pipeline.event === 'pull_request') &&
pipeline.changed_files &&
pipeline.changed_files.length > 0
"
id="changed-files"
:title="$t('repo.pipeline.files', { files: pipeline.changed_files.length })"
/>
<router-view />
</Scaffold>
</template>
<Tab id="tasks" :title="$t('repo.pipeline.tasks')" />
<Tab
v-if="pipeline.errors && pipeline.errors.length > 0"
id="errors"
:title="
pipeline.errors.some((e) => !e.is_warning)
? '❌ ' +
$t('repo.pipeline.errors', {
count: pipeline.errors?.length,
})
: '⚠️ ' +
$t('repo.pipeline.warnings', {
count: pipeline.errors?.length,
})
"
/>
<Tab id="config" :title="$t('repo.pipeline.config')" />
<Tab
v-if="
(pipeline.event === 'push' || pipeline.event === 'pull_request') &&
pipeline.changed_files &&
pipeline.changed_files.length > 0
"
id="changed-files"
:title="$t('repo.pipeline.files', { files: pipeline.changed_files?.length })"
/>
<router-view />
</Scaffold>
</template>
<script lang="ts" setup>
@@ -182,6 +195,10 @@ const activeTab = computed({
return 'config';
}
if (route.name === 'repo-pipeline-errors') {
return 'errors';
}
return 'tasks';
},
set(tab: string) {
@@ -196,6 +213,10 @@ const activeTab = computed({
if (tab === 'config') {
router.replace({ name: 'repo-pipeline-config' });
}
if (tab === 'errors') {
router.replace({ name: 'repo-pipeline-errors' });
}
},
});