Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libs/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,7 @@
"Stop": "Stop",
"Restart": "Restart",
"Open console": "Open console",
"The requested action failed": "The requested action failed",
"Expand row": "Expand row",
"Ready": "Ready",
"Restarts": "Restarts",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ const ApplicationLifecycleActions = ({
<Alert
isInline
variant="danger"
title={t('An error occurred')}
title={t('The requested action failed')}
isPlain
actionClose={<AlertActionCloseButton onClose={clearError} />}
>
Expand Down
21 changes: 20 additions & 1 deletion libs/ui-components/src/hooks/useApplicationLifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import * as React from 'react';

import type { ApplicationStatusType, Device } from '@flightctl/types';

import { type ApplicationLifecycleAction, shouldClearPendingLifecycleAction } from '../utils/applicationLifecycle';
import {
type ApplicationLifecycleAction,
RESTART_PENDING_TIMEOUT_MS,
shouldClearPendingLifecycleAction,
} from '../utils/applicationLifecycle';
import { getErrorMessage } from '../utils/error';
import { useFetch } from './useFetch';

Expand Down Expand Up @@ -57,6 +61,21 @@ export const useApplicationLifecycle = ({
}
}, [appRestarts, appStatus, pendingAction]);

React.useEffect(() => {
if (pendingAction !== 'restart') {
return;
}

// Restarting a running application does not produce an observable change in status.
// If the action was successfully submitted, it is considered finished after a short timeout.
const timer = window.setTimeout(() => {
setPendingAction(null);
restartsAtRequestRef.current = undefined;
}, RESTART_PENDING_TIMEOUT_MS);

return () => window.clearTimeout(timer);
}, [pendingAction]);

const executeAction = React.useCallback(
async (action: ApplicationLifecycleAction) => {
setIsSubmitting(true);
Expand Down
10 changes: 9 additions & 1 deletion libs/ui-components/src/utils/applicationLifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,12 @@ export const getDeviceAppLifecycleOverrides = (
return mergeApplicationLifecycleLayers(fleetOverrides, deviceOverrides);
};

/**
* The "status.apps[].restarts" field is only modified on "real" app restarts/crashes, not by successful restarts.
* Successfully restarting an application is not observable through status, and the action is cleared after a timeout.
*/
export const RESTART_PENDING_TIMEOUT_MS = 500;

export const shouldClearPendingLifecycleAction = (
pendingAction: ApplicationLifecycleAction,
currentStatus: ApplicationStatusType,
Expand All @@ -168,11 +174,13 @@ export const shouldClearPendingLifecycleAction = (

case 'start':
return (
currentStatus === ApplicationStatusType.ApplicationStatusStarting ||
currentStatus === ApplicationStatusType.ApplicationStatusCompleted ||
currentStatus === ApplicationStatusType.ApplicationStatusRunning ||
currentStatus === ApplicationStatusType.ApplicationStatusError
);
case 'restart':
// The following conditions to clearing pending "restart" actions may not always eventually be satisfied.
// See RESTART_PENDING_TIMEOUT_MS for more details.
return (
currentStatus === ApplicationStatusType.ApplicationStatusStarting ||
currentStatus !== statusAtRequest ||
Expand Down
Loading