From b6a313c5faa91c1a5a530dd5a9d8d4d80703f13a Mon Sep 17 00:00:00 2001 From: Alex Freska Date: Tue, 7 May 2024 11:49:22 -0400 Subject: [PATCH] fix: renterd recommendations states and crashing --- .changeset/fast-colts-turn.md | 5 ++ .changeset/old-candles-remember.md | 5 ++ .changeset/proud-dancers-train.md | 5 ++ .../components/Config/Recommendations.tsx | 81 +++++++++++-------- .../config/useAutopilotEvaluations.tsx | 79 ++++++++++++++++-- apps/renterd/contexts/config/useForm.tsx | 9 ++- 6 files changed, 143 insertions(+), 41 deletions(-) create mode 100644 .changeset/fast-colts-turn.md create mode 100644 .changeset/old-candles-remember.md create mode 100644 .changeset/proud-dancers-train.md diff --git a/.changeset/fast-colts-turn.md b/.changeset/fast-colts-turn.md new file mode 100644 index 000000000..eab4a474b --- /dev/null +++ b/.changeset/fast-colts-turn.md @@ -0,0 +1,5 @@ +--- +'renterd': patch +--- + +Fixed an issue where the recommendations bar was overlapping notifications. diff --git a/.changeset/old-candles-remember.md b/.changeset/old-candles-remember.md new file mode 100644 index 000000000..860690531 --- /dev/null +++ b/.changeset/old-candles-remember.md @@ -0,0 +1,5 @@ +--- +'renterd': patch +--- + +Fixed an issue where clearing some configuration fields would crash the app due to how recommendations were being calculated. diff --git a/.changeset/proud-dancers-train.md b/.changeset/proud-dancers-train.md new file mode 100644 index 000000000..ff7589c27 --- /dev/null +++ b/.changeset/proud-dancers-train.md @@ -0,0 +1,5 @@ +--- +'renterd': patch +--- + +The configuration recommendations now instruct the user to first fill all fields. Closes https://github.com/SiaFoundation/renterd/issues/1214 diff --git a/apps/renterd/components/Config/Recommendations.tsx b/apps/renterd/components/Config/Recommendations.tsx index 5f22c49b9..ed3822d81 100644 --- a/apps/renterd/components/Config/Recommendations.tsx +++ b/apps/renterd/components/Config/Recommendations.tsx @@ -13,6 +13,7 @@ import { CaretDown16, Information20, CheckmarkFilled20, + PendingFilled20, } from '@siafoundation/react-icons' import { useApp } from '../../contexts/app' import { routes } from '../../config/routes' @@ -33,6 +34,7 @@ export function Recommendations() { const { hostMargin50, hostTarget50, + hasDataToEvaluate, needsRecommendations, foundRecommendation, recommendations, @@ -113,6 +115,27 @@ export function Recommendations() { ) + if (!hasDataToEvaluate) { + return ( + + + + + + The system will review your configuration once all fields are + filled + + + } + /> + ) + } + if (!needsRecommendations) { return ( void maximizeControls: boolean title: React.ReactNode - tip: React.ReactNode + tip?: React.ReactNode }) { + const el = ( +
{ + if (maximizeControls) { + setMaximized(!maximized) + } + }} + > +
{title}
+ {maximizeControls && ( + + )} +
+ ) return (
-
+
- { - if (maximizeControls) { - setMaximized(!maximized) - } - }} - > -
{title}
- {maximizeControls && ( - - )} -
- } - > - {tip} - + {tip ? {tip} : el} {children}
diff --git a/apps/renterd/contexts/config/useAutopilotEvaluations.tsx b/apps/renterd/contexts/config/useAutopilotEvaluations.tsx index be89e4306..725086e0a 100644 --- a/apps/renterd/contexts/config/useAutopilotEvaluations.tsx +++ b/apps/renterd/contexts/config/useAutopilotEvaluations.tsx @@ -29,14 +29,31 @@ export function useAutopilotEvaluations({ }) { const values = form.watch() const renterdState = useBusState() - const payloads = useMemo(() => { + + const hasDataToEvaluate = useMemo(() => { if (!checkIfAllResourcesLoaded(resources)) { - return + return false } if (!form.formState.isValid) { - return + return false } if (!renterdState.data) { + return false + } + return true + }, [form, resources, renterdState]) + + // We need to pass valid settings data into transformUp to get the payloads. + // The form can be invalid so we need to merge in valid data to make sure + // numbers are not undefined in the transformUp calculations that assume + // all data is valid and present. + const currentValuesWithZeroDefaults = useMemo( + () => mergeIfDefined(valuesZeroDefaults, values), + [values] + ) + + const payloads = useMemo(() => { + if (!hasDataToEvaluate) { return } @@ -46,17 +63,17 @@ export function useAutopilotEvaluations({ isAutopilotEnabled, showAdvanced, estimatedSpendingPerMonth, - values, + values: currentValuesWithZeroDefaults, }) return payloads }, [ - form, - values, + currentValuesWithZeroDefaults, resources, renterdState, isAutopilotEnabled, showAdvanced, estimatedSpendingPerMonth, + hasDataToEvaluate, ]) const userContractCountTarget = payloads?.autopilot.contracts.amount || 0 @@ -187,7 +204,7 @@ export function useAutopilotEvaluations({ const rec = getRecommendationItem({ key: remoteToLocalFields[key], recommendationDown, - values, + values: currentValuesWithZeroDefaults, }) if (rec) { recs.push(rec) @@ -195,7 +212,12 @@ export function useAutopilotEvaluations({ } }) return recs - }, [recommendedGougingSettings, resources, payloads, values]) + }, [ + recommendedGougingSettings, + resources, + payloads, + currentValuesWithZeroDefaults, + ]) const foundRecommendation = !!recommendations.length return { @@ -206,6 +228,7 @@ export function useAutopilotEvaluations({ usableHostsCurrent, userContractCountTarget, usableHostsAfterRecommendation, + hasDataToEvaluate, needsRecommendations, foundRecommendation, recommendations, @@ -283,3 +306,43 @@ const remoteToLocalFields: Record = { minMaxEphemeralAccountBalance: 'minMaxEphemeralAccountBalance', migrationSurchargeMultiplier: 'migrationSurchargeMultiplier', } + +export const valuesZeroDefaults: SettingsData = { + autopilotContractSet: '', + amountHosts: new BigNumber(0), + allowanceMonth: new BigNumber(0), + periodWeeks: new BigNumber(0), + renewWindowWeeks: new BigNumber(0), + downloadTBMonth: new BigNumber(0), + uploadTBMonth: new BigNumber(0), + storageTB: new BigNumber(0), + prune: false, + allowRedundantIPs: false, + maxDowntimeHours: new BigNumber(0), + minRecentScanFailures: new BigNumber(0), + minProtocolVersion: '', + defaultContractSet: '', + uploadPackingEnabled: true, + maxRpcPriceMillion: new BigNumber(0), + maxStoragePriceTBMonth: new BigNumber(0), + maxContractPrice: new BigNumber(0), + maxDownloadPriceTB: new BigNumber(0), + maxUploadPriceTB: new BigNumber(0), + hostBlockHeightLeeway: new BigNumber(0), + minPriceTableValidityMinutes: new BigNumber(0), + minAccountExpiryDays: new BigNumber(0), + minMaxEphemeralAccountBalance: new BigNumber(0), + migrationSurchargeMultiplier: new BigNumber(0), + minShards: new BigNumber(0), + totalShards: new BigNumber(0), +} + +export function mergeIfDefined(defaults: SettingsData, values: SettingsData) { + const merged: SettingsData = { ...defaults } + Object.keys(values).forEach((key) => { + if (values[key] !== undefined) { + merged[key] = values[key] + } + }) + return merged +} diff --git a/apps/renterd/contexts/config/useForm.tsx b/apps/renterd/contexts/config/useForm.tsx index 7964b28bb..107aa4e05 100644 --- a/apps/renterd/contexts/config/useForm.tsx +++ b/apps/renterd/contexts/config/useForm.tsx @@ -1,4 +1,4 @@ -import { useMemo } from 'react' +import { useEffect, useMemo } from 'react' import { defaultValues, getAdvancedDefaults } from './types' import { getRedundancyMultiplier } from './utils' import { useForm as useHookForm } from 'react-hook-form' @@ -46,6 +46,13 @@ export function useForm({ resources }: { resources: Resources }) { } ) + // Trigger input validation on showAdvanced change because many field validation + // objects switch from required to not required. + useEffect(() => { + form.trigger() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [showAdvanced]) + const estimates = useEstimates({ isAutopilotEnabled, redundancyMultiplier,