Add issue workflow progress tracker
Magent CI/CD / verify (push) Canceled after 3m55s
Magent CI/CD / deploy-prod (push) Canceled after 0s
Magent CI/CD / deploy-beta (push) Canceled after 0s

This commit is contained in:
2026-09-01 14:44:17 +12:00
parent 87a4aae246
commit c7a56f2525
4 changed files with 363 additions and 6 deletions
+67 -6
View File
@@ -40,6 +40,20 @@ type PortalItem = {
related_item_id?: number | null
is_resolved?: boolean
resolved_at?: string | null
workflow?: {
current_step?: number
total_steps?: number
stage?: string
stage_label?: string
headline?: string
message?: string
state?: 'active' | 'attention' | 'complete'
steps?: Array<{
key: string
label: string
state: 'waiting' | 'active' | 'attention' | 'complete'
}>
}
confirmation?: {
status?: string | null
attempts_sent?: number
@@ -345,6 +359,53 @@ const formatIssueStatus = (value?: string | null) => {
return labels[String(value ?? '').toLowerCase()] ?? String(value ?? 'Unknown').replaceAll('_', ' ')
}
function IssuePipeline({ item, compact = false }: { item: PortalItem; compact?: boolean }) {
const workflow = item.issue?.workflow
const steps = workflow?.steps ?? []
const currentStep = workflow?.current_step ?? 1
const totalSteps = workflow?.total_steps ?? 6
const state = workflow?.state ?? 'active'
if (compact) {
return (
<div className={`issue-card-progress is-${state}`}>
<span>
<strong>{workflow?.stage_label ?? formatIssueStatus(item.status)}</strong>
<small>Step {currentStep} of {totalSteps}</small>
</span>
<i aria-hidden="true">
<b style={{ width: `${Math.max(0, Math.min(100, (currentStep / totalSteps) * 100))}%` }} />
</i>
</div>
)
}
return (
<section className={`issue-pipeline-card is-${state}`} aria-label="Issue progress">
<header>
<div>
<span className="section-kicker">Issue progress</span>
<h3>{workflow?.headline ?? formatIssueStatus(item.status)}</h3>
<p>{workflow?.message ?? 'The support team will update this issue as work progresses.'}</p>
</div>
<span className="small-pill">Step {currentStep} of {totalSteps}</span>
</header>
<ol>
{steps.map((step, index) => (
<li
key={step.key}
className={`is-${step.state}`}
aria-current={step.state === 'active' || step.state === 'attention' ? 'step' : undefined}
>
<i aria-hidden="true">{step.state === 'complete' ? '✓' : index + 1}</i>
<span>{step.label}</span>
</li>
))}
</ol>
</section>
)
}
const toPositiveInt = (value: string) => {
const parsed = Number.parseInt(value, 10)
if (Number.isNaN(parsed) || parsed <= 0) return null
@@ -1999,14 +2060,12 @@ export default function PortalClient({ workspace }: PortalClientProps) {
<span className="small-pill is-muted">{item.priority}</span>
</div>
<p>{item.description}</p>
{item.kind === 'issue' ? <IssuePipeline item={item} compact /> : null}
<div className="portal-item-row-meta">
<span>#{item.id}</span>
<span>
Status:{' '}
{item.kind === 'request'
? item.workflow?.stage_label ?? item.status
: formatIssueStatus(item.status)}
</span>
{item.kind === 'request' ? (
<span>Status: {item.workflow?.stage_label ?? item.status}</span>
) : null}
{isAdmin ? <span>By: {item.created_by_username}</span> : null}
<span>Updated: {formatDate(item.last_activity_at)}</span>
</div>
@@ -2086,6 +2145,8 @@ export default function PortalClient({ workspace }: PortalClientProps) {
</div>
</div>
{selectedItem.kind === 'issue' ? <IssuePipeline item={selectedItem} /> : null}
{selectedItem.kind === 'issue' && selectedItem.external_ref?.startsWith('/requests/') ? (
<div className="issue-linked-request">
<div>