Skip to content

Commit

Permalink
feat(renterd): generate debug report zip
Browse files Browse the repository at this point in the history
  • Loading branch information
alexfreska committed Oct 11, 2024
1 parent 3d2a152 commit 9810512
Show file tree
Hide file tree
Showing 14 changed files with 500 additions and 13 deletions.
5 changes: 5 additions & 0 deletions .changeset/large-games-love.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'renterd': minor
---

There is now an option to generate a metadata report for debugging purposes. It can be accessed from the cmd+k menu. Closes https://github.com/SiaFoundation/renterd/issues/1119 Closes https://github.com/SiaFoundation/renterd/issues/1279
6 changes: 6 additions & 0 deletions .changeset/perfect-beds-grab.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'hostd': minor
'renterd': minor
---

The cmd+k menu / command palette dialog now announces itself via assistive technology.
6 changes: 6 additions & 0 deletions .changeset/swift-otters-lick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@siafoundation/renterd-react': minor
'@siafoundation/renterd-types': minor
---

The alerts API limit and skip params are now optional.
2 changes: 2 additions & 0 deletions apps/hostd/components/CmdKDialog/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export function CmdKDialog({ open, onOpenChange, setOpen }: Props) {
<>
<Dialog
open={open}
title="Command palette"
titleVisuallyHidden
onOpenChange={onOpenChange}
contentVariants={{
className: '!absolute !p-1 w-[450px] top-[200px]',
Expand Down
80 changes: 80 additions & 0 deletions apps/renterd-e2e/src/specs/report.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { test, expect } from '@playwright/test'
import { afterTest, beforeTest } from '../fixtures/beforeTest'
import {
fillTextInputByName,
openCmdkMenu,
setSwitchByLabel,
} from '@siafoundation/e2e'
import fs from 'fs'
import jszip from 'jszip'

test.beforeEach(async ({ page }) => {
await beforeTest(page)
})

test.afterEach(async () => {
await afterTest()
})

test('generating a debug report', async ({ page }) => {
const cmdk = await openCmdkMenu(page)
await fillTextInputByName(page, 'cmdk-input', 'generate a debug report')
await expect(cmdk.locator('div[cmdk-item]')).toHaveCount(1)
await cmdk
.locator('div[cmdk-item]')
.getByText('generate a debug report')
.click()
const dialog = page.getByRole('dialog', {
name: 'Generate a debug report',
})

// Wait for the download event and capture it.
const [download] = await Promise.all([
page.waitForEvent('download'),
dialog.getByRole('button', { name: 'Generate' }).click(),
])

// Check if the download file has the correct name.
const downloadPath = await download.path()
expect(download.suggestedFilename()).toBe('renterd-debug.zip')

// Verify contents of the zip.
const zipBuffer = fs.readFileSync(downloadPath)
const zip = await jszip.loadAsync(zipBuffer)
const fileNames = Object.keys(zip.files)
expect(fileNames).toContain('contracts.json')
expect(fileNames).toContain('alerts.json')
expect(fileNames).toContain('autopilot.json')
expect(fileNames).toContain('gouging.json')
expect(fileNames).toContain('upload.json')
expect(fileNames).toContain('pinned.json')

// Check the contents is json.
const alertsFileContent = await zip.file('alerts.json').async('string')
expect(alertsFileContent).toContain('{')

// Disabled some metadata and regenerate.
await setSwitchByLabel(page, 'autopilot', false)
await setSwitchByLabel(page, 'gouging', false)

// Wait for the download event and capture it.
const [download2] = await Promise.all([
page.waitForEvent('download'),
dialog.getByRole('button', { name: 'Generate' }).click(),
])

// Check if the download file has the correct name.
const downloadPath2 = await download2.path()
expect(download2.suggestedFilename()).toBe('renterd-debug.zip')

// Verify contents of the zip.
const zipBuffer2 = fs.readFileSync(downloadPath2)
const zip2 = await jszip.loadAsync(zipBuffer2)
const fileNames2 = Object.keys(zip2.files)
expect(fileNames2).toContain('contracts.json')
expect(fileNames2).toContain('alerts.json')
expect(fileNames2).not.toContain('autopilot.json')
expect(fileNames2).not.toContain('gouging.json')
expect(fileNames2).toContain('upload.json')
expect(fileNames2).toContain('pinned.json')
})
2 changes: 2 additions & 0 deletions apps/renterd/components/CmdKDialog/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export function CmdKDialog({ open, onOpenChange, setOpen }: Props) {
<>
<Dialog
open={open}
title="Command palette"
titleVisuallyHidden
onOpenChange={onOpenChange}
contentVariants={{
className: '!absolute !p-1 w-[450px] top-[200px]',
Expand Down
9 changes: 9 additions & 0 deletions apps/renterd/components/CmdRoot/AppCmdGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,15 @@ export function AppCmdGroup({ currentPage, parentPage }: Props) {
>
Set theme to light
</CommandItemSearch>
<CommandItemSearch
currentPage={currentPage}
commandPage={commandPage}
onSelect={() => {
openDialog('debug')
}}
>
Generate a debug report
</CommandItemSearch>
<CommandItemSearch
currentPage={currentPage}
commandPage={commandPage}
Expand Down
3 changes: 3 additions & 0 deletions apps/renterd/contexts/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { FilesBucketPolicyDialog } from '../dialogs/FilesBucketPolicyDialog'
import { FilesBucketCreateDialog } from '../dialogs/FilesBucketCreateDialog'
import { FileRenameDialog } from '../dialogs/FileRenameDialog'
import { KeysCreateDialog } from '../components/Keys/KeysCreateDialog'
import { DebugDialog } from '../dialogs/DebugDialog'

export type DialogType =
| 'cmdk'
Expand All @@ -46,6 +47,7 @@ export type DialogType =
| 'filesSearch'
| 'fileRename'
| 'keysCreate'
| 'debug'
| 'confirm'

type ConfirmProps = {
Expand Down Expand Up @@ -212,6 +214,7 @@ export function Dialogs() {
open={dialog === 'keysCreate'}
onOpenChange={onOpenChange}
/>
<DebugDialog open={dialog === 'debug'} onOpenChange={onOpenChange} />
<ConfirmDialog
open={dialog === 'confirm'}
params={confirm}
Expand Down
221 changes: 221 additions & 0 deletions apps/renterd/dialogs/DebugDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
import {
Dialog,
ConfigFields,
useOnInvalid,
FormSubmitButton,
useDialogFormHelpers,
FieldSwitch,
Label,
} from '@siafoundation/design-system'
import {
useAlerts,
useAutopilotConfig,
useContracts,
useSettingsGouging,
useSettingsPinned,
useSettingsUpload,
} from '@siafoundation/renterd-react'
import { useCallback, useMemo } from 'react'
import { useForm } from 'react-hook-form'
import JSZip from 'jszip'
import { saveAs } from 'file-saver'
import { useApp } from '../contexts/app'

function getDefaultValues() {
return {
contracts: true,
alerts: true,
autopilot: true,
gouging: true,
upload: true,
pinned: true,
}
}

type Values = ReturnType<typeof getDefaultValues>

function getFields(): ConfigFields<Values, never> {
return {
contracts: {
type: 'boolean',
title: 'Contracts',
validation: {},
},
alerts: {
type: 'boolean',
title: 'Alerts',
validation: {},
},
autopilot: {
type: 'boolean',
title: 'Autopilot',
validation: {},
},
gouging: {
type: 'boolean',
title: 'Gouging',
validation: {},
},
upload: {
type: 'boolean',
title: 'Upload',
validation: {},
},
pinned: {
type: 'boolean',
title: 'Pinned',
validation: {},
},
}
}

type Props = {
trigger?: React.ReactNode
open: boolean
onOpenChange: (val: boolean) => void
}

export function DebugDialog({ trigger, open, onOpenChange }: Props) {
const { isAutopilotEnabled } = useApp()
const defaultValues = useMemo(() => getDefaultValues(), [])

const contracts = useContracts()
const alerts = useAlerts({
params: {
limit: 1000,
},
})
const autopilot = useAutopilotConfig({
disabled: !isAutopilotEnabled,
})
const gouging = useSettingsGouging()
const upload = useSettingsUpload()
const pinned = useSettingsPinned()

const form = useForm({
mode: 'all',
defaultValues,
})

const { handleOpenChange } = useDialogFormHelpers({
form,
onOpenChange,
defaultValues,
initKey: [name],
})

const onValid = useCallback(
async (values: Values) => {
const zip = new JSZip()

// Add all the data to the zip file as JSON files.
if (values.alerts) {
zip.file('alerts.json', JSON.stringify(alerts.data, null, 2))
}
if (values.contracts) {
zip.file('contracts.json', JSON.stringify(contracts.data, null, 2))
}
if (isAutopilotEnabled && values.autopilot) {
zip.file('autopilot.json', JSON.stringify(autopilot.data, null, 2))
}
if (values.gouging) {
zip.file('gouging.json', JSON.stringify(gouging.data, null, 2))
}
if (values.upload) {
zip.file('upload.json', JSON.stringify(upload.data, null, 2))
}
if (values.pinned) {
zip.file('pinned.json', JSON.stringify(pinned.data, null, 2))
}

// Generate the zip file.
const blob = await zip.generateAsync({ type: 'blob' })

// Trigger download.
saveAs(blob, 'renterd-debug.zip')
},
[
isAutopilotEnabled,
contracts.data,
alerts.data,
autopilot.data,
gouging.data,
upload.data,
pinned.data,
]
)

const fields = useMemo(() => getFields(), [])

const onInvalid = useOnInvalid(fields)

return (
<Dialog
title="Generate a debug report"
description="Select which metadata files to include in the generated report."
trigger={trigger}
open={open}
onOpenChange={handleOpenChange}
contentVariants={{
className: 'w-[400px]',
}}
onSubmit={form.handleSubmit(onValid, onInvalid)}
>
<div className="flex flex-col gap-6 pt-4">
<div className="flex flex-col gap-2">
<Label size="14" color="subtle">
General
</Label>
<div className="flex gap-4">
<FieldSwitch
size="small"
form={form}
fields={fields}
name="contracts"
/>
<FieldSwitch
size="small"
form={form}
fields={fields}
name="alerts"
/>
</div>
</div>
<div className="flex flex-col gap-2">
<Label size="14" color="subtle">
Configuration
</Label>
<div className="flex gap-4">
{isAutopilotEnabled && (
<FieldSwitch
size="small"
form={form}
fields={fields}
name="autopilot"
/>
)}
<FieldSwitch
size="small"
form={form}
fields={fields}
name="gouging"
/>
<FieldSwitch
size="small"
form={form}
fields={fields}
name="upload"
/>
<FieldSwitch
size="small"
form={form}
fields={fields}
name="pinned"
/>
</div>
</div>
<FormSubmitButton form={form}>Generate</FormSubmitButton>
</div>
</Dialog>
)
}
2 changes: 2 additions & 0 deletions libs/design-system/src/core/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ export const Dialog = React.forwardRef<
<Content
title={title}
description={description}
titleVisuallyHidden={titleVisuallyHidden}
descriptionVisuallyHidden={descriptionVisuallyHidden}
contentVariants={contentVariants}
onSubmit={onSubmit}
controls={controls}
Expand Down
2 changes: 1 addition & 1 deletion libs/renterd-react/src/bus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -845,7 +845,7 @@ export function useSettingsUploadUpdate(
}

// params are required because omitting them returns a deprecated response structure
export function useAlerts(args: HookArgsSwr<AlertsParams, AlertsResponse>) {
export function useAlerts(args?: HookArgsSwr<AlertsParams, AlertsResponse>) {
return useGetSwr({ ...args, route: busAlertsRoute })
}

Expand Down
Loading

0 comments on commit 9810512

Please sign in to comment.