Skip to content

Commit

Permalink
feat(hostd): upgraded alerts feature
Browse files Browse the repository at this point in the history
  • Loading branch information
alexfreska committed Jan 10, 2025
1 parent dc08559 commit 4421f61
Show file tree
Hide file tree
Showing 40 changed files with 1,327 additions and 667 deletions.
5 changes: 5 additions & 0 deletions .changeset/forty-gifts-divide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'hostd': minor
---

Alerts can now be accessed via the cmd+k menu.
5 changes: 5 additions & 0 deletions .changeset/four-avocados-develop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@siafoundation/design-system': minor
---

Removed AlertsDialog.
5 changes: 5 additions & 0 deletions .changeset/short-mayflies-exercise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@siafoundation/hostd-types': minor
---

Added named AlertData type.
5 changes: 5 additions & 0 deletions .changeset/smart-turkeys-smoke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@siafoundation/e2e': minor
---

Added continueToClickUntil.
5 changes: 5 additions & 0 deletions .changeset/spicy-tips-boil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'hostd': minor
---

The hostd alerts feature is now a full page and matches the user experience of renterd alerts.
24 changes: 24 additions & 0 deletions apps/hostd-e2e/src/fixtures/alerts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Page } from '@playwright/test'
import { maybeExpectAndReturn, step } from '@siafoundation/e2e'

export const getAlertRows = (page: Page) => {
return page.getByTestId('alertsTable').locator('tbody').getByRole('row')
}

export const getAlertRowsAll = step('get alert rows', async (page: Page) => {
return getAlertRows(page).all()
})

export const getAlertRowByIndex = step(
'get alert row by index',
async (page: Page, index: number, shouldExpect?: boolean) => {
return maybeExpectAndReturn(
page
.getByTestId('alertsTable')
.locator('tbody')
.getByRole('row')
.nth(index),
shouldExpect
)
}
)
8 changes: 3 additions & 5 deletions apps/hostd-e2e/src/fixtures/navigate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,10 @@ export const navigateToContracts = step(
}
)

export const openAlertsDialog = step(
'open alerts dialog',
export const navigateToAlerts = step(
'navigate to alerts',
async (page: Page) => {
await page.getByTestId('sidenav').getByLabel('Alerts').click()
const dialog = page.getByRole('dialog')
await expect(dialog.getByText('Alerts')).toBeVisible()
return dialog
await expect(page.getByTestId('navbar').getByText('Alerts')).toBeVisible()
}
)
84 changes: 84 additions & 0 deletions apps/hostd-e2e/src/specs/alerts.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { test, expect, Page } from '@playwright/test'
import { navigateToAlerts, navigateToVolumes } from '../fixtures/navigate'
import { afterTest, beforeTest } from '../fixtures/beforeTest'
import { Alert } from '@siafoundation/hostd-types'
import { getAlertRows } from '../fixtures/alerts'
import { createVolume, deleteVolume } from '../fixtures/volumes'
import fs from 'fs'
import os from 'os'
import { clickAndWait, continueToClickUntil } from '@siafoundation/e2e'

let dirPath = '/'

test.beforeEach(async ({ page }) => {
await beforeTest(page)
// Create a temporary directory.
dirPath = fs.mkdtempSync(process.env.GITHUB_WORKSPACE || os.tmpdir())
})

test.afterEach(async () => {
await afterTest()
fs.rmSync(dirPath, { recursive: true })
})

test('filtering alerts', async ({ page }) => {
await mockApiAlerts(page)
await page.reload()

await navigateToAlerts(page)

// Check initial number of alerts.
await expect(getAlertRows(page)).toHaveCount(2)

// Verify alert content.
await expect(page.getByText('Volume initialized')).toBeVisible()
await expect(page.getByText('Volume warning')).toBeVisible()

// Test filtering.
await page.getByRole('button', { name: 'Info' }).click()
await expect(getAlertRows(page)).toHaveCount(1)
})

test('dismissing alerts', async ({ page }) => {
const name = 'my-new-volume'
await navigateToVolumes({ page })
await createVolume(page, name, dirPath)
await navigateToAlerts(page)
await expect(getAlertRows(page).getByText('Volume initialized')).toBeVisible()
// Dismissing the alert too early will cause the alert to reappear
// so maybe try to dismiss more than once.
await continueToClickUntil(
page.getByRole('button', { name: 'dismiss alert' }),
page.getByText('There are currently no alerts.')
)
})

async function mockApiAlerts(page: Page) {
const alerts: Alert[] = [
{
id: 'c39d09ee61a5d1dd9ad97015a0e87e9286f765bbf109cafad936d5a1aa843e54',
severity: 'info',
message: 'Volume initialized',
data: {
elapsed: 95823333,
target: 2623,
volumeID: 3,
},
timestamp: '2025-01-10T10:17:52.365323-05:00',
},
{
id: '93683b58d12c2de737a8849561b9f0dae07120eee3185f40da489d48585b416a',
severity: 'warning',
message: 'Volume warning',
data: {
elapsed: 257749500,
targetSectors: 7868,
volumeID: 2,
},
timestamp: '2025-01-10T10:17:18.754568-05:00',
},
]
await page.route('**/api/alerts', async (route) => {
await route.fulfill({ json: alerts })
})
}
9 changes: 5 additions & 4 deletions apps/hostd-e2e/src/specs/volumes.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect, test } from '@playwright/test'
import { navigateToVolumes, openAlertsDialog } from '../fixtures/navigate'
import { navigateToVolumes, navigateToAlerts } from '../fixtures/navigate'
import {
createVolume,
deleteVolume,
Expand All @@ -13,6 +13,7 @@ import fs from 'fs'
import os from 'os'
import { fillTextInputByName } from '@siafoundation/e2e'
import path from 'path'
import { getAlertRows } from '../fixtures/alerts'

let dirPath = '/'

Expand All @@ -31,9 +32,9 @@ test('can create and delete a volume', async ({ page }) => {
const name = 'my-new-volume'
await navigateToVolumes({ page })
await createVolume(page, name, dirPath)
const dialog = await openAlertsDialog(page)
await expect(dialog.getByText('Volume initialized')).toBeVisible()
await dialog.getByLabel('close').click()
await navigateToAlerts(page)
await expect(getAlertRows(page).getByText('Volume initialized')).toBeVisible()
await navigateToVolumes({ page })
await deleteVolume(page, name, dirPath)
})

Expand Down
54 changes: 54 additions & 0 deletions apps/hostd/components/Alerts/AlertContextMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {
DropdownMenu,
DropdownMenuItem,
Button,
DropdownMenuLeftSlot,
DropdownMenuLabel,
Text,
} from '@siafoundation/design-system'
import { CaretDown16, Checkmark16 } from '@siafoundation/react-icons'
import { useAlerts } from '../../contexts/alerts'

type Props = {
id: string
contentProps?: React.ComponentProps<typeof DropdownMenu>['contentProps']
buttonProps?: React.ComponentProps<typeof Button>
}

export function AlertContextMenu({ id, contentProps, buttonProps }: Props) {
const { dismissOne } = useAlerts()

return (
<DropdownMenu
trigger={
<Button variant="ghost" icon="hover" {...buttonProps}>
<CaretDown16 />
</Button>
}
contentProps={{
align: 'start',
...contentProps,
onClick: (e) => {
e.stopPropagation()
},
}}
>
<div className="px-1.5 py-1">
<Text size="14" weight="medium" color="subtle">
Alert {id.slice(0, 24)}...
</Text>
</div>
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem
onSelect={() => {
dismissOne(id)
}}
>
<DropdownMenuLeftSlot>
<Checkmark16 />
</DropdownMenuLeftSlot>
Clear alert
</DropdownMenuItem>
</DropdownMenu>
)
}
9 changes: 9 additions & 0 deletions apps/hostd/components/Alerts/AlertsActionsMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { AlertsViewDropdownMenu } from './AlertsViewDropdownMenu'

export function AlertsActionsMenu() {
return (
<div className="flex gap-2">
<AlertsViewDropdownMenu />
</div>
)
}
55 changes: 55 additions & 0 deletions apps/hostd/components/Alerts/AlertsCmd/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {
CommandGroup,
CommandItemNav,
CommandItemSearch,
} from '../../CmdRoot/Item'
import { Page } from '../../CmdRoot/types'
import { useRouter } from 'next/router'
import { useDialog } from '../../../contexts/dialog'
import { routes } from '../../../config/routes'

export const commandPage = {
namespace: 'alerts',
label: 'Alerts',
}

export function AlertsCmd({
currentPage,
parentPage,
pushPage,
}: {
currentPage: Page
parentPage?: Page
beforeSelect?: () => void
afterSelect?: () => void
pushPage: (page: Page) => void
}) {
const router = useRouter()
const { closeDialog } = useDialog()
return (
<>
<CommandItemNav
currentPage={currentPage}
parentPage={parentPage}
commandPage={parentPage}
onSelect={() => {
pushPage(commandPage)
}}
>
{commandPage.label}
</CommandItemNav>
<CommandGroup currentPage={currentPage} commandPage={commandPage}>
<CommandItemSearch
currentPage={currentPage}
commandPage={commandPage}
onSelect={() => {
router.push(routes.alerts.index)
closeDialog()
}}
>
View alerts
</CommandItemSearch>
</CommandGroup>
</>
)
}
Loading

0 comments on commit 4421f61

Please sign in to comment.