From 2eae113473e87285bbf1aa9d70f704de9ae1f7cc Mon Sep 17 00:00:00 2001 From: Alex Freska Date: Thu, 9 Jan 2025 15:45:24 -0500 Subject: [PATCH] fix(renterd): contract churn alert percentage --- .changeset/strange-pigs-begin.md | 5 + apps/renterd-e2e/src/specs/alerts.spec.ts | 145 +++++++++++++++++- .../contexts/alerts/ChurnEventsField.tsx | 24 +-- apps/renterd/contexts/contracts/index.tsx | 9 ++ 4 files changed, 171 insertions(+), 12 deletions(-) create mode 100644 .changeset/strange-pigs-begin.md diff --git a/.changeset/strange-pigs-begin.md b/.changeset/strange-pigs-begin.md new file mode 100644 index 000000000..cae8f6aff --- /dev/null +++ b/.changeset/strange-pigs-begin.md @@ -0,0 +1,5 @@ +--- +'renterd': patch +--- + +Fixed an issue with the contract churn alert's overall churn percentage. diff --git a/apps/renterd-e2e/src/specs/alerts.spec.ts b/apps/renterd-e2e/src/specs/alerts.spec.ts index 5ae3a76b8..ac2417cc7 100644 --- a/apps/renterd-e2e/src/specs/alerts.spec.ts +++ b/apps/renterd-e2e/src/specs/alerts.spec.ts @@ -1,9 +1,15 @@ import { Page, test, expect } from '@playwright/test' import { navigateToAlerts } from '../fixtures/navigate' import { afterTest, beforeTest } from '../fixtures/beforeTest' -import { AlertsResponse, busAlertsRoute } from '@siafoundation/renterd-types' +import { + AlertsResponse, + busAlertsRoute, + busContractsRoute, + ContractsResponse, +} from '@siafoundation/renterd-types' test.beforeEach(async ({ page }) => { + await mockApiBusContracts(page) await mockApiBusAlerts(page) await beforeTest(page) }) @@ -17,7 +23,7 @@ test('alert data', async ({ page }) => { // Churn alert const churnData = page.getByTestId('churn') - await expect(churnData.getByText('churn: 99.90%')).toBeVisible() + await expect(churnData.getByText('churn: 37.54%')).toBeVisible() const churnDataContractB6 = churnData.getByTestId( 'b6f32dc39998bd85d730d39666360225af12fbad3bc18de4df50ce09073c9393' ) @@ -36,6 +42,141 @@ test('alert data', async ({ page }) => { await expect(objectsData.getByText('bucket1/nest2/file3.png')).toBeVisible() }) +async function mockApiBusContracts(page: Page) { + const json: ContractsResponse = [ + { + id: 'b6f32dc39998bd85d730d39666360225af12fbad3bc18de4df50ce09073c9393', + hostKey: 'hk', + usability: 'bad', + proofHeight: 100, + revisionHeight: 100, + revisionNumber: 1, + startHeight: 100, + windowStart: 200, + windowEnd: 300, + renewedFrom: '', + spending: { + deletions: '0', + fundAccount: '0', + sectorRoots: '0', + uploads: '0', + }, + initialRenterFunds: '2000', + size: 30000000, + state: 'active', + }, + { + id: '26cd68ac42d4056f1494aef012bf9da4f753ba15e2831722eebf30a78243d534', + hostKey: 'hk', + usability: 'good', + proofHeight: 100, + revisionHeight: 100, + revisionNumber: 1, + startHeight: 100, + windowStart: 200, + windowEnd: 300, + renewedFrom: '', + spending: { + deletions: '0', + fundAccount: '0', + sectorRoots: '0', + uploads: '0', + }, + initialRenterFunds: '2000', + size: 30000, + state: 'active', + }, + { + id: '437b0c09f6167790fefc21000c4a4a81de109729151414526562721ee7802ac6', + hostKey: 'hk', + usability: 'bad', + proofHeight: 100, + revisionHeight: 100, + revisionNumber: 1, + startHeight: 100, + windowStart: 200, + windowEnd: 300, + renewedFrom: '', + spending: { + deletions: '0', + fundAccount: '0', + sectorRoots: '0', + uploads: '0', + }, + initialRenterFunds: '2000', + size: 4000, + state: 'active', + }, + { + id: '89dfc5594909fd468729b59096b26c886b25106e5479ceb1a28276420cb32fd3', + hostKey: 'hk', + usability: 'bad', + proofHeight: 100, + revisionHeight: 100, + revisionNumber: 1, + startHeight: 100, + windowStart: 200, + windowEnd: 300, + renewedFrom: '', + spending: { + deletions: '0', + fundAccount: '0', + sectorRoots: '0', + uploads: '0', + }, + initialRenterFunds: '2000', + size: 10000, + state: 'active', + }, + { + id: 'f0bbb8b6a1a6219beb510f0c4008bba9ed5687b5e617d10efce206022248ed59', + hostKey: 'hk', + usability: 'bad', + proofHeight: 100, + revisionHeight: 100, + revisionNumber: 1, + startHeight: 100, + windowStart: 200, + windowEnd: 300, + renewedFrom: '', + spending: { + deletions: '0', + fundAccount: '0', + sectorRoots: '0', + uploads: '0', + }, + initialRenterFunds: '2000', + size: 50000, + state: 'active', + }, + { + id: 'c7f32dc39998bd85d730d39666360225af12fbad3bc18de4df50ce09073c9666', + hostKey: 'hk', + usability: 'good', + proofHeight: 100, + revisionHeight: 100, + revisionNumber: 1, + startHeight: 100, + windowStart: 200, + windowEnd: 300, + renewedFrom: '', + spending: { + deletions: '0', + fundAccount: '0', + sectorRoots: '0', + uploads: '0', + }, + initialRenterFunds: '2000', + size: 50000000, + state: 'active', + }, + ] + await page.route(`**/api${busContractsRoute}*`, async (route) => { + await route.fulfill({ json }) + }) + return json +} + async function mockApiBusAlerts(page: Page) { const json: AlertsResponse = { hasMore: false, diff --git a/apps/renterd/contexts/alerts/ChurnEventsField.tsx b/apps/renterd/contexts/alerts/ChurnEventsField.tsx index 26d5fba5c..8fd572a8f 100644 --- a/apps/renterd/contexts/alerts/ChurnEventsField.tsx +++ b/apps/renterd/contexts/alerts/ChurnEventsField.tsx @@ -12,6 +12,8 @@ import { useMemo } from 'react' import { Add16, Subtract16 } from '@siafoundation/react-icons' import { cx } from 'class-variance-authority' import { AlertChurnEvent } from '@siafoundation/renterd-types' +import { useContracts } from '../contracts' +import BigNumber from 'bignumber.js' type Change = { contractId: string @@ -26,6 +28,7 @@ export function ChurnEventsField({ }: { data: Record }) { + const { contractSizeTotal } = useContracts() const churnEvents = useMemo(() => { return objectEntries(data) .map(([contractId, events]) => { @@ -45,11 +48,6 @@ export function ChurnEventsField({ }) }, [data]) - // calculate churn %: contracts bad size / total size - const totalSize = useMemo( - () => churnEvents.reduce((acc, { events }) => acc + events[0].size, 0), - [churnEvents] - ) const bads = useMemo( () => churnEvents.filter(({ events }) => events[0].to === 'bad'), [churnEvents] @@ -59,12 +57,18 @@ export function ChurnEventsField({ [churnEvents] ) const badSize = useMemo( - () => bads.reduce((acc, { events }) => acc + events[0].size, 0), + () => + bads.reduce( + (acc, { events }) => acc.plus(events[0].size), + new BigNumber(0) + ), [bads] ) + // Calculate churn %: contracts bad size / total size. const churn = useMemo( - () => (totalSize > 0 ? (badSize / totalSize) * 100 : 0), - [badSize, totalSize] + () => + contractSizeTotal?.gt(0) ? badSize.div(contractSizeTotal).times(100) : 0, + [badSize, contractSizeTotal] ) return ( @@ -76,7 +80,7 @@ export function ChurnEventsField({
@@ -84,7 +88,7 @@ export function ChurnEventsField({ churn: {churn.toFixed(2)}% - ({humanBytes(badSize)} / {humanBytes(totalSize)}) + ({humanBytes(badSize)} / {humanBytes(contractSizeTotal)})
diff --git a/apps/renterd/contexts/contracts/index.tsx b/apps/renterd/contexts/contracts/index.tsx index aea08592b..4b207c9c2 100644 --- a/apps/renterd/contexts/contracts/index.tsx +++ b/apps/renterd/contexts/contracts/index.tsx @@ -27,6 +27,7 @@ import { defaultDatasetRefreshInterval } from '../../config/swr' import { useDataset } from './dataset' import { useFilteredStats } from './useFilteredStats' import { daysInMilliseconds } from '@siafoundation/units' +import BigNumber from 'bignumber.js' const defaultLimit = 50 @@ -164,6 +165,13 @@ function useContractsMain() { disabled: !selectedContract, }) + const contractSizeTotal = useMemo( + () => + dataset?.reduce((acc, { size }) => acc.plus(size), new BigNumber(0)) ?? + new BigNumber(0), + [dataset] + ) + return { datasetState, limit, @@ -206,6 +214,7 @@ function useContractsMain() { fetchPrunableSize, fetchPrunableSizeAll, multiSelect, + contractSizeTotal, } }