diff --git a/apps/renterd/contexts/alerts/SetChange.tsx b/apps/renterd/contexts/alerts/SetChange.tsx index 77618a216..5ff90ca58 100644 --- a/apps/renterd/contexts/alerts/SetChange.tsx +++ b/apps/renterd/contexts/alerts/SetChange.tsx @@ -2,7 +2,7 @@ import { Text, Tooltip, ValueCopyable } from '@siafoundation/design-system' import { HostContextMenuFromKey } from '../../components/Hosts/HostContextMenuFromKey' import { ContractContextMenuFromId } from '../../components/Contracts/ContractContextMenuFromId' import { humanBytes } from '@siafoundation/units' -import { format } from 'date-fns' +import { formatRelative } from 'date-fns' import { useMemo } from 'react' import { Add16, Subtract16 } from '@siafoundation/react-icons' import { cx } from 'class-variance-authority' @@ -50,36 +50,103 @@ export function SetChangesField({ ...Object.keys(setAdditions), ...Object.keys(setRemovals), ]) - return contractIds.map((contractId) => { - const additions = setAdditions[contractId]?.additions || [] - const removals = setRemovals[contractId]?.removals || [] - return { - contractId, - hostKey: - setAdditions[contractId]?.hostKey || setRemovals[contractId]?.hostKey, - events: [ - ...additions.map((a) => ({ - type: 'addition', - size: a.size, - time: a.time, - })), - ...removals.map((r) => ({ - type: 'removal', - size: r.size, - time: r.time, - reasons: r.reasons, - })), - ].sort((a, b) => - new Date(a.time).getTime() > new Date(b.time).getTime() ? 1 : -1 - ) as ChangeEvent[], - } - }) + return contractIds + .map((contractId) => { + const additions = setAdditions[contractId]?.additions || [] + const removals = setRemovals[contractId]?.removals || [] + return { + contractId, + hostKey: + setAdditions[contractId]?.hostKey || + setRemovals[contractId]?.hostKey, + events: [ + ...additions.map((a) => ({ + type: 'addition', + size: a.size, + time: a.time, + })), + ...removals.map((r) => ({ + type: 'removal', + size: r.size, + time: r.time, + reasons: r.reasons, + })), + ].sort((a, b) => + new Date(a.time).getTime() < new Date(b.time).getTime() ? 1 : -1 + ) as ChangeEvent[], + } + }) + .sort((a, b) => { + // size in latest event + const aSize = a.events[0].size + const bSize = b.events[0].size + return aSize < bSize ? 1 : -1 + }) }, [setAdditions, setRemovals]) + + // calculate churn %: contracts removed size / total size + const totalSize = useMemo( + () => changes.reduce((acc, { events }) => acc + events[0].size, 0), + [changes] + ) + const removals = useMemo( + () => changes.filter(({ events }) => events[0].type === 'removal'), + [changes] + ) + const additions = useMemo( + () => changes.filter(({ events }) => events[0].type === 'addition'), + [changes] + ) + const removedSize = useMemo( + () => removals.reduce((acc, { events }) => acc + events[0].size, 0), + [removals] + ) + const churn = useMemo( + () => (removedSize / totalSize) * 100, + [removedSize, totalSize] + ) + return (