This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / web / src / lib / components / repo / pipelines / PipelineCardContent.svelte
5.5 kB 154 lines
1<script module lang="ts"> 2 import { tv, type VariantProps } from "tailwind-variants"; 3 import type { TagVariants } from "$lib/components/ui/Tag.svelte"; 4 import type { PipelineStatus } from "$lib/components/repo/types"; 5 6 export const pipelineCardContent = tv({ 7 slots: { 8 // does not wrap: a long branch name wraps inside the trigger group instead, so the 9 // sha stays pinned top-right 10 header: "flex items-start justify-between gap-3 pb-2", 11 trigger: "flex min-w-0 flex-wrap items-center gap-x-1.5 gap-y-1", 12 triggerRow: "flex flex-wrap items-center gap-x-1.5 gap-y-1 text-foreground-default", 13 triggerIcon: "size-4 shrink-0 text-foreground-muted", 14 triggerVerb: "text-foreground-muted", 15 triggerRef: "font-semibold", 16 triggerArrow: "size-3 shrink-0 text-foreground-muted", 17 pullLink: "typography-paragraph-small text-foreground-muted no-underline hover:underline", 18 sha: "shrink-0 rounded-sm bg-background-inset px-2 py-0.5 typography-monospace-small text-foreground-default no-underline hover:underline", 19 meta: "flex flex-wrap items-center justify-between text-foreground-muted", 20 metaSub: "flex flex-wrap items-center gap-2", 21 workflows: "ml-1", 22 duration: "inline-flex items-center gap-1", 23 durationIcon: "size-3 shrink-0" 24 }, 25 variants: { 26 // the trigger line is only a link once a workflow exists to open 27 linked: { 28 true: { triggerRow: "no-underline hover:underline" }, 29 false: {} 30 } 31 }, 32 defaultVariants: { 33 linked: false 34 } 35 }); 36 37 export type PipelineCardContentVariants = VariantProps<typeof pipelineCardContent>; 38 39 /** which Tag colour each status uses */ 40 export const STATUS_TAG_COLORS: Record<PipelineStatus, TagVariants["color"]> = { 41 pending: "gray", 42 running: "warning", 43 success: "success", 44 failed: "danger", 45 timeout: "warning", 46 cancelled: "gray" 47 }; 48</script> 49 50<script lang="ts"> 51 import { resolve } from "$app/paths"; 52 import ArrowLeft from "$icon/arrow-left"; 53 import CirclePlay from "$icon/circle-play"; 54 import Clock from "$icon/clock"; 55 import GitCommitHorizontal from "$icon/git-commit-horizontal"; 56 import GitPullRequest from "$icon/git-pull-request"; 57 import TimeAgo from "$lib/components/ui/TimeAgo.svelte"; 58 import { formatDuration } from "$lib/format"; 59 import Tag from "$lib/components/ui/Tag.svelte"; 60 import type { PipelineSummary } from "$lib/components/repo/types"; 61 import { aggregateStatus, longSummary, STATUS_LABELS, totalDuration } from "./pipeline"; 62 import { STATUS_ICONS } from "./PipelineStatusIcon.svelte"; 63 import PipelineWorkflows from "./PipelineWorkflows.svelte"; 64 import Separator from "$lib/components/ui/Separator.svelte"; 65 66 interface Props { 67 ownerHandle: string; 68 repoName: string; 69 pipeline: PipelineSummary; 70 } 71 72 let { ownerHandle, repoName, pipeline }: Props = $props(); 73 74 const status = $derived(aggregateStatus(pipeline)); 75 const duration = $derived(totalDuration(pipeline)); 76 77 // links to the first workflow's logs, plain text until a spindle reports one 78 // TODO: the workflow log route does not exist in the svelte app yet 79 const href = $derived( 80 pipeline.workflows.length > 0 81 ? resolve( 82 `/${ownerHandle}/${repoName}/pipelines/${pipeline.id}/workflow/${pipeline.workflows[0].name}` as "/" 83 ) 84 : undefined 85 ); 86 const commitHref = $derived(resolve(`/${ownerHandle}/${repoName}/commit/${pipeline.sha}` as "/")); 87 88 const classes = $derived(pipelineCardContent({ linked: href !== undefined })); 89</script> 90 91{#snippet triggerLabel()} 92 {#if pipeline.trigger.kind === "push"} 93 <GitCommitHorizontal class={classes.triggerIcon()} aria-hidden="true" /> 94 <span class={classes.triggerVerb()}>Push to</span> 95 <span class={classes.triggerRef()}>{pipeline.trigger.targetRef}</span> 96 {:else if pipeline.trigger.kind === "pull_request"} 97 <GitPullRequest class={classes.triggerIcon()} aria-hidden="true" /> 98 <span class={classes.triggerVerb()}>Pull request</span> 99 <span class={classes.triggerRef()}>{pipeline.trigger.targetRef}</span> 100 <ArrowLeft class={classes.triggerArrow()} aria-hidden="true" /> 101 <span class={classes.triggerRef()}>{pipeline.trigger.sourceLabel}</span> 102 {:else} 103 <CirclePlay class={classes.triggerIcon()} aria-hidden="true" /> 104 <span class={classes.triggerVerb()}>Manual dispatch</span> 105 {/if} 106{/snippet} 107 108<div class={classes.header()}> 109 <div class={classes.trigger()}> 110 {#if href} 111 <a {href} class={classes.triggerRow()}> 112 {@render triggerLabel()} 113 </a> 114 {:else} 115 <span class={classes.triggerRow()}> 116 {@render triggerLabel()} 117 </span> 118 {/if} 119 120 {#if pipeline.trigger.kind === "pull_request" && pipeline.trigger.pullPath} 121 <a href={resolve(pipeline.trigger.pullPath as "/")} class={classes.pullLink()}> (view PR) </a> 122 {/if} 123 </div> 124 125 {#if pipeline.sha} 126 <a href={commitHref} class={classes.sha()}> 127 {pipeline.sha.slice(0, 8)} 128 </a> 129 {/if} 130</div> 131 132<div class={classes.meta()}> 133 <span class={classes.metaSub()}> 134 <Tag class="mt-px" size="lg" color={STATUS_TAG_COLORS[status]} icon={STATUS_ICONS[status]} 135 >{STATUS_LABELS[status]}</Tag 136 > 137 138 <span class={classes.workflows()} title={longSummary(pipeline) || undefined}> 139 <PipelineWorkflows {ownerHandle} {repoName} {pipeline} /> 140 </span> 141 </span> 142 143 <span class={classes.metaSub()}> 144 <TimeAgo value={pipeline.createdAt} /> 145 146 {#if duration > 0} 147 <Separator variant="dot" /> 148 <span class={classes.duration()}> 149 <Clock class={classes.durationIcon()} aria-hidden="true" /> 150 {formatDuration(duration)} 151 </span> 152 {/if} 153 </span> 154</div>