fix(web): expose delivery mainline truth
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 30s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 30s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
This commit is contained in:
@@ -53,6 +53,16 @@ interface DeliveryLane {
|
||||
Icon: typeof Rocket
|
||||
}
|
||||
|
||||
interface DeliveryFocusRow {
|
||||
id: string
|
||||
title: string
|
||||
action: string
|
||||
percent: number
|
||||
blockerCount: number
|
||||
tone: DeliveryTone
|
||||
Icon: typeof Rocket
|
||||
}
|
||||
|
||||
const SOURCE_COUNT = 6
|
||||
|
||||
const EMPTY_DATA: DeliveryData = {
|
||||
@@ -164,6 +174,108 @@ function MetricTile({
|
||||
)
|
||||
}
|
||||
|
||||
function CurrentP0Panel({
|
||||
workbench,
|
||||
t,
|
||||
}: {
|
||||
workbench: DeliveryClosureWorkbenchSnapshot | null
|
||||
t: DeliveryTranslator
|
||||
}) {
|
||||
if (!workbench) return null
|
||||
|
||||
const { readback, rollups, operation_boundaries: boundaries } = workbench
|
||||
const readiness = clampPercent(readback.current_p0_readiness_percent || rollups.current_p0_readiness_percent)
|
||||
const blockers = readback.current_p0_active_blockers.length > 0 ? readback.current_p0_active_blockers : workbench.active_blockers
|
||||
const panelTone: DeliveryTone = blockers.length > 0 ? 'danger' : readiness >= 100 ? 'ok' : 'warn'
|
||||
const closedP0 = readback.closed_p0_workplan_ids.length > 0
|
||||
? readback.closed_p0_workplan_ids.join(' / ')
|
||||
: t('currentP0.closedEmpty')
|
||||
const truthRows = [
|
||||
{ key: 'eventGate', value: rollups.current_p0_blocked_by_fresh_reboot_window_only ? t('currentP0.values.yes') : t('currentP0.values.no') },
|
||||
{ key: 'service', value: rollups.current_p0_service_green ? t('currentP0.values.green') : t('currentP0.values.blocked') },
|
||||
{ key: 'productData', value: rollups.current_p0_product_data_green ? t('currentP0.values.green') : t('currentP0.values.blocked') },
|
||||
{ key: 'backup', value: rollups.current_p0_backup_core_green ? t('currentP0.values.green') : t('currentP0.values.blocked') },
|
||||
{ key: 'stock', value: `${rollups.current_p0_stockplatform_freshness_status} / ${rollups.current_p0_stockplatform_ingestion_status}` },
|
||||
{ key: 'github', value: readback.github_lane_status },
|
||||
]
|
||||
const flagRows = [
|
||||
{ key: 'runtimeWrite', value: boundaries.runtime_write_allowed || readback.runtime_write_authorized || rollups.runtime_write_authorized },
|
||||
{ key: 'workflowTrigger', value: boundaries.workflow_trigger_allowed || readback.workflow_trigger_authorized || rollups.workflow_trigger_authorized },
|
||||
{ key: 'secretCollection', value: boundaries.secret_value_collection_allowed || readback.secret_value_collection_allowed || rollups.secret_values_collected },
|
||||
{ key: 'remoteWrite', value: boundaries.remote_write_allowed },
|
||||
{ key: 'repoCreation', value: boundaries.repo_creation_allowed },
|
||||
{ key: 'activeScan', value: boundaries.active_scan_allowed },
|
||||
]
|
||||
|
||||
return (
|
||||
<section className="delivery-current-p0" data-testid="delivery-current-p0">
|
||||
<div className="delivery-section-heading">
|
||||
<div>
|
||||
<h2>{t('currentP0.title')}</h2>
|
||||
<p>{t('currentP0.detail')}</p>
|
||||
</div>
|
||||
<StatusPill tone={panelTone} label={readback.current_p0_status} />
|
||||
</div>
|
||||
<GlassCard variant="default" padding="md">
|
||||
<div className="delivery-current-p0-grid">
|
||||
<div className="delivery-current-p0-main">
|
||||
<div className="delivery-current-p0-icon">
|
||||
<ShieldCheck size={22} aria-hidden="true" />
|
||||
</div>
|
||||
<div>
|
||||
<h3>{readback.current_p0_workplan_id}</h3>
|
||||
<p>{readback.current_p0_safe_next_step || workbench.safe_next_step}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="delivery-current-p0-stats">
|
||||
<div>
|
||||
<span>{t('currentP0.source')}</span>
|
||||
<strong>{readback.current_p0_source_id}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>{t('currentP0.readiness')}</span>
|
||||
<strong>{readiness}%</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>{t('currentP0.closed')}</span>
|
||||
<strong>{closedP0}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="delivery-current-p0-blockers">
|
||||
<strong>{t('currentP0.blockers')}</strong>
|
||||
<div>
|
||||
{(blockers.length > 0 ? blockers : [t('currentP0.noBlockers')]).map(blocker => (
|
||||
<StatusPill key={blocker} tone={blockers.length > 0 ? 'danger' : 'ok'} label={blocker} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="delivery-current-p0-truth">
|
||||
{truthRows.map(row => (
|
||||
<div key={row.key}>
|
||||
<span>{t(`currentP0.truth.${row.key}`)}</span>
|
||||
<strong>{row.value}</strong>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="delivery-current-p0-flags">
|
||||
{flagRows.map(row => (
|
||||
<StatusPill
|
||||
key={row.key}
|
||||
tone={row.value ? 'danger' : 'ok'}
|
||||
label={`${t(`currentP0.flags.${row.key}`)}: ${row.value ? t('currentP0.values.open') : t('currentP0.values.closed')}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function DrillPreflightPanel({
|
||||
preflight,
|
||||
locale,
|
||||
@@ -535,6 +647,55 @@ export default function DeliveryPage({ params }: { params: { locale: string } })
|
||||
const highRiskBlockers = workbench?.summary.high_risk_blocker_count ?? lanes.reduce((sum, lane) => sum + lane.blockerCount, 0)
|
||||
const averageCompletion = workbench?.summary.average_completion_percent ?? clampPercent(lanes.reduce((sum, lane) => sum + lane.percent, 0) / Math.max(lanes.length, 1))
|
||||
const pageTone: DeliveryTone = highRiskBlockers > 0 ? 'danger' : loadedCount === sourceTotal ? 'ok' : 'warn'
|
||||
const activeExecutionCount = workbench
|
||||
? [
|
||||
workbench.operation_boundaries.runtime_write_allowed,
|
||||
workbench.operation_boundaries.remote_write_allowed,
|
||||
workbench.operation_boundaries.workflow_trigger_allowed,
|
||||
workbench.operation_boundaries.secret_value_collection_allowed,
|
||||
workbench.operation_boundaries.active_scan_allowed,
|
||||
].filter(Boolean).length
|
||||
: 0
|
||||
const focusRows = useMemo<DeliveryFocusRow[]>(() => {
|
||||
if (workbench?.next_focus?.length) {
|
||||
return workbench.next_focus.slice(0, 5).map(focus => {
|
||||
const lane = lanes.find(item => item.id === focus.lane_id)
|
||||
const percent = clampPercent(focus.completion_percent)
|
||||
return {
|
||||
id: focus.lane_id,
|
||||
title: lane?.title ?? focus.lane_id,
|
||||
action: focus.next_action,
|
||||
percent,
|
||||
blockerCount: focus.blocker_count,
|
||||
tone: focus.blocker_count > 0 ? 'danger' : resolveTone(0, percent),
|
||||
Icon: lane?.Icon ?? Rocket,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return lanes
|
||||
.filter(lane => lane.blockerCount > 0 || lane.percent < 80)
|
||||
.slice(0, 5)
|
||||
.map(lane => ({
|
||||
id: lane.id,
|
||||
title: lane.title,
|
||||
action: lane.nextAction || lane.metric,
|
||||
percent: lane.percent,
|
||||
blockerCount: lane.blockerCount,
|
||||
tone: lane.tone,
|
||||
Icon: lane.Icon,
|
||||
}))
|
||||
}, [lanes, workbench])
|
||||
const boundaryRows = workbench
|
||||
? [
|
||||
{ key: 'readOnlyApi', value: workbench.operation_boundaries.read_only_api_allowed, tone: 'ok' as DeliveryTone },
|
||||
{ key: 'runtimeWrite', value: workbench.operation_boundaries.runtime_write_allowed, tone: workbench.operation_boundaries.runtime_write_allowed ? 'danger' as DeliveryTone : 'ok' as DeliveryTone },
|
||||
{ key: 'remoteWrite', value: workbench.operation_boundaries.remote_write_allowed, tone: workbench.operation_boundaries.remote_write_allowed ? 'danger' as DeliveryTone : 'ok' as DeliveryTone },
|
||||
{ key: 'workflowTrigger', value: workbench.operation_boundaries.workflow_trigger_allowed, tone: workbench.operation_boundaries.workflow_trigger_allowed ? 'warn' as DeliveryTone : 'ok' as DeliveryTone },
|
||||
{ key: 'secretCollection', value: workbench.operation_boundaries.secret_value_collection_allowed, tone: workbench.operation_boundaries.secret_value_collection_allowed ? 'danger' as DeliveryTone : 'ok' as DeliveryTone },
|
||||
{ key: 'activeScan', value: workbench.operation_boundaries.active_scan_allowed, tone: workbench.operation_boundaries.active_scan_allowed ? 'danger' as DeliveryTone : 'ok' as DeliveryTone },
|
||||
]
|
||||
: []
|
||||
|
||||
return (
|
||||
<AppLayout locale={params.locale}>
|
||||
@@ -582,9 +743,11 @@ export default function DeliveryPage({ params }: { params: { locale: string } })
|
||||
<MetricTile label={t('metrics.loaded')} value={`${loadedCount}/${sourceTotal}`} detail={t('metrics.loadedDetail')} tone={loadedCount === sourceTotal ? 'ok' : 'warn'} />
|
||||
<MetricTile label={t('metrics.completion')} value={`${averageCompletion}%`} detail={t('metrics.completionDetail')} tone={averageCompletion >= 80 ? 'ok' : 'warn'} />
|
||||
<MetricTile label={t('metrics.blockers')} value={highRiskBlockers} detail={t('metrics.blockersDetail')} tone={highRiskBlockers > 0 ? 'danger' : 'ok'} />
|
||||
<MetricTile label={t('metrics.execution')} value="0" detail={t('metrics.executionDetail')} tone="ok" />
|
||||
<MetricTile label={t('metrics.execution')} value={activeExecutionCount} detail={t('metrics.executionDetail')} tone={activeExecutionCount > 0 ? 'danger' : 'ok'} />
|
||||
</section>
|
||||
|
||||
<CurrentP0Panel workbench={workbench} t={t} />
|
||||
|
||||
<DrillPreflightPanel preflight={drillPreflight} locale={params.locale} t={t} />
|
||||
|
||||
{errors.length > 0 && (
|
||||
@@ -618,19 +781,20 @@ export default function DeliveryPage({ params }: { params: { locale: string } })
|
||||
</div>
|
||||
</div>
|
||||
<div className="delivery-next-list">
|
||||
{lanes
|
||||
.filter(lane => lane.blockerCount > 0 || lane.percent < 80)
|
||||
.slice(0, 5)
|
||||
.map(lane => (
|
||||
<div key={lane.id} className="delivery-next-row">
|
||||
<lane.Icon size={17} aria-hidden="true" />
|
||||
{focusRows.length > 0 ? (
|
||||
focusRows.map(row => (
|
||||
<div key={row.id} className="delivery-next-row">
|
||||
<row.Icon size={17} aria-hidden="true" />
|
||||
<div>
|
||||
<strong>{lane.title}</strong>
|
||||
<span>{lane.nextAction || lane.metric}</span>
|
||||
<strong>{row.title}</strong>
|
||||
<span>{row.action}</span>
|
||||
</div>
|
||||
<StatusPill tone={lane.tone} label={`${lane.percent}%`} />
|
||||
<StatusPill tone={row.tone} label={t('nextFocus.status', { percent: row.percent, blockers: row.blockerCount })} />
|
||||
</div>
|
||||
))}
|
||||
))
|
||||
) : (
|
||||
<div className="delivery-next-empty">{t('nextFocus.empty')}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -642,13 +806,21 @@ export default function DeliveryPage({ params }: { params: { locale: string } })
|
||||
</div>
|
||||
</div>
|
||||
<div className="delivery-boundary-list">
|
||||
{['secret', 'production', 'repo', 'data', 'security'].map(key => (
|
||||
<div key={key} className="delivery-boundary-row">
|
||||
<Lock size={16} aria-hidden="true" />
|
||||
<span>{t(`boundaries.${key}`)}</span>
|
||||
<CheckCircle2 size={16} aria-hidden="true" />
|
||||
</div>
|
||||
))}
|
||||
{boundaryRows.length > 0
|
||||
? boundaryRows.map(row => (
|
||||
<div key={row.key} className="delivery-boundary-row">
|
||||
<Lock size={16} aria-hidden="true" />
|
||||
<span>{t(`boundaryFacts.${row.key}`)}</span>
|
||||
<StatusPill tone={row.tone} label={row.value ? t('boundaryFacts.values.open') : t('boundaryFacts.values.closed')} />
|
||||
</div>
|
||||
))
|
||||
: ['secret', 'production', 'repo', 'data', 'security'].map(key => (
|
||||
<div key={key} className="delivery-boundary-row">
|
||||
<Lock size={16} aria-hidden="true" />
|
||||
<span>{t(`boundaries.${key}`)}</span>
|
||||
<CheckCircle2 size={16} aria-hidden="true" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -668,6 +840,97 @@ export default function DeliveryPage({ params }: { params: { locale: string } })
|
||||
grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
:global(.delivery-current-p0-grid) {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.1fr) minmax(300px, 0.9fr);
|
||||
gap: 14px;
|
||||
align-items: start;
|
||||
}
|
||||
:global(.delivery-current-p0-main) {
|
||||
display: grid;
|
||||
grid-template-columns: 44px minmax(0, 1fr);
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
}
|
||||
:global(.delivery-current-p0-icon) {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #2f7d54;
|
||||
background: rgba(47, 125, 84, 0.1);
|
||||
}
|
||||
:global(.delivery-current-p0-main h3) {
|
||||
margin: 0;
|
||||
font-size: 19px;
|
||||
font-weight: 900;
|
||||
color: #141413;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
:global(.delivery-current-p0-main p) {
|
||||
margin: 6px 0 0;
|
||||
color: #595852;
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
:global(.delivery-current-p0-stats),
|
||||
:global(.delivery-current-p0-truth) {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
:global(.delivery-current-p0-truth) {
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
|
||||
}
|
||||
:global(.delivery-current-p0-stats div),
|
||||
:global(.delivery-current-p0-truth div) {
|
||||
min-height: 68px;
|
||||
border: 0.5px solid #e0ddd4;
|
||||
border-radius: 8px;
|
||||
padding: 9px;
|
||||
background: rgba(255, 255, 255, 0.56);
|
||||
min-width: 0;
|
||||
}
|
||||
:global(.delivery-current-p0-stats span),
|
||||
:global(.delivery-current-p0-truth span) {
|
||||
display: block;
|
||||
color: #706f68;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
:global(.delivery-current-p0-stats strong),
|
||||
:global(.delivery-current-p0-truth strong) {
|
||||
color: #141413;
|
||||
font-size: 15px;
|
||||
line-height: 1.3;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
:global(.delivery-current-p0-blockers) {
|
||||
grid-column: 1 / -1;
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
:global(.delivery-current-p0-blockers > strong) {
|
||||
color: #141413;
|
||||
font-size: 13px;
|
||||
font-weight: 900;
|
||||
}
|
||||
:global(.delivery-current-p0-blockers > div),
|
||||
:global(.delivery-current-p0-flags) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
:global(.delivery-current-p0-flags) {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
:global(.delivery-drill-panel) {
|
||||
min-width: 0;
|
||||
}
|
||||
@@ -824,6 +1087,16 @@ export default function DeliveryPage({ params }: { params: { locale: string } })
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.delivery-next-empty {
|
||||
min-height: 54px;
|
||||
border: 0.5px solid #e0ddd4;
|
||||
background: rgba(255, 255, 255, 0.62);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
color: #706f68;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.delivery-next-row,
|
||||
.delivery-boundary-row {
|
||||
display: grid;
|
||||
@@ -855,10 +1128,11 @@ export default function DeliveryPage({ params }: { params: { locale: string } })
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.delivery-boundary-row {
|
||||
grid-template-columns: 20px minmax(0, 1fr) 20px;
|
||||
grid-template-columns: 20px minmax(0, 1fr) auto;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.delivery-hero,
|
||||
:global(.delivery-current-p0-grid),
|
||||
:global(.delivery-drill-grid),
|
||||
.delivery-grid-two {
|
||||
grid-template-columns: 1fr;
|
||||
@@ -868,6 +1142,9 @@ export default function DeliveryPage({ params }: { params: { locale: string } })
|
||||
}
|
||||
}
|
||||
@media (max-width: 560px) {
|
||||
:global(.delivery-current-p0-stats) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
:global(.delivery-drill-metrics) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user