From 01ff5107e813cb9dd207e33a787113b24ace7ad5 Mon Sep 17 00:00:00 2001 From: Alex Freska Date: Wed, 27 Nov 2024 14:15:48 -0500 Subject: [PATCH] refactor(renterd): alerts set change event replaced with similar churn event --- .changeset/short-spoons-march.md | 5 + .../{SetChange.tsx => ChurnEventsField.tsx} | 138 +++++++----------- apps/renterd/contexts/alerts/columns.tsx | 24 --- apps/renterd/contexts/alerts/data.tsx | 6 + 4 files changed, 64 insertions(+), 109 deletions(-) create mode 100644 .changeset/short-spoons-march.md rename apps/renterd/contexts/alerts/{SetChange.tsx => ChurnEventsField.tsx} (61%) diff --git a/.changeset/short-spoons-march.md b/.changeset/short-spoons-march.md new file mode 100644 index 000000000..5020d6b03 --- /dev/null +++ b/.changeset/short-spoons-march.md @@ -0,0 +1,5 @@ +--- +'renterd': minor +--- + +The set change alert was replaced with a churn alert that features a similar breakdown of contract changes with details such as reason and size. diff --git a/apps/renterd/contexts/alerts/SetChange.tsx b/apps/renterd/contexts/alerts/ChurnEventsField.tsx similarity index 61% rename from apps/renterd/contexts/alerts/SetChange.tsx rename to apps/renterd/contexts/alerts/ChurnEventsField.tsx index 23c5cdb93..cf4b0f575 100644 --- a/apps/renterd/contexts/alerts/SetChange.tsx +++ b/apps/renterd/contexts/alerts/ChurnEventsField.tsx @@ -1,4 +1,9 @@ -import { Text, Tooltip, ValueCopyable } from '@siafoundation/design-system' +import { + objectEntries, + Text, + Tooltip, + ValueCopyable, +} from '@siafoundation/design-system' import { HostContextMenuFromKey } from '../../components/Hosts/HostContextMenuFromKey' import { ContractContextMenuFromId } from '../../components/Contracts/ContractContextMenuFromId' import { humanBytes } from '@siafoundation/units' @@ -6,74 +11,37 @@ import { formatRelative } from 'date-fns' import { useMemo } from 'react' import { Add16, Subtract16 } from '@siafoundation/react-icons' import { cx } from 'class-variance-authority' -import { uniq } from '@technically/lodash' -type ChangeEvent = { - type: 'addition' | 'removal' - reasons?: string +type ChurnEvent = { + type: 'bad' | 'good' + reason?: string size: number + hostKey: string time: string } type Change = { contractId: string hostKey: string - events: ChangeEvent[] + events: ChurnEvent[] } -export type SetAdditions = Record< - string, - { - hostKey: string - additions: { size: number; time: string }[] - } -> - -export type SetRemovals = Record< - string, - { - hostKey: string - removals: { reasons: string; size: number; time: string }[] - } -> +export type ChurnData = Record -export function SetChangesField({ - setAdditions, - setRemovals, +export function ChurnEventsField({ + data, }: { - setAdditions: SetAdditions - setRemovals: SetRemovals + data: Record }) { - const changes = useMemo(() => { - // Merge all unique contract ids from additions and removals together - const contractIds = uniq([ - ...Object.keys(setAdditions), - ...Object.keys(setRemovals), - ]) - return contractIds - .map((contractId) => { - const additions = setAdditions[contractId]?.additions || [] - const removals = setRemovals[contractId]?.removals || [] + const churnEvents = useMemo(() => { + return objectEntries(data) + .map(([contractId, events]) => { 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) => + hostKey: events[0].hostKey, + events: events.sort((a, b) => new Date(a.time).getTime() < new Date(b.time).getTime() ? 1 : -1 - ) as ChangeEvent[], + ), } }) .sort((a, b) => { @@ -82,39 +50,39 @@ export function SetChangesField({ const bSize = b.events[0].size return aSize < bSize ? 1 : -1 }) - }, [setAdditions, setRemovals]) + }, [data]) - // calculate churn %: contracts removed size / total size + // calculate churn %: contracts bad size / total size const totalSize = useMemo( - () => changes.reduce((acc, { events }) => acc + events[0].size, 0), - [changes] + () => churnEvents.reduce((acc, { events }) => acc + events[0].size, 0), + [churnEvents] ) - const removals = useMemo( - () => changes.filter(({ events }) => events[0].type === 'removal'), - [changes] + const bads = useMemo( + () => churnEvents.filter(({ events }) => events[0].type === 'bad'), + [churnEvents] ) - const additions = useMemo( - () => changes.filter(({ events }) => events[0].type === 'addition'), - [changes] + const goods = useMemo( + () => churnEvents.filter(({ events }) => events[0].type === 'good'), + [churnEvents] ) - const removedSize = useMemo( - () => removals.reduce((acc, { events }) => acc + events[0].size, 0), - [removals] + const badSize = useMemo( + () => bads.reduce((acc, { events }) => acc + events[0].size, 0), + [bads] ) const churn = useMemo( - () => (totalSize > 0 ? (removedSize / totalSize) * 100 : 0), - [removedSize, totalSize] + () => (totalSize > 0 ? (badSize / totalSize) * 100 : 0), + [badSize, totalSize] ) return (
- contract set changes + contract changes
@@ -123,12 +91,12 @@ export function SetChangesField({ churn: {churn.toFixed(2)}% - ({humanBytes(removedSize)} / {humanBytes(totalSize)}) + ({humanBytes(badSize)} / {humanBytes(totalSize)})
- + - {additions.length} + {goods.length} - + - {removals.length} + {bads.length}
- {changes.map(({ contractId, hostKey, events }, i) => ( - ( +
- {events.map(({ type, reasons, size, time }, i) => ( + {events.map(({ type, reason, size, time }, i) => ( @@ -230,18 +198,18 @@ function ContractSetChange({ className={cx( 'flex gap-2 justify-between mr-2 pr-1', i === 0 - ? type === 'addition' + ? type === 'good' ? 'bg-green-400/20' : 'bg-red-400/20' : 'opacity-50' )} >
- - {type === 'addition' ? : } + + {type === 'good' ? : } - {reasons} + {reason}
diff --git a/apps/renterd/contexts/alerts/columns.tsx b/apps/renterd/contexts/alerts/columns.tsx index 77862e7db..c4db94f8a 100644 --- a/apps/renterd/contexts/alerts/columns.tsx +++ b/apps/renterd/contexts/alerts/columns.tsx @@ -9,7 +9,6 @@ import { } from '@siafoundation/design-system' import { AlertData, TableColumnId } from './types' import { dataFields } from './data' -import { SetAdditions, SetChangesField, SetRemovals } from './SetChange' import { Checkmark16 } from '@siafoundation/react-icons' import { formatRelative } from 'date-fns' import { Fragment, useMemo } from 'react' @@ -102,32 +101,9 @@ export const columns: AlertsTableColumn[] = [ .filter(Boolean) as { key: string; value: unknown }[], [data] ) - // Collect set changes for the custom SetChangeField component - // which is a special case that combines two keys of data - const setAdditions = useMemo( - () => data['setAdditions'] as SetAdditions, - [data] - ) - const setRemovals = useMemo( - () => data['setRemovals'] as SetRemovals, - [data] - ) return (
- {(setAdditions || setRemovals) && ( - -
- -
- {datums.length >= 1 && ( - - )} -
- )} {datums.map(({ key, value }, i) => { const Component = dataFields?.[key]?.render if (!Component) { diff --git a/apps/renterd/contexts/alerts/data.tsx b/apps/renterd/contexts/alerts/data.tsx index be97a0ea6..de514eaa0 100644 --- a/apps/renterd/contexts/alerts/data.tsx +++ b/apps/renterd/contexts/alerts/data.tsx @@ -18,6 +18,7 @@ import { ContractContextMenuFromId } from '../../components/Contracts/ContractCo import { AccountContextMenu } from '../../components/AccountContextMenu' import { FileContextMenu } from '../../components/Files/FileContextMenu' import { CaretDown16 } from '@siafoundation/react-icons' +import { ChurnData, ChurnEventsField } from './ChurnEventsField' type Render = (props: { value: unknown }) => JSX.Element @@ -327,4 +328,9 @@ export const dataFields: Record< ) } as Render, }, + churn: { + render: function Component({ value }: { value: ChurnData }) { + return + } as Render, + }, }