Refactor AppUpdateProgress for improved stage tracking and status management
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
- Introduced a new mechanism to track the active update stage using an index, allowing for better handling of out-of-order progress events. - Simplified status determination functions for download, install, and restart stages, enhancing clarity and maintainability. - Updated the rendering logic to ensure accurate display of progress and status messages, improving user experience during updates. - Adjusted modal width in AppUpdateContext for better layout consistency.
This commit is contained in:
parent
f9d4074d85
commit
3caa62d675
@ -1,5 +1,5 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import { useState } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Button, Flex, Modal, Typography, theme, Divider } from 'antd'
|
||||
|
||||
import CloudIcon from '../../../Icons/CloudIcon'
|
||||
@ -66,16 +66,9 @@ const getStageColor = (status, token) => {
|
||||
return token.colorTextQuaternary
|
||||
}
|
||||
|
||||
const getDownloadStageStatus = (phase, isError) => {
|
||||
if (isError && ['preparing', 'downloading'].includes(phase)) return 'error'
|
||||
if (['downloaded', 'installing'].includes(phase)) return 'complete'
|
||||
if (['preparing', 'downloading'].includes(phase)) return 'active'
|
||||
return 'pending'
|
||||
}
|
||||
|
||||
const isInstallComplete = (phase, message) => {
|
||||
if (phase !== 'installing') return false
|
||||
const STAGE_INDEX = { download: 0, install: 1, restart: 2 }
|
||||
|
||||
const isCompletionMessage = (message) => {
|
||||
const normalized = String(message || '').toLowerCase()
|
||||
|
||||
return (
|
||||
@ -85,17 +78,33 @@ const isInstallComplete = (phase, message) => {
|
||||
)
|
||||
}
|
||||
|
||||
const getInstallStageStatus = (phase, isError, message) => {
|
||||
if (isError && ['downloaded', 'installing'].includes(phase)) return 'error'
|
||||
if (isInstallComplete(phase, message)) return 'complete'
|
||||
if (phase === 'installing') return 'active'
|
||||
return 'pending'
|
||||
// Maps a progress event to the stage that should currently be active.
|
||||
// Returns null when the event carries no stage information (e.g. errors),
|
||||
// so the previously reached stage is kept.
|
||||
const getStageIndexFromProgress = (phase, percent, message) => {
|
||||
if (phase === 'restarting') return STAGE_INDEX.restart
|
||||
|
||||
if (phase === 'installing') {
|
||||
return percent >= 100 || isCompletionMessage(message)
|
||||
? STAGE_INDEX.restart
|
||||
: STAGE_INDEX.install
|
||||
}
|
||||
|
||||
if (phase === 'downloaded') return STAGE_INDEX.install
|
||||
|
||||
if (phase === 'preparing' || phase === 'downloading') {
|
||||
return phase === 'downloading' && percent >= 100
|
||||
? STAGE_INDEX.install
|
||||
: STAGE_INDEX.download
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const getRestartStageStatus = (phase, isError, message) => {
|
||||
if (isError && isInstallComplete(phase, message)) return 'error'
|
||||
if (isInstallComplete(phase, message)) return 'active'
|
||||
return 'pending'
|
||||
const getStageStatus = (stageIndex, activeIndex, isError) => {
|
||||
if (stageIndex < activeIndex) return 'complete'
|
||||
if (stageIndex > activeIndex) return 'pending'
|
||||
return isError ? 'error' : 'active'
|
||||
}
|
||||
|
||||
const getProgressStatus = (stageStatus) => {
|
||||
@ -110,15 +119,13 @@ const UpdateStage = ({ stage, status, percent, detail }) => {
|
||||
const StageIcon = config.icon
|
||||
const resolvedPercent =
|
||||
typeof percent === 'number' ? Math.min(percent, 100) : undefined
|
||||
const resolvedStatus =
|
||||
status !== 'error' && resolvedPercent === 100 ? 'complete' : status
|
||||
const color = getStageColor(resolvedStatus, token)
|
||||
const showProgress = resolvedStatus === 'active' && stage !== 'restart'
|
||||
const color = getStageColor(status, token)
|
||||
const showProgress = status === 'active' && stage !== 'restart'
|
||||
|
||||
const StatusIcon =
|
||||
resolvedStatus === 'complete'
|
||||
status === 'complete'
|
||||
? CheckCircleIcon
|
||||
: resolvedStatus === 'error'
|
||||
: status === 'error'
|
||||
? XMarkCircleIcon
|
||||
: StageIcon
|
||||
|
||||
@ -126,16 +133,20 @@ const UpdateStage = ({ stage, status, percent, detail }) => {
|
||||
<Flex align='start' gap='middle' style={{ width: '100%' }}>
|
||||
<StatusIcon style={{ fontSize: 22, color, flexShrink: 0 }} />
|
||||
<Flex align='start' gap='24px' style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text style={{ flexShrink: 0 }}>{config.labels[resolvedStatus]}</Text>
|
||||
<Text style={{ flexShrink: 0 }}>{config.labels[status]}</Text>
|
||||
{showProgress && (
|
||||
<Flex vertical gap={2} style={{ flex: 1 }}>
|
||||
<Flex vertical gap={2} style={{ flex: 1, minWidth: 0 }}>
|
||||
<HProgress
|
||||
percent={resolvedPercent}
|
||||
status={getProgressStatus(resolvedStatus)}
|
||||
status={getProgressStatus(status)}
|
||||
showInfo={typeof resolvedPercent === 'number'}
|
||||
style={{ flex: 1, margin: 0 }}
|
||||
/>
|
||||
{detail && <Text type='secondary'>{detail}</Text>}
|
||||
{detail && (
|
||||
<Text type='secondary' ellipsis style={{ minWidth: 0 }}>
|
||||
{detail}
|
||||
</Text>
|
||||
)}
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
@ -164,21 +175,46 @@ const AppUpdateProgress = ({ progress, update, onClose }) => {
|
||||
|
||||
const [errorModalOpen, setErrorModalOpen] = useState(true)
|
||||
|
||||
const downloadStatus = getDownloadStageStatus(phase, isError)
|
||||
const installStatus = getInstallStageStatus(phase, isError, message)
|
||||
const restartStatus = getRestartStageStatus(phase, isError, message)
|
||||
// Track the furthest stage reached so out-of-order or skipped progress
|
||||
// events can never move the steps backwards.
|
||||
const [activeStageIndex, setActiveStageIndex] = useState(STAGE_INDEX.download)
|
||||
|
||||
useEffect(() => {
|
||||
const stageIndex = getStageIndexFromProgress(phase, percent, message)
|
||||
if (stageIndex !== null) {
|
||||
setActiveStageIndex((previous) => Math.max(previous, stageIndex))
|
||||
}
|
||||
}, [phase, percent, message])
|
||||
|
||||
const downloadStatus = getStageStatus(
|
||||
STAGE_INDEX.download,
|
||||
activeStageIndex,
|
||||
isError
|
||||
)
|
||||
const installStatus = getStageStatus(
|
||||
STAGE_INDEX.install,
|
||||
activeStageIndex,
|
||||
isError
|
||||
)
|
||||
const restartStatus = getStageStatus(
|
||||
STAGE_INDEX.restart,
|
||||
activeStageIndex,
|
||||
isError
|
||||
)
|
||||
|
||||
const downloadPercent =
|
||||
downloadStatus === 'active' ? (phase === 'preparing' ? 0 : percent) : null
|
||||
downloadStatus === 'active' && phase === 'downloading' ? percent : 0
|
||||
|
||||
const installPercent = installStatus === 'active' ? percent : null
|
||||
const installPercent =
|
||||
installStatus === 'active' && phase === 'installing' ? percent : null
|
||||
|
||||
const downloadDetail =
|
||||
downloadStatus === 'active' && downloaded && total
|
||||
? `${downloaded} of ${total}`
|
||||
: null
|
||||
|
||||
const installDetail = installStatus === 'active' ? message : null
|
||||
const installDetail =
|
||||
installStatus === 'active' && phase === 'installing' ? message : null
|
||||
|
||||
return (
|
||||
<Flex vertical gap='middle'>
|
||||
|
||||
@ -403,7 +403,7 @@ export const AppUpdateProvider = ({ children }) => {
|
||||
setNoUpdateOpen(false)
|
||||
setUpdatePromptOpen(false)
|
||||
setInstallingUpdate(update)
|
||||
setModelWidth(550)
|
||||
setModelWidth(580)
|
||||
setUpdateProgress({
|
||||
phase: 'preparing',
|
||||
percent: 0,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user