Skip to content

Commit

Permalink
improvement(firmware): add firmware upgrade progress bar
Browse files Browse the repository at this point in the history
  • Loading branch information
germanferrero committed Aug 10, 2020
1 parent 269ea70 commit 3f6959e
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 45 deletions.
5 changes: 3 additions & 2 deletions .i18nrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"out": "i18n/generic.json"
}
"out": "i18n/generic.json",
"directories": ["src", "plugins"]
}
9 changes: 6 additions & 3 deletions i18n/generic.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@
"please_configure_your_network_d6eb8b76": "Please configure your network",
"please_verify_that_the_image_is_for_the_target_dev_7200f22e": "Please verify that the image is for the target device and that you trust its origin",
"please_wait_62914c7c": "Please wait",
"please_wait_while_the_device_reboots_and_reload_th_a6d06bb4": "Please wait while the device reboots and reload the app",
"please_wait_patiently_for_seconds_seconds_and_do_n_b98cfb66": "Please wait patiently for %{seconds} seconds and do not disconnect the device.",
"please_wait_while_the_device_reboots_and_reload_th_67bd290d": "Please wait while the device reboots, and reload the app",
"preserve_config_94b340cf": "Preserve config",
"re_enter_password_49757ed": "Re-enter Password",
"re_enter_the_shared_password_20f09406": "Re-enter the shared password",
Expand Down Expand Up @@ -98,12 +99,12 @@
"stations_18122820": "Stations",
"status_e7fdbe06": "Status",
"system_55b0ca91": "System",
"the_device_will_reboot_you_may_need_to_connect_to__8285b1a3": "The device will reboot, you may need to connect to the new wireless network and reload the app",
"the_firmware_is_being_upgraded_f3881802": "The firmware is being upgraded...",
"the_password_should_have_b9f88155": "The password should have:",
"the_passwords_do_not_match_62d77c67": "The passwords do not match!",
"the_selected_image_is_not_a_valid_for_the_target_d_c5edf208": "The selected image is not a valid for the target device",
"the_shared_password_has_been_chosen_by_the_communi_f9d30a92": "The shared password has been chosen by the community when the network was created. You can ask other community members for it.",
"the_upgrade_is_done_232faea7": "The upgrade is done",
"the_upgrade_should_be_done_d66854": "The upgrade should be done",
"this_device_does_not_support_secure_rollback_to_pr_1c167a2c": "This device does not support secure rollback to previous version if something goes wrong",
"this_device_supports_secure_rollback_to_previous_v_a60ddbcb": "This device supports secure rollback to previous version if something goes wrong",
"this_node_does_not_see_other_nodes_in_the_network_f281b047": "This node does not see other nodes in the network.",
Expand All @@ -112,6 +113,7 @@
"to_keep_the_current_configuration_or_ab76f6d1": "to keep the current configuration. Or ...",
"to_the_previous_configuration_bf087867": "to the previous configuration",
"traffic_bfe536d2": "Traffic",
"try_reloading_the_app_4e4c3a66": "Try reloading the app",
"un_unexpected_error_occurred_please_contact_the_de_b9eb7c49": "Un unexpected error occurred, please contact the developer team",
"upgrade_5de364f8": "Upgrade",
"upload_firmware_image_from_your_device_57327bee": "Upload firmware image from your device",
Expand All @@ -122,6 +124,7 @@
"you_can_search_for_mesh_networks_around_you_to_add_e6fbf1c5": "You can search for mesh networks around you to add or to create a new one.",
"you_don_t_go_through_any_paths_to_get_here_25203ed3": "You don't go through any paths to get here.",
"you_have_successfuly_connected_to_ddb8c613": "You have successfuly connected to",
"you_may_need_to_connect_to_the_new_wireless_networ_e931e438": "You may need to connect to the new wireless network and reload the app",
"you_need_to_know_the_shared_password_to_enter_this_4b0c4ec1": "You need to know the shared password to enter this page",
"you_should_try_to_connect_to_the_network_network_8d7f515e": "You should try to connect to the network %{network}.",
"your_router_has_not_yet_been_configured_you_can_us_27c91373": "Your router has not yet been configured, \n\t\t\tyou can use our wizard to incorporate it into an existing network or create a new one.\n\t\t\tIf you ignore this message it will continue to work with the default configuration."
Expand Down
11 changes: 7 additions & 4 deletions i18n/translations/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,10 @@
"reload_3e45154f": "Recargar",
"firmware_6a098a0d": "Firmware",
"please_verify_that_the_image_is_for_the_target_dev_7200f22e": "Por favor, verificá que la imagen sea para este dispositivo y que confiás en su origen",
"please_wait_while_the_device_reboots_and_reload_th_a6d06bb4": "Por favor, esperá a que el equipo se reinicie y recargá la aplicación",
"preserve_config_94b340cf": "Preservar configuración",
"select_file_71aa4113": "Seleccioná el archivo",
"the_device_will_reboot_you_may_need_to_connect_to__8285b1a3": "El equipo se reiniciará, es posible que debas conectarte a la nueva red wifi y recargar la aplicación",
"you_may_need_to_connect_to_the_new_wireless_networ_e931e438": "Es posible que debas conectarte a la nueva red wifi y recargar la aplicación",
"the_selected_image_is_not_a_valid_for_the_target_d_c5edf208": "La imagen seleccionada no es válida para este dispositivo",
"the_upgrade_is_done_232faea7": "La actualización se realizó",
"this_device_does_not_support_secure_rollback_to_pr_1c167a2c": "En este dispositivo no se puede revertir la actualización en caso de errores",
"this_device_supports_secure_rollback_to_previous_v_a60ddbcb": "En este dispositivo se puede revertir la actualización en caso de errores",
"upgrade_5de364f8": "Actualizar",
Expand All @@ -129,6 +127,11 @@
"reverting_to_previous_version_e6e43529": "Volviendo a la versión anterior",
"size_b30e1077": "Tamaño",
"to_keep_the_current_configuration_or_ab76f6d1":"La actualización, o...",
"to_the_previous_configuration_bf087867":"a la versión anterior"
"to_the_previous_configuration_bf087867":"a la versión anterior",
"please_wait_patiently_for_seconds_seconds_and_do_n_b98cfb66": "Por favor esperá %{seconds} segundos pacientemente y no desconectes el router",
"please_wait_while_the_device_reboots_and_reload_th_67bd290d": "Por favor esperá a que el dispositivo se reinicie y recargá la app",
"the_firmware_is_being_upgraded_f3881802": "La actualización se está realizando",
"the_upgrade_should_be_done_d66854": "La actualización debiera estar lista",
"try_reloading_the_app_4e4c3a66": "Intentar recargar la app"
}

51 changes: 41 additions & 10 deletions plugins/lime-plugin-firmware/firmware.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { h } from 'preact';
import { render, fireEvent, cleanup } from '@testing-library/preact';
import { render, fireEvent, cleanup, act, screen } from '@testing-library/preact';
import '@testing-library/jest-dom';
import waitForExpect from 'wait-for-expect';

Expand Down Expand Up @@ -41,8 +41,11 @@ function triggerUpgrade(getByLabelText, getByRole, preserveConfig=false) {
}

describe('firmware form', () => {
beforeAll(() => {
jest.useFakeTimers();
})

beforeEach(() => {
// Reset default mock implementations
upgradeConfirmIsAvailable.mockImplementation(jest.fn(async () => true));
uploadFile.mockImplementation(jest.fn(async () => true));
validateFirmware.mockImplementation(jest.fn(async () => true));
Expand All @@ -53,6 +56,10 @@ describe('firmware form', () => {
afterEach(() => {
cleanup();
});

afterAll(() => {
jest.useRealTimers();
})

it('shows up a legend telling confirm-mechanism is available when it is', async () => {
upgradeConfirmIsAvailable.mockImplementation(async () => true);
Expand Down Expand Up @@ -146,27 +153,51 @@ describe('firmware form', () => {
})
});

it('shows up a legend asking the user to wait for reboot after upgrade, preserve config', async () => {
it.each`preserveConfig ${true} ${false}
`('shows up a legend asking the user to wait for the upgrade to finish preserveCongif: $preserveConfig', async ({preserveConfig}) => {
uploadFile.mockImplementation(async () => true);
validateFirmware.mockImplementation(async () => true);
upgradeFirmware.mockImplementation(async () => true);
const { findByText, getByLabelText, getByRole} = render(<FirmwarePage />);
triggerUpgrade(getByLabelText, getByRole, preserveConfig);
const noteText1 = new RegExp(
'The firmware is being upgraded...', 'i');
expect(await findByText(noteText1)).toBeInTheDocument();
const noteText2 = new RegExp(
'Please wait patienly for .* seconds and do not disconnect the device', 'i');
expect(await findByText(noteText2)).toBeInTheDocument();
});

it('shows a button to reload app after upgrade countdown when preserving config', async () => {
uploadFile.mockImplementation(async () => true);
validateFirmware.mockImplementation(async () => true);
upgradeFirmware.mockImplementation(async () => true);
const { getByLabelText, getByRole, findByRole, findByText} = render(<FirmwarePage />);
const preserveConfig = true;
triggerUpgrade(getByLabelText, getByRole, preserveConfig);
const noteText = new RegExp('Please wait while the device reboots and reload the app', 'i');
expect(await findByText(noteText)).toBeInTheDocument();
const noteText1 = new RegExp(
'The firmware is being upgraded...', 'i');
await findByText(noteText1);
act(() => {
jest.advanceTimersByTime(181 * 1000)
})
expect(await findByRole('button', {name: /try reloading the app/i})).toBeEnabled()
});

it('shows up a legend asking the user to wait for reboot after upgrade, not preserve config', async () => {
it('shows a legend saying the user may switch network after upgrade when not preserving config', async () => {
uploadFile.mockImplementation(async () => true);
validateFirmware.mockImplementation(async () => true);
upgradeFirmware.mockImplementation(async () => true);
const { findByText, getByLabelText, getByRole} = render(<FirmwarePage />);
const { getByLabelText, getByRole, findByText} = render(<FirmwarePage />);
const preserveConfig = false;
triggerUpgrade(getByLabelText, getByRole, preserveConfig);
const noteText = new RegExp(
'The device will reboot, you may need to connect to the new wireless network', 'i');
expect(await findByText(noteText)).toBeInTheDocument();
const noteText1 = new RegExp(
'The firmware is being upgraded...', 'i');
await findByText(noteText1);
act(() => {
jest.advanceTimersByTime(181 * 1000)
})
expect(await findByText('You may need to connect to the new wireless network and reload the app')).toBeInTheDocument()
});
});

Expand Down
10 changes: 8 additions & 2 deletions plugins/lime-plugin-firmware/firmware.stories.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { UpgradeConfirm, UpgradeForm, UpgradeSuccess, UpgradeReverted } from './src/firmwarePage';
import { UpgradeConfirm, UpgradeForm, UpgradeSuccess, UpgradeProgress, UpgradeReverted } from './src/firmwarePage';
import { SafeUpgradeCountdown } from './src/upgradeCountdown';
import { action } from '@storybook/addon-actions';
import { withKnobs, number } from '@storybook/addon-knobs';

const formActions = {
onUpgrade: action('onUpgrade'),
Expand All @@ -9,7 +10,8 @@ const formActions = {


export default {
title: 'Containers|Firmware Upgrade'
title: 'Containers|Firmware Upgrade',
decorators: [withKnobs]
};

export const SafeUpgradeIsAvailable = () => (
Expand All @@ -32,6 +34,10 @@ export const SuccessfullUpgradeNotPreservingConfig = () => (
<UpgradeSuccess preserveConfig={false} />
);

export const upgradeProgress = () => (
<UpgradeProgress elapsedTime={number('elapsedTime', 60)} totalTime={number('totalTime', 180)} />
)

export const safeUpgradeCountdown = () => (
<SafeUpgradeCountdown counter={600} />
)
Expand Down
65 changes: 47 additions & 18 deletions plugins/lime-plugin-firmware/src/firmwarePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import { useState, useEffect } from 'preact/hooks';
import { upgradeConfirmIsAvailable, uploadFile, validateFirmware,
upgradeFirmware, upgradeConfirm, upgradeRevert} from './firmwareApi';
import { useAppContext } from '../../../src/utils/app.context';
import { route } from 'preact-router';
import ProgressBar from '../../../src/components/progressbar';

import { route } from 'preact-router';

const secureRollbackText = I18n.t(
'This device supports secure rollback to previous version if something goes wrong');
Expand All @@ -19,13 +20,11 @@ const validationErrorText = I18n.t(
'The selected image is not a valid for the target device'
);

const upgradeSuccessTextPreserveConfig = I18n.t(
'Please wait while the device reboots and reload the app'
const isUpgradingText = seconds => I18n.t(
'Please wait patiently for %{seconds} seconds and do not disconnect the device.', {seconds}
);

const upgradeSuccessTextNotPreserveConfig = I18n.t(
'The device will reboot, you may need to connect to the new wireless network and reload the app'
);
const afterUpgradeNotPreserveConfigText = I18n.t('You may need to connect to the new wireless network and reload the app');

export const UpgradeConfirm = ({onConfirm, onRevert}) => (
<div class={`container container-padded container-center`}>
Expand All @@ -39,7 +38,7 @@ export const UpgradeConfirm = ({onConfirm, onRevert}) => (
export const UpgradeReverted = () => (
<div class={`container container-padded container-center`}>
<h3>{I18n.t('Reverting to previous version')}</h3>
<span>{upgradeSuccessTextPreserveConfig}</span>
<span>{I18n.t('Please wait while the device reboots, and reload the app')}</span>
</div>
);

Expand All @@ -64,20 +63,50 @@ const _UpgradeConfirm = () => {
return <UpgradeConfirm onConfirm={onConfirm} onRevert={onRevert} />
}

export const UpgradeSuccess = ({preserveConfig}) => (
<div class={`container container-padded container-center`}>
export const UpgradeSuccess = ({ preserveConfig, onReload } ) => (
<div class="container container-padded container-center">
<h3>
{I18n.t('The upgrade is done')}
{I18n.t('The upgrade should be done')}
</h3>
{ preserveConfig &&
<span>{upgradeSuccessTextPreserveConfig}</span>
{preserveConfig &&
<button onClick={onReload}>{I18n.t('Try reloading the app')}</button>
}
{!(preserveConfig) &&
<span>{upgradeSuccessTextNotPreserveConfig}</span>
<span>{afterUpgradeNotPreserveConfigText}</span>
}
</div>
);
)
export const UpgradeProgress = ({elapsedTime, totalTime}) => {
const remainingTime = totalTime - elapsedTime;
const progress = elapsedTime / totalTime * 100;
return (
<div class="container container-padded container-center">
<h3>
{I18n.t('The firmware is being upgraded...')}
</h3>
<ProgressBar progress={progress} />
<span>{isUpgradingText(remainingTime)}</span>
</div>
)
};

const _UpgradeSubmitted = ({ preserveConfig }) => {
const totalTime = 180;
const [elapsedTime, setElapsedTime] = useState(0);

useEffect(() => {
const id = setInterval(() => setElapsedTime(elapsedTime => elapsedTime + 1), 1000)
return () => {
clearInterval(id);
}
}, [elapsedTime, setElapsedTime])


if (elapsedTime < totalTime) {
return <UpgradeProgress elapsedTime={elapsedTime} totalTime={totalTime} />
}
return <UpgradeSuccess preserveConfig={preserveConfig} />
}

export const UpgradeForm = ({
upgradeConfirmAvailable,
Expand Down Expand Up @@ -157,7 +186,7 @@ const _UpgradeForm = () => {
const { uhttpdService } = useAppContext();
const [upgradeConfirmAvailable, setUpgradeConfirmAvailable] = useState(undefined);
const [firmwareIsValid, setFirmwareIsValid] = useState(undefined);
const [upgradeSuccess, setUpgradeSuccess] = useState(undefined);
const [submitSuccess, setSubmitSucces] = useState(undefined);
const [preserveConfig, setpreserveConfig] = useState(false);
const [fileName, setFilename] = useState('');
const fileInputRef = createRef();
Expand All @@ -182,7 +211,7 @@ const _UpgradeForm = () => {
uploadFile(uhttpdService, file)
.then(_validateFirmware)
.then(() => upgradeFirmware(uhttpdService, preserveConfig))
.then(() => setUpgradeSuccess(true))
.then(() => setSubmitSucces(true))
.catch(error => {
switch (error) {
case 'validation':
Expand All @@ -194,8 +223,8 @@ const _UpgradeForm = () => {
})
}

if (upgradeSuccess) {
return <UpgradeSuccess preserveConfig={preserveConfig} />
if (submitSuccess) {
return <_UpgradeSubmitted preserveConfig={preserveConfig} />
}

return <UpgradeForm {...{upgradeConfirmAvailable, firmwareIsValid,
Expand Down
8 changes: 3 additions & 5 deletions src/components/progressbar/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { h } from 'preact';
import style from './style';
import style from './style.less';

const ProgressBar = ({ color = '#38927f', progress = 0 }) => (
<div>
<div className={style.wrapper}>
<div className={style.progress} style={{ backgroundColor: color, width: `${progress}%` }} />
</div>
<div className={style.wrapper}>
<div className={style.progress} style={{ backgroundColor: color, width: `${progress}%` }} />
</div>
);

Expand Down
3 changes: 2 additions & 1 deletion src/components/progressbar/style.less
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
width: 100%;
height: 15px;
border: 1px solid rgba(0,0,0,0.5);
margin-bottom: 0.2em;
}

.progress {
width: 0;
height: 11px;
margin-top: 1px;
}
}

0 comments on commit 3f6959e

Please sign in to comment.