Use icons for step and workflow states (#1409)

Co-authored-by: 6543 <6543@obermui.de>
This commit is contained in:
Anbraten 2022-11-14 12:25:58 +01:00 committed by GitHub
parent 0295d36f29
commit cab996608e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 119 additions and 118 deletions

15
web/components.d.ts vendored
View file

@ -19,10 +19,22 @@ declare module '@vue/runtime-core' {
FluidContainer: typeof import('./src/components/layout/FluidContainer.vue')['default'] FluidContainer: typeof import('./src/components/layout/FluidContainer.vue')['default']
GeneralTab: typeof import('./src/components/repo/settings/GeneralTab.vue')['default'] GeneralTab: typeof import('./src/components/repo/settings/GeneralTab.vue')['default']
Header: typeof import('./src/components/layout/scaffold/Header.vue')['default'] Header: typeof import('./src/components/layout/scaffold/Header.vue')['default']
IBiCheckCircle: typeof import('~icons/bi/check-circle')['default']
IBiCheckCircleFill: typeof import('~icons/bi/check-circle-fill')['default']
IBiCircle: typeof import('~icons/bi/circle')['default']
IBiPauseCircleFill: typeof import('~icons/bi/pause-circle-fill')['default']
IBiPlayCircleFill: typeof import('~icons/bi/play-circle-fill')['default']
IBiSlashCircleFill: typeof import('~icons/bi/slash-circle-fill')['default']
IBiStopCircleFill: typeof import('~icons/bi/stop-circle-fill')['default']
IBiXCircleFill: typeof import('~icons/bi/x-circle-fill')['default']
IBxBxPowerOff: typeof import('~icons/bx/bx-power-off')['default'] IBxBxPowerOff: typeof import('~icons/bx/bx-power-off')['default']
ICarbonCloseOutline: typeof import('~icons/carbon/close-outline')['default'] ICarbonCloseOutline: typeof import('~icons/carbon/close-outline')['default']
ICarbonInProgress: typeof import('~icons/carbon/in-progress')['default']
IClarityDeployLine: typeof import('~icons/clarity/deploy-line')['default'] IClarityDeployLine: typeof import('~icons/clarity/deploy-line')['default']
IClarityNoAccessSolid: typeof import('~icons/clarity/no-access-solid')['default']
IClaritySettingsSolid: typeof import('~icons/clarity/settings-solid')['default'] IClaritySettingsSolid: typeof import('~icons/clarity/settings-solid')['default']
IClaritySuccessStandardSolid: typeof import('~icons/clarity/success-standard-solid')['default']
IClarityTimesCircleSolid: typeof import('~icons/clarity/times-circle-solid')['default']
Icon: typeof import('./src/components/atomic/Icon.vue')['default'] Icon: typeof import('./src/components/atomic/Icon.vue')['default']
IconButton: typeof import('./src/components/atomic/IconButton.vue')['default'] IconButton: typeof import('./src/components/atomic/IconButton.vue')['default']
IEntypoDotsTwoVertical: typeof import('~icons/entypo/dots-two-vertical')['default'] IEntypoDotsTwoVertical: typeof import('~icons/entypo/dots-two-vertical')['default']
@ -63,6 +75,9 @@ declare module '@vue/runtime-core' {
IPhXCircle: typeof import('~icons/ph/x-circle')['default'] IPhXCircle: typeof import('~icons/ph/x-circle')['default']
ISimpleIconsGitea: typeof import('~icons/simple-icons/gitea')['default'] ISimpleIconsGitea: typeof import('~icons/simple-icons/gitea')['default']
ITeenyiconsGitSolid: typeof import('~icons/teenyicons/git-solid')['default'] ITeenyiconsGitSolid: typeof import('~icons/teenyicons/git-solid')['default']
IUiwCircleCheck: typeof import('~icons/uiw/circle-check')['default']
IUiwCircleClose: typeof import('~icons/uiw/circle-close')['default']
IUiwStop: typeof import('~icons/uiw/stop')['default']
IVaadinQuestionCircleO: typeof import('~icons/vaadin/question-circle-o')['default'] IVaadinQuestionCircleO: typeof import('~icons/vaadin/question-circle-o')['default']
ListItem: typeof import('./src/components/atomic/ListItem.vue')['default'] ListItem: typeof import('./src/components/atomic/ListItem.vue')['default']
ManualPipelinePopup: typeof import('./src/components/layout/popups/ManualPipelinePopup.vue')['default'] ManualPipelinePopup: typeof import('./src/components/layout/popups/ManualPipelinePopup.vue')['default']

View file

@ -237,6 +237,19 @@
"deploy": "Deploy", "deploy": "Deploy",
"cron": "Cron", "cron": "Cron",
"manual": "Manual" "manual": "Manual"
},
"status": {
"status": "Status: {status}",
"blocked": "blocked",
"pending": "pending",
"running": "running",
"started": "started",
"skipped": "skipped",
"success": "success",
"declined": "declined",
"error": "error",
"failure": "failure",
"killed": "killed"
} }
} }
}, },

View file

@ -12,16 +12,16 @@
<i-teenyicons-git-solid v-else-if="name === 'repo'" class="h-8 w-8" /> <i-teenyicons-git-solid v-else-if="name === 'repo'" class="h-8 w-8" />
<i-clarity-settings-solid v-else-if="name === 'settings'" class="w-8 h-8" /> <i-clarity-settings-solid v-else-if="name === 'settings'" class="w-8 h-8" />
<i-gg-trash v-else-if="name === 'trash'" class="h-6 w-6" /> <i-gg-trash v-else-if="name === 'trash'" class="h-6 w-6" />
<i-ph-hand v-else-if="name === 'status-blocked'" class="h-6 w-6" /> <i-bi-play-circle-fill v-else-if="name === 'status-blocked'" class="h-6 w-6" />
<i-ph-hand v-else-if="name === 'status-declined'" class="h-6 w-6" /> <i-bi-stop-circle-fill v-else-if="name === 'status-declined'" class="h-6 w-6" />
<i-ph-warning v-else-if="name === 'status-error'" class="h-8 w-8" /> <i-bi-x-circle-fill
<i-ph-x-circle v-else-if="name === 'status-failure'" class="h-8 w-8" /> v-else-if="name === 'status-failure' || name === 'status-error' || name === 'status-killed'"
<i-octicon-skip-24 v-else-if="name === 'status-killed'" class="h-7 w-7" /> class="h-6 w-6"
<i-ph-hourglass v-else-if="name === 'status-pending'" class="h-7 w-7" /> />
<i-entypo-dots-two-vertical v-else-if="name === 'status-running'" class="h-8 w-8" /> <i-bi-circle v-else-if="name === 'status-pending'" class="h-6 w-6" />
<i-ph-prohibit v-else-if="name === 'status-skipped'" class="h-8 w-8" /> <i-carbon-in-progress v-else-if="name === 'status-running' || name === 'status-started'" class="h-6 w-6" />
<i-entypo-dots-two-vertical v-else-if="name === 'status-started'" class="h-8 w-8" /> <i-bi-slash-circle-fill v-else-if="name === 'status-skipped'" class="h-6 w-6" />
<i-ph-check-circle v-else-if="name === 'status-success'" class="h-8 w-8" /> <i-bi-check-circle-fill v-else-if="name === 'status-success'" class="h-6 w-6" />
<i-simple-icons-gitea v-else-if="name === 'gitea'" class="h-8 w-8" /> <i-simple-icons-gitea v-else-if="name === 'gitea'" class="h-8 w-8" />
<i-ph-gitlab-logo-simple-fill v-else-if="name === 'gitlab'" class="h-8 w-8" /> <i-ph-gitlab-logo-simple-fill v-else-if="name === 'gitlab'" class="h-8 w-8" />
<i-mdi-bitbucket v-else-if="name === 'bitbucket'" class="h-8 w-8" /> <i-mdi-bitbucket v-else-if="name === 'bitbucket'" class="h-8 w-8" />

View file

@ -1,6 +1,6 @@
<template> <template>
<div v-if="pipeline" class="flex text-color w-full"> <div v-if="pipeline" class="flex text-color w-full">
<PipelineStatusIcon :pipeline="pipeline" class="flex items-center" /> <PipelineStatusIcon :status="pipeline.status" class="flex items-center" />
<div class="flex flex-col ml-4 min-w-0"> <div class="flex flex-col ml-4 min-w-0">
<span class="underline">{{ pipeline.owner }} / {{ pipeline.name }}</span> <span class="underline">{{ pipeline.owner }} / {{ pipeline.name }}</span>
<span class="whitespace-nowrap overflow-hidden overflow-ellipsis">{{ message }}</span> <span class="whitespace-nowrap overflow-hidden overflow-ellipsis">{{ message }}</span>

View file

@ -13,7 +13,7 @@
/> />
<div class="w-8 flex flex-wrap justify-between items-center h-full"> <div class="w-8 flex flex-wrap justify-between items-center h-full">
<PipelineRunningIcon v-if="pipeline.status === 'started' || pipeline.status === 'running'" /> <PipelineRunningIcon v-if="pipeline.status === 'started' || pipeline.status === 'running'" />
<PipelineStatusIcon v-else class="mx-2 md:mx-3" :pipeline="pipeline" /> <PipelineStatusIcon v-else class="mx-2 md:mx-3" :status="pipeline.status" />
</div> </div>
</div> </div>

View file

@ -1,43 +1,27 @@
<template> <template>
<div v-if="pipeline" class="flex items-center justify-center"> <div
class="flex items-center justify-center"
:title="$t('repo.pipeline.status.status', { status: $t(`repo.pipeline.status.${status}`) })"
>
<Icon <Icon
:name="`status-${pipeline.status}`" :name="`status-${status}`"
:class="{ :class="{
'text-yellow-400': pipeline.status === 'pending', 'text-red-400': pipelineStatusColors[status] === 'red',
'text-red-400': pipelineStatusColors[pipeline.status] === 'red', 'text-gray-400': pipelineStatusColors[status] === 'gray',
'text-gray-400': pipelineStatusColors[pipeline.status] === 'gray', 'text-lime-400': pipelineStatusColors[status] === 'green',
'text-lime-400': pipelineStatusColors[pipeline.status] === 'green', 'text-blue-400': pipelineStatusColors[status] === 'blue',
'text-blue-400': pipelineStatusColors[pipeline.status] === 'blue',
[pipelineStatusAnimations[pipeline.status]]: true,
}" }"
/> />
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent, PropType } from 'vue';
import Icon from '~/components/atomic/Icon.vue'; import Icon from '~/components/atomic/Icon.vue';
import { Pipeline } from '~/lib/api/types'; import { PipelineStatus } from '~/lib/api/types';
import { pipelineStatusAnimations, pipelineStatusColors } from './pipeline-status'; import { pipelineStatusColors } from './pipeline-status';
export default defineComponent({ defineProps<{
name: 'PipelineStatusIcon', status: PipelineStatus;
}>();
components: {
Icon,
},
props: {
pipeline: {
type: Object as PropType<Pipeline>,
required: true,
},
},
setup() {
return { pipelineStatusColors, pipelineStatusAnimations };
},
});
</script> </script>

View file

@ -42,84 +42,69 @@
<span>{{ $t('repo.pipeline.no_pipeline_steps') }}</span> <span>{{ $t('repo.pipeline.no_pipeline_steps') }}</span>
</div> </div>
<div class="flex flex-grow flex-col relative min-h-0 overflow-y-auto gap-2"> <div class="flex-grow min-h-0 w-full relative">
<div <div class="absolute top-0 left-0 -right-3 h-full flex flex-col overflow-y-scroll gap-y-2">
v-for="step in pipeline.steps"
:key="step.id"
class="p-2 md:rounded-md bg-white shadow dark:border-b-dark-gray-600 dark:bg-dark-gray-700"
>
<div class="flex flex-col gap-2">
<div v-if="step.environ" class="flex flex-wrap gap-x-1 gap-y-2 text-xs justify-end pt-1">
<div v-for="(value, key) in step.environ" :key="key">
<span
class="pl-2 pr-1 py-0.5 bg-gray-800 text-gray-200 dark:bg-gray-600 border-2 border-gray-800 dark:border-gray-600 rounded-l-full"
>
{{ key }}
</span>
<span class="pl-1 pr-2 py-0.5 border-2 border-gray-800 dark:border-gray-600 rounded-r-full">
{{ value }}
</span>
</div>
</div>
<button
v-if="pipeline.steps && pipeline.steps.length > 1"
type="button"
:title="step.name"
class="flex items-center gap-2 py-2 px-1 hover:bg-black hover:bg-opacity-10 dark:hover:bg-white dark:hover:bg-opacity-5 rounded-md"
@click="stepsCollapsed[step.id] = !!!stepsCollapsed[step.id]"
>
<Icon
name="chevron-right"
class="transition-transform duration-150 min-w-6 h-6"
:class="{ 'transform rotate-90': !stepsCollapsed[step.id] }"
/>
<div
class="min-w-2 h-2 rounded-full -ml-0.75"
:class="{
'bg-lime-400': ['success'].includes(step.state),
'bg-gray-400': ['pending', 'skipped'].includes(step.state),
'bg-red-400': ['killed', 'error', 'failure', 'blocked', 'declined'].includes(step.state),
'bg-blue-400': ['started', 'running'].includes(step.state),
}"
/>
<span class="truncate">{{ step.name }}</span>
</button>
</div>
<div <div
class="transition-height duration-150 overflow-hidden" v-for="workflow in pipeline.steps"
:class="{ :key="workflow.id"
'max-h-screen': !stepsCollapsed[step.id], class="p-2 md:rounded-md bg-white shadow dark:border-b-dark-gray-600 dark:bg-dark-gray-700"
'max-h-0': stepsCollapsed[step.id],
'ml-6': pipeline.steps && pipeline.steps.length > 1,
}"
> >
<button <div class="flex flex-col gap-2">
v-for="subStep in step.children" <div v-if="workflow.environ" class="flex flex-wrap gap-x-1 gap-y-2 text-xs justify-end pt-1">
:key="subStep.pid" <div v-for="(value, key) in workflow.environ" :key="key">
type="button" <span
:title="subStep.name" class="pl-2 pr-1 py-0.5 bg-gray-800 text-gray-200 dark:bg-gray-600 border-2 border-gray-800 dark:border-gray-600 rounded-l-full"
class="flex p-2 gap-2 border-2 border-transparent rounded-md items-center hover:bg-black hover:bg-opacity-10 dark:hover:bg-white dark:hover:bg-opacity-5 w-full" >
{{ key }}
</span>
<span class="pl-1 pr-2 py-0.5 border-2 border-gray-800 dark:border-gray-600 rounded-r-full">
{{ value }}
</span>
</div>
</div>
<button
v-if="pipeline.steps && pipeline.steps.length > 1"
type="button"
:title="workflow.name"
class="flex items-center gap-2 py-2 px-1 hover:bg-black hover:bg-opacity-10 dark:hover:bg-white dark:hover:bg-opacity-5 rounded-md"
@click="workflowsCollapsed[workflow.id] = !workflowsCollapsed[workflow.id]"
>
<Icon
name="chevron-right"
class="transition-transform duration-150 min-w-6 h-6"
:class="{ 'transform rotate-90': !workflowsCollapsed[workflow.id] }"
/>
<PipelineStatusIcon :status="workflow.state" class="!h-4 !w-4" />
<span class="truncate">{{ workflow.name }}</span>
</button>
</div>
<div
class="transition-height duration-150 overflow-hidden"
:class="{ :class="{
'bg-black bg-opacity-10 dark:bg-white dark:bg-opacity-5': 'max-h-screen': !workflowsCollapsed[workflow.id],
selectedStepId && selectedStepId === subStep.pid, 'max-h-0': workflowsCollapsed[workflow.id],
'mt-1': 'ml-6': pipeline.steps && pipeline.steps.length > 1,
(pipeline.steps && pipeline.steps.length > 1) ||
(step.children && subStep.pid !== step.children[0].pid),
}" }"
@click="$emit('update:selected-step-id', subStep.pid)"
> >
<div <button
class="min-w-2 h-2 rounded-full" v-for="step in workflow.children"
:key="step.pid"
type="button"
:title="step.name"
class="flex p-2 gap-2 border-2 border-transparent rounded-md items-center hover:bg-black hover:bg-opacity-10 dark:hover:bg-white dark:hover:bg-opacity-5 w-full"
:class="{ :class="{
'bg-lime-400': ['success'].includes(subStep.state), 'bg-black bg-opacity-10 dark:bg-white dark:bg-opacity-5': selectedStepId && selectedStepId === step.pid,
'bg-gray-400': ['pending', 'skipped'].includes(subStep.state), 'mt-1':
'bg-red-400': ['killed', 'error', 'failure', 'blocked', 'declined'].includes(subStep.state), (pipeline.steps && pipeline.steps.length > 1) ||
'bg-blue-400': ['started', 'running'].includes(subStep.state), (workflow.children && step.pid !== workflow.children[0].pid),
}" }"
/> @click="$emit('update:selected-step-id', step.pid)"
<span class="truncate">{{ subStep.name }}</span> >
<PipelineStepDuration :step="subStep" /> <PipelineStatusIcon :status="step.state" class="!h-4 !w-4" />
</button> <span class="truncate">{{ step.name }}</span>
<PipelineStepDuration :step="step" />
</button>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -130,6 +115,7 @@
import { ref, toRef } from 'vue'; import { ref, toRef } from 'vue';
import Icon from '~/components/atomic/Icon.vue'; import Icon from '~/components/atomic/Icon.vue';
import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vue';
import PipelineStepDuration from '~/components/repo/pipeline/PipelineStepDuration.vue'; import PipelineStepDuration from '~/components/repo/pipeline/PipelineStepDuration.vue';
import usePipeline from '~/compositions/usePipeline'; import usePipeline from '~/compositions/usePipeline';
import { Pipeline, PipelineStep } from '~/lib/api/types'; import { Pipeline, PipelineStep } from '~/lib/api/types';
@ -146,10 +132,13 @@ defineEmits<{
const pipeline = toRef(props, 'pipeline'); const pipeline = toRef(props, 'pipeline');
const { prettyRef } = usePipeline(pipeline); const { prettyRef } = usePipeline(pipeline);
const stepsCollapsed = ref<Record<PipelineStep['id'], boolean>>( const workflowsCollapsed = ref<Record<PipelineStep['id'], boolean>>(
props.pipeline.steps && props.pipeline.steps.length > 1 props.pipeline.steps && props.pipeline.steps.length > 1
? (props.pipeline.steps || []).reduce( ? (props.pipeline.steps || []).reduce(
(collapsed, step) => ({ ...collapsed, [step.id]: !['started', 'running', 'pending'].includes(step.state) }), (collapsed, workflow) => ({
...collapsed,
[workflow.id]: ['success', 'skipped', 'blocked'].includes(workflow.state),
}),
{}, {},
) )
: {}, : {},

View file

@ -1,6 +1,6 @@
import { PipelineStatus } from '~/lib/api/types'; import { PipelineStatus } from '~/lib/api/types';
export const pipelineStatusColors: Record<PipelineStatus, string> = { export const pipelineStatusColors: Record<PipelineStatus, 'green' | 'gray' | 'red' | 'blue'> = {
blocked: 'gray', blocked: 'gray',
declined: 'red', declined: 'red',
error: 'red', error: 'red',

View file

@ -8,7 +8,7 @@
</template> </template>
<template #titleActions> <template #titleActions>
<PipelineStatusIcon :pipeline="pipeline" class="flex flex-shrink-0" /> <PipelineStatusIcon :status="pipeline.status" class="flex flex-shrink-0" />
<template v-if="repoPermissions.push"> <template v-if="repoPermissions.push">
<Button <Button