= ({
+ failingCondition,
+ progressingCondition,
+ hasOperatorProblems,
+ message,
+ rawFailureMessage,
+ currentVersion,
+ desiredVersion,
+ showPreCheck,
+ cv,
+ t,
+}) => {
+ const hasFailures = !!failingCondition || hasOperatorProblems;
+ const isProgressing = !!progressingCondition;
+
+ // Memoize expensive operations
+ const hasUpdates = useMemo(() => hasAvailableUpdates(cv), [cv]);
+ const availableUpdates = useMemo(() => getSortedAvailableUpdates(cv), [cv]);
+
+ const updatesDisplayText = useMemo(() => {
+ if (!hasUpdates) {
+ return t('public~Cluster {{currentVersion}} - Up to Date', { currentVersion });
+ }
+
+ if (availableUpdates.length === 1) {
+ return t('public~Update Available: {{updateVersion}}', {
+ currentVersion,
+ updateVersion: availableUpdates[0]?.version,
+ });
+ }
+
+ if (availableUpdates.length > 1) {
+ return t('public~Available Updates (latest: {{latestVersion}})', {
+ currentVersion,
+ latestVersion: availableUpdates[0]?.version,
+ });
+ }
+ return '';
+ }, [hasUpdates, availableUpdates, currentVersion, t]);
+
+ if (hasFailures && message) {
+ return (
+ <>
+ {message}
+ {rawFailureMessage && rawFailureMessage !== message && (
+
+
+ {t('public~View technical details')}
+
+
+
+
+
+ )}
+ >
+ );
+ }
+
+ if (isProgressing) {
+ return (
+ <>
+
+ {currentVersion !== desiredVersion
+ ? t('public~Updating from {{currentVersion}} to {{desiredVersion}}', {
+ currentVersion,
+ desiredVersion,
+ })
+ : t('public~Update is in progress')}
+
+
+ {t('public~Need help understanding the progress?')}
+
+ >
+ );
+ }
+
+ if (showPreCheck) {
+ return (
+ <>
+ {updatesDisplayText}
+
+ {hasUpdates
+ ? t('public~Check cluster health and update prerequisites.')
+ : t('public~Verify cluster health and operational status.')}
+
+ >
+ );
+ }
+
+ return null;
+};
+
+export const UpdateAssessmentCard: FC<{
+ cv: ClusterVersionKind;
+ clusterOperators?: ClusterOperator[];
+}> = ({ cv, clusterOperators }) => {
+ const { t } = useTranslation();
+ const isOLSAvailable = useFlag('LIGHTSPEED_CONSOLE');
+ const [assessmentExpanded, setAssessmentExpanded] = useState(true);
+
+ // Memoize expensive computations (call all hooks before any returns)
+ const conditions = useMemo(() => cv.status?.conditions || [], [cv.status?.conditions]);
+ const currentVersion = useMemo(() => getLastCompletedUpdate(cv), [cv]);
+ const desiredVersion = useMemo(() => getDesiredClusterVersion(cv), [cv]);
+
+ // Check cluster and operator conditions for alert display
+ const progressingCondition = useMemo(
+ () => conditions.find((c) => c.type === 'Progressing' && c.status === 'True'),
+ [conditions],
+ );
+ const failingCondition = useMemo(
+ () => conditions.find((c) => c.type === 'Failing' && c.status === 'True'),
+ [conditions],
+ );
+ const hasOperatorProblems = useMemo(() => hasOperatorIssues(clusterOperators), [
+ clusterOperators,
+ ]);
+
+ // Determine button visibility using the new unified logic
+ const { showStatus, showPreCheck } = useMemo(
+ () => determineWorkflowButtons(cv, clusterOperators),
+ [cv, clusterOperators],
+ );
+
+ // Get failure details for display when issues exist
+ const releaseAccepted = useMemo(
+ () => getConditionOfType(cv, ClusterVersionConditionType.ReleaseAccepted),
+ [cv],
+ );
+ const retrievedUpdates = useMemo(
+ () => getConditionOfType(cv, ClusterVersionConditionType.RetrievedUpdates),
+ [cv],
+ );
+ const invalid = useMemo(() => getConditionOfType(cv, ClusterVersionConditionType.Invalid), [cv]);
+
+ const rawFailureMessage = useMemo(
+ () =>
+ failingCondition?.message ||
+ releaseAccepted?.message ||
+ retrievedUpdates?.message ||
+ invalid?.message ||
+ '',
+ [
+ failingCondition?.message,
+ releaseAccepted?.message,
+ retrievedUpdates?.message,
+ invalid?.message,
+ ],
+ );
+
+ const { message } = useMemo(
+ () => parseUpdateFailureMessage(rawFailureMessage, t, cv, clusterOperators),
+ [rawFailureMessage, t, cv, clusterOperators],
+ );
+
+ // Memoize alert title determination
+ const alertTitle = useMemo(() => {
+ const hasFailures = !!failingCondition || hasOperatorProblems;
+ const isProgressing = !!progressingCondition;
+
+ if (hasFailures && isProgressing) {
+ return t('public~Update issues detected');
+ }
+ if (hasFailures) {
+ return t('public~Cluster issues detected');
+ }
+ if (isProgressing) {
+ return t('public~Cluster updating');
+ }
+ if (showPreCheck) {
+ return t('public~Cluster health');
+ }
+ return t('public~Cluster status');
+ }, [failingCondition, hasOperatorProblems, progressingCondition, showPreCheck, t]);
+
+ // Don't render if OLS is not available
+ if (!isOLSAvailable) {
+ return null;
+ }
+
+ // Don't render if no buttons should show
+ if (!showPreCheck && !showStatus) {
+ return null;
+ }
+
+ return (
+
+ setAssessmentExpanded(!assessmentExpanded)}
+ toggleButtonProps={{
+ id: 'update-assessment-toggle',
+ 'aria-expanded': assessmentExpanded,
+ }}
+ >
+ {t('public~AI Assessment')}
+
+
+
+ }
+ isInline
+ title={alertTitle}
+ className="pf-v6-u-background-color-purple-100 pf-v6-u-border-color-purple-200"
+ actionLinks={
+ (showPreCheck || showStatus) && (
+
+ {/* Pre-check button: appears when cluster is healthy and ready for updates */}
+ {showPreCheck && (
+
+ )}
+ {/* Status button: appears when cluster is progressing or has issues */}
+ {showStatus && (
+
+ )}
+
+ )
+ }
+ >
+
+
+
+
+
+ );
+};
+
+export const PreCheckCard: FC<{ cv: ClusterVersionKind }> = ({ cv }) => {
+ const { t } = useTranslation();
+ const isOLSAvailable = useFlag('LIGHTSPEED_CONSOLE');
+ const [preCheckExpanded, setPreCheckExpanded] = useState(true);
+
+ // Memoize expensive computations (call all hooks before any returns)
+ const currentVersion = useMemo(() => getLastCompletedUpdate(cv), [cv]);
+ const hasUpdates = useMemo(() => hasAvailableUpdates(cv), [cv]);
+ const availableUpdates = useMemo(() => getSortedAvailableUpdates(cv), [cv]);
+
+ const updatesDisplayText = useMemo(() => {
+ if (!hasUpdates) {
+ return t('public~Cluster {{currentVersion}} - Up to Date', { currentVersion });
+ }
+
+ if (availableUpdates.length === 1) {
+ return t('public~Update Available: {{updateVersion}}', {
+ currentVersion,
+ updateVersion: availableUpdates[0]?.version,
+ });
+ }
+
+ if (availableUpdates.length > 1) {
+ return t('public~Available Updates (latest: {{latestVersion}})', {
+ currentVersion,
+ latestVersion: availableUpdates[0]?.version,
+ });
+ }
+ return '';
+ }, [hasUpdates, availableUpdates, currentVersion, t]);
+
+ // Don't render if OLS is not available
+ if (!isOLSAvailable) {
+ return null;
+ }
+
+ return (
+
+ setPreCheckExpanded(!preCheckExpanded)}
+ toggleButtonProps={{
+ id: 'precheck-toggle',
+ 'aria-expanded': preCheckExpanded,
+ }}
+ >
+ {t('public~AI Assessment')}
+
+
+
+
+ {updatesDisplayText}
+
+ {hasUpdates
+ ? t('public~Check cluster health and update prerequisites.')
+ : t('public~Verify cluster health and operational status.')}
+
+
+
+
+
+
+ );
+};
+
export const ClusterSettingsAlerts: FC = ({
cv,
machineConfigPools,
}) => {
const { t } = useTranslation();
+ const isOLSAvailable = useFlag('LIGHTSPEED_CONSOLE');
+
+ // Gate cluster operator watching behind OLS availability to prevent unnecessary API calls
+ const [clusterOperators] = useK8sWatchResource(
+ isOLSAvailable ? ClusterOperatorsResource : null,
+ );
if (isClusterExternallyManaged()) {
return (
@@ -874,6 +1480,7 @@ export const ClusterSettingsAlerts: FC = ({
<>
{!!getConditionUpgradeableFalse(cv) && }
+
>
);
};
@@ -896,6 +1503,7 @@ export const ClusterVersionDetailsTable: FC = (
const [machineConfigPools] = useK8sWatchResource(
MachineConfigPoolsResource,
);
+
const serviceLevelTitle = useServiceLevelTitle();
const desiredVersion = getDesiredClusterVersion(cv);
@@ -1003,12 +1611,14 @@ export const ClusterVersionDetailsTable: FC = (
)}
{(status === ClusterUpdateStatus.UpdatingAndFailing ||
status === ClusterUpdateStatus.Updating) && (
-
+ <>
+
+ >
)}