Skip to content

Commit

Permalink
refactor(renterd): alerts set change event replaced with similar chur…
Browse files Browse the repository at this point in the history
…n event
  • Loading branch information
alexfreska committed Nov 27, 2024
1 parent 706e746 commit ae1e074
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 101 deletions.
5 changes: 5 additions & 0 deletions .changeset/short-spoons-march.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -1,79 +1,47 @@
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'
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<string, ChurnEvent[]>

export function SetChangesField({
setAdditions,
setRemovals,
export function ChurnEventsField({
data,
}: {
setAdditions: SetAdditions
setRemovals: SetRemovals
data: Record<string, ChurnEvent[]>
}) {
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) => {
Expand All @@ -82,24 +50,24 @@ export function SetChangesField({
const bSize = b.events[0].size
return aSize < bSize ? 1 : -1
})
}, [setAdditions, setRemovals])
}, [data])

// calculate churn %: contracts removed 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]
() => bads.reduce((acc, { events }) => acc + events[0].size, 0),
[bads]
)
const churn = useMemo(
() => (totalSize > 0 ? (removedSize / totalSize) * 100 : 0),
Expand All @@ -110,7 +78,7 @@ export function SetChangesField({
<div className="flex flex-col gap-2">
<div className="flex gap-2 items-center pr-1">
<Text size="12" color="subtle" ellipsis>
contract set changes
contract changes
</Text>
<div className="flex-1" />
<Tooltip
Expand All @@ -128,28 +96,28 @@ export function SetChangesField({
</div>
</Tooltip>
<div className="flex gap-1 items-center">
<Tooltip content={`${additions.length} contracts added`}>
<Tooltip content={`${goods.length} contracts added`}>
<Text
size="12"
color="green"
ellipsis
className="flex items-center"
>
<Add16 />
{additions.length}
{goods.length}
</Text>
</Tooltip>
<Tooltip content={`${removals.length} contracts removed`}>
<Tooltip content={`${bads.length} contracts removed`}>
<Text size="12" color="red" ellipsis className="flex items-center">
<Subtract16 />
{removals.length}
{bads.length}
</Text>
</Tooltip>
</div>
</div>
<div className="flex flex-col gap-3 mb-2">
{changes.map(({ contractId, hostKey, events }, i) => (
<ContractSetChange
{churnEvents.map(({ contractId, hostKey, events }, i) => (
<ChurnEventItem
key={contractId + hostKey}
contractId={contractId}
hostKey={hostKey}
Expand All @@ -162,7 +130,7 @@ export function SetChangesField({
)
}

function ContractSetChange({
function ChurnEventItem({
contractId,
hostKey,
events,
Expand Down Expand Up @@ -219,26 +187,26 @@ function ContractSetChange({
/>
</div>
</div>
{events.map(({ type, reasons, size, time }, i) => (
{events.map(({ type, reason: reasons, size, time }, i) => (
<Tooltip
key={type + reasons + time}
content={type === 'addition' ? 'added' : `removed: ${reasons}`}
content={type === 'good' ? 'added' : `removed: ${reasons}`}
align="start"
side="bottom"
>
<div
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'
)}
>
<div className="flex gap-1 items-center overflow-hidden">
<Text size="12" color={type === 'addition' ? 'green' : 'red'}>
{type === 'addition' ? <Add16 /> : <Subtract16 />}
<Text size="12" color={type === 'good' ? 'green' : 'red'}>
{type === 'good' ? <Add16 /> : <Subtract16 />}
</Text>
<Text size="12" ellipsis>
{reasons}
Expand Down
24 changes: 0 additions & 24 deletions apps/renterd/contexts/alerts/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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 (
<div className="py-4 w-full">
<Panel color="subtle" className="flex flex-col gap-1 w-full py-1">
{(setAdditions || setRemovals) && (
<Fragment key="setChanges">
<div className="py-1 px-2">
<SetChangesField
setAdditions={setAdditions}
setRemovals={setRemovals}
/>
</div>
{datums.length >= 1 && (
<Separator color="verySubtle" className="w-full" />
)}
</Fragment>
)}
{datums.map(({ key, value }, i) => {
const Component = dataFields?.[key]?.render
if (!Component) {
Expand Down
6 changes: 6 additions & 0 deletions apps/renterd/contexts/alerts/data.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -327,4 +328,9 @@ export const dataFields: Record<
)
} as Render,
},
churn: {
render: function Component({ value }: { value: ChurnData }) {
return <ChurnEventsField data={value} />
} as Render,
},
}

0 comments on commit ae1e074

Please sign in to comment.