-
Notifications
You must be signed in to change notification settings - Fork 179
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(app, app-shell): add download progress to robot update flows #13915
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,12 +3,17 @@ | |
import path from 'path' | ||
import { promisify } from 'util' | ||
import tempy from 'tempy' | ||
import { move, readdir, remove } from 'fs-extra' | ||
import { move, readdir, remove, readFile } from 'fs-extra' | ||
import StreamZip from 'node-stream-zip' | ||
import getStream from 'get-stream' | ||
|
||
import { RobotUpdateTarget } from '@opentrons/app/src/redux/robot-update/types' | ||
|
||
import { createLogger } from '../log' | ||
import { fetchToFile } from '../http' | ||
import { Dispatch } from '../types' | ||
import { CURRENT_VERSION } from '../update' | ||
|
||
import type { DownloadProgress } from '../http' | ||
import type { ReleaseSetUrls, ReleaseSetFilepaths, UserFileInfo } from './types' | ||
|
||
|
@@ -23,6 +28,8 @@ | |
export function getReleaseFiles( | ||
urls: ReleaseSetUrls, | ||
directory: string, | ||
dispatch: Dispatch, | ||
target: RobotUpdateTarget, | ||
onProgress: (progress: DownloadProgress) => unknown | ||
): Promise<ReleaseSetFilepaths> { | ||
return readdir(directory) | ||
|
@@ -44,41 +51,65 @@ | |
return { system, releaseNotes } | ||
} | ||
|
||
return downloadReleaseFiles(urls, directory, onProgress) | ||
return Promise.all([ | ||
downloadAndNotify(true, urls.releaseNotes, directory, dispatch, target), | ||
downloadAndNotify( | ||
false, | ||
urls.system, | ||
directory, | ||
dispatch, | ||
target, | ||
onProgress | ||
), | ||
]).then(([releaseNotes, system]) => ({ releaseNotes, system })) | ||
Comment on lines
+54
to
+64
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If a user clicks an the robot update banner while the file is still downloading, there will not be release notes (as seen in the before vide). Let's separate the release notes from the system file download. If a user somehow navigates to the robot update banner before 20kb have been downloaded, they won't see the notes, but this won't prevent the download. |
||
}) | ||
} | ||
|
||
// downloads the entire release set to a temporary directory, and once they're | ||
// all successfully downloaded, renames the directory to `directory` | ||
// downloads robot update files to a temporary directory, and once | ||
// successfully downloaded, renames the directory to `directory` | ||
// TODO(mc, 2019-07-09): DRY this up if/when more than 2 files are required | ||
export function downloadReleaseFiles( | ||
urls: ReleaseSetUrls, | ||
export function downloadAndNotify( | ||
isReleaseNotesDownload: boolean, | ||
url: ReleaseSetUrls['releaseNotes' | 'system'], | ||
directory: string, | ||
dispatch: Dispatch, | ||
target: RobotUpdateTarget, | ||
// `onProgress` will be called with download progress as the files are read | ||
onProgress: (progress: DownloadProgress) => unknown | ||
): Promise<ReleaseSetFilepaths> { | ||
onProgress?: (progress: DownloadProgress) => unknown | ||
): Promise<string> { | ||
const tempDir: string = tempy.directory() | ||
const tempSystemPath = outPath(tempDir, urls.system) | ||
const tempNotesPath = outPath(tempDir, urls.releaseNotes) | ||
const tempPath = outPath(tempDir, url) | ||
const path = outPath(directory, tempPath) | ||
const logMessage = isReleaseNotesDownload ? 'release notes' : 'system files' | ||
|
||
log.debug('directory created for robot update downloads', { tempDir }) | ||
log.debug('directory created for ' + logMessage, { tempDir }) | ||
|
||
// downloads are streamed directly to the filesystem to avoid loading them | ||
// all into memory simultaneously | ||
const systemReq = fetchToFile(urls.system, tempSystemPath, { onProgress }) | ||
const notesReq = fetchToFile(urls.releaseNotes, tempNotesPath) | ||
|
||
return Promise.all([systemReq, notesReq]).then(results => { | ||
const [systemTemp, releaseNotesTemp] = results | ||
const systemPath = outPath(directory, systemTemp) | ||
const notesPath = outPath(directory, releaseNotesTemp) | ||
|
||
log.debug('renaming directory', { from: tempDir, to: directory }) | ||
const req = fetchToFile(url, tempPath, { | ||
onProgress, | ||
}) | ||
|
||
return move(tempDir, directory, { overwrite: true }).then(() => ({ | ||
system: systemPath, | ||
releaseNotes: notesPath, | ||
})) | ||
return req.then(() => { | ||
return move(tempPath, path, { overwrite: true }) | ||
.then(() => { | ||
if (isReleaseNotesDownload) { | ||
return readFile(path, 'utf8').then(releaseNotes => | ||
dispatch({ | ||
type: 'robotUpdate:UPDATE_INFO', | ||
payload: { releaseNotes, target, version: CURRENT_VERSION }, | ||
}) | ||
) | ||
} | ||
// This action will only have an effect if the user is actively waiting for the download to complete. | ||
else { | ||
return dispatch({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This dispatch occurs after the system files are downloaded but not yet cleaned up. The redux store will update the robot's session from "waiting on download to finish" to done if the robot is of the same version as the file that finished downloading. All the file cleanup can happen asynchronously without any problem. |
||
type: 'robotUpdate:DOWNLOAD_DONE', | ||
payload: target, | ||
}) | ||
} | ||
}) | ||
.then(() => path) | ||
}) | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
import * as React from 'react' | ||
import { useTranslation } from 'react-i18next' | ||
import { useDispatch } from 'react-redux' | ||
import { useDispatch, useSelector } from 'react-redux' | ||
import { css } from 'styled-components' | ||
|
||
import { | ||
|
@@ -23,11 +23,13 @@ | |
import { | ||
startRobotUpdate, | ||
clearRobotUpdateSession, | ||
getRobotUpdateDownloadError, | ||
} from '../../../../redux/robot-update' | ||
import { useRobotUpdateInfo } from './useRobotUpdateInfo' | ||
import successIcon from '../../../../assets/images/icon_success.png' | ||
|
||
import type { SetStatusBarCreateCommand } from '@opentrons/shared-data' | ||
import type { State } from '../../../../redux/types' | ||
import type { SetStatusBarCreateCommand } from '@opentrons/shared-data/protocol' | ||
import type { RobotUpdateSession } from '../../../../redux/robot-update/types' | ||
import type { UpdateStep } from './useRobotUpdateInfo' | ||
|
||
|
@@ -66,8 +68,15 @@ | |
const completeRobotUpdateHandler = (): void => { | ||
if (closeUpdateBuildroot != null) closeUpdateBuildroot() | ||
} | ||
const { error } = session || { error: null } | ||
const { updateStep, progressPercent } = useRobotUpdateInfo(session) | ||
|
||
const { updateStep, progressPercent } = useRobotUpdateInfo(robotName, session) | ||
|
||
let { error } = session || { error: null } | ||
const downloadError = useSelector((state: State) => | ||
getRobotUpdateDownloadError(state, robotName) | ||
) | ||
if (error == null && downloadError != null) error = downloadError | ||
|
||
useStatusBarAnimation(error != null) | ||
useCleanupRobotUpdateSessionOnDismount() | ||
|
||
|
@@ -89,11 +98,27 @@ | |
progressPercent | ||
) | ||
|
||
let modalBodyText = t('installing_update') | ||
let modalBodyText = '' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unless we can prove exhaustion in the if cases after this I think we should give this a meaningful default value
or even |
||
let subProgressBarText = t('do_not_turn_off') | ||
if (updateStep === 'restart') modalBodyText = t('restarting_robot') | ||
if (updateStep === 'restart' && letUserExitUpdate) { | ||
subProgressBarText = t('restart_taking_too_long', { robotName }) | ||
switch (updateStep) { | ||
case 'initial': | ||
case 'error': | ||
modalBodyText = '' | ||
break | ||
Check warning on line 107 in app/src/organisms/Devices/RobotSettings/UpdateBuildroot/RobotUpdateProgressModal.tsx Codecov / codecov/patchapp/src/organisms/Devices/RobotSettings/UpdateBuildroot/RobotUpdateProgressModal.tsx#L106-L107
|
||
case 'download': | ||
modalBodyText = t('downloading_update') | ||
break | ||
Check warning on line 110 in app/src/organisms/Devices/RobotSettings/UpdateBuildroot/RobotUpdateProgressModal.tsx Codecov / codecov/patchapp/src/organisms/Devices/RobotSettings/UpdateBuildroot/RobotUpdateProgressModal.tsx#L109-L110
|
||
case 'install': | ||
modalBodyText = t('installing_update') | ||
break | ||
case 'restart': | ||
modalBodyText = t('restarting_robot') | ||
if (letUserExitUpdate) { | ||
subProgressBarText = t('restart_taking_too_long', { robotName }) | ||
Check warning on line 117 in app/src/organisms/Devices/RobotSettings/UpdateBuildroot/RobotUpdateProgressModal.tsx Codecov / codecov/patchapp/src/organisms/Devices/RobotSettings/UpdateBuildroot/RobotUpdateProgressModal.tsx#L117
|
||
} | ||
break | ||
default: | ||
modalBodyText = t('installing_update') | ||
} | ||
|
||
return ( | ||
|
@@ -209,7 +234,7 @@ | |
export const TIME_BEFORE_ALLOWING_EXIT_MS = 600000 // 10 mins | ||
|
||
function useAllowExitIfUpdateStalled( | ||
updateStep: UpdateStep, | ||
updateStep: UpdateStep | null, | ||
progressPercent: number | ||
): boolean { | ||
const [letUserExitUpdate, setLetUserExitUpdate] = React.useState<boolean>( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This condition is hit if a user actively starts a robot update, but the file has not yet been downloaded. The CHECKING_FOR_UPDATE action creates an update session for the robot with a downloadFile stage. The UI layer uses this session to alert the user that the file is downloading.