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 PropTypes from 'prop-types'
|
||||||
import { useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { Button, Flex, Modal, Typography, theme, Divider } from 'antd'
|
import { Button, Flex, Modal, Typography, theme, Divider } from 'antd'
|
||||||
|
|
||||||
import CloudIcon from '../../../Icons/CloudIcon'
|
import CloudIcon from '../../../Icons/CloudIcon'
|
||||||
@ -66,16 +66,9 @@ const getStageColor = (status, token) => {
|
|||||||
return token.colorTextQuaternary
|
return token.colorTextQuaternary
|
||||||
}
|
}
|
||||||
|
|
||||||
const getDownloadStageStatus = (phase, isError) => {
|
const STAGE_INDEX = { download: 0, install: 1, restart: 2 }
|
||||||
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 isCompletionMessage = (message) => {
|
||||||
const normalized = String(message || '').toLowerCase()
|
const normalized = String(message || '').toLowerCase()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -85,17 +78,33 @@ const isInstallComplete = (phase, message) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getInstallStageStatus = (phase, isError, message) => {
|
// Maps a progress event to the stage that should currently be active.
|
||||||
if (isError && ['downloaded', 'installing'].includes(phase)) return 'error'
|
// Returns null when the event carries no stage information (e.g. errors),
|
||||||
if (isInstallComplete(phase, message)) return 'complete'
|
// so the previously reached stage is kept.
|
||||||
if (phase === 'installing') return 'active'
|
const getStageIndexFromProgress = (phase, percent, message) => {
|
||||||
return 'pending'
|
if (phase === 'restarting') return STAGE_INDEX.restart
|
||||||
|
|
||||||
|
if (phase === 'installing') {
|
||||||
|
return percent >= 100 || isCompletionMessage(message)
|
||||||
|
? STAGE_INDEX.restart
|
||||||
|
: STAGE_INDEX.install
|
||||||
}
|
}
|
||||||
|
|
||||||
const getRestartStageStatus = (phase, isError, message) => {
|
if (phase === 'downloaded') return STAGE_INDEX.install
|
||||||
if (isError && isInstallComplete(phase, message)) return 'error'
|
|
||||||
if (isInstallComplete(phase, message)) return 'active'
|
if (phase === 'preparing' || phase === 'downloading') {
|
||||||
return 'pending'
|
return phase === 'downloading' && percent >= 100
|
||||||
|
? STAGE_INDEX.install
|
||||||
|
: STAGE_INDEX.download
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStageStatus = (stageIndex, activeIndex, isError) => {
|
||||||
|
if (stageIndex < activeIndex) return 'complete'
|
||||||
|
if (stageIndex > activeIndex) return 'pending'
|
||||||
|
return isError ? 'error' : 'active'
|
||||||
}
|
}
|
||||||
|
|
||||||
const getProgressStatus = (stageStatus) => {
|
const getProgressStatus = (stageStatus) => {
|
||||||
@ -110,15 +119,13 @@ const UpdateStage = ({ stage, status, percent, detail }) => {
|
|||||||
const StageIcon = config.icon
|
const StageIcon = config.icon
|
||||||
const resolvedPercent =
|
const resolvedPercent =
|
||||||
typeof percent === 'number' ? Math.min(percent, 100) : undefined
|
typeof percent === 'number' ? Math.min(percent, 100) : undefined
|
||||||
const resolvedStatus =
|
const color = getStageColor(status, token)
|
||||||
status !== 'error' && resolvedPercent === 100 ? 'complete' : status
|
const showProgress = status === 'active' && stage !== 'restart'
|
||||||
const color = getStageColor(resolvedStatus, token)
|
|
||||||
const showProgress = resolvedStatus === 'active' && stage !== 'restart'
|
|
||||||
|
|
||||||
const StatusIcon =
|
const StatusIcon =
|
||||||
resolvedStatus === 'complete'
|
status === 'complete'
|
||||||
? CheckCircleIcon
|
? CheckCircleIcon
|
||||||
: resolvedStatus === 'error'
|
: status === 'error'
|
||||||
? XMarkCircleIcon
|
? XMarkCircleIcon
|
||||||
: StageIcon
|
: StageIcon
|
||||||
|
|
||||||
@ -126,16 +133,20 @@ const UpdateStage = ({ stage, status, percent, detail }) => {
|
|||||||
<Flex align='start' gap='middle' style={{ width: '100%' }}>
|
<Flex align='start' gap='middle' style={{ width: '100%' }}>
|
||||||
<StatusIcon style={{ fontSize: 22, color, flexShrink: 0 }} />
|
<StatusIcon style={{ fontSize: 22, color, flexShrink: 0 }} />
|
||||||
<Flex align='start' gap='24px' style={{ flex: 1, minWidth: 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 && (
|
{showProgress && (
|
||||||
<Flex vertical gap={2} style={{ flex: 1 }}>
|
<Flex vertical gap={2} style={{ flex: 1, minWidth: 0 }}>
|
||||||
<HProgress
|
<HProgress
|
||||||
percent={resolvedPercent}
|
percent={resolvedPercent}
|
||||||
status={getProgressStatus(resolvedStatus)}
|
status={getProgressStatus(status)}
|
||||||
showInfo={typeof resolvedPercent === 'number'}
|
showInfo={typeof resolvedPercent === 'number'}
|
||||||
style={{ flex: 1, margin: 0 }}
|
style={{ flex: 1, margin: 0 }}
|
||||||
/>
|
/>
|
||||||
{detail && <Text type='secondary'>{detail}</Text>}
|
{detail && (
|
||||||
|
<Text type='secondary' ellipsis style={{ minWidth: 0 }}>
|
||||||
|
{detail}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
@ -164,21 +175,46 @@ const AppUpdateProgress = ({ progress, update, onClose }) => {
|
|||||||
|
|
||||||
const [errorModalOpen, setErrorModalOpen] = useState(true)
|
const [errorModalOpen, setErrorModalOpen] = useState(true)
|
||||||
|
|
||||||
const downloadStatus = getDownloadStageStatus(phase, isError)
|
// Track the furthest stage reached so out-of-order or skipped progress
|
||||||
const installStatus = getInstallStageStatus(phase, isError, message)
|
// events can never move the steps backwards.
|
||||||
const restartStatus = getRestartStageStatus(phase, isError, message)
|
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 =
|
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 =
|
const downloadDetail =
|
||||||
downloadStatus === 'active' && downloaded && total
|
downloadStatus === 'active' && downloaded && total
|
||||||
? `${downloaded} of ${total}`
|
? `${downloaded} of ${total}`
|
||||||
: null
|
: null
|
||||||
|
|
||||||
const installDetail = installStatus === 'active' ? message : null
|
const installDetail =
|
||||||
|
installStatus === 'active' && phase === 'installing' ? message : null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex vertical gap='middle'>
|
<Flex vertical gap='middle'>
|
||||||
|
|||||||
@ -403,7 +403,7 @@ export const AppUpdateProvider = ({ children }) => {
|
|||||||
setNoUpdateOpen(false)
|
setNoUpdateOpen(false)
|
||||||
setUpdatePromptOpen(false)
|
setUpdatePromptOpen(false)
|
||||||
setInstallingUpdate(update)
|
setInstallingUpdate(update)
|
||||||
setModelWidth(550)
|
setModelWidth(580)
|
||||||
setUpdateProgress({
|
setUpdateProgress({
|
||||||
phase: 'preparing',
|
phase: 'preparing',
|
||||||
percent: 0,
|
percent: 0,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user