feat: add report analysis and visualization feature
- Add recharts library for data visualization - Implement ReportAnalyzer service to parse report data - Add report analysis IPC handler with Admin permission check - Add ReportAnalysisDialog component with charts - Add unit and E2E tests for report analyzer - Update Playwright config to exclude vitest-specific test files - Expand vitest config to include source code tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,8 @@ import { createLogger } from '../services/logger'
|
||||
import { ConfigManager } from '../services/config/config-manager'
|
||||
import { RustfsService } from '../services/rustfs'
|
||||
import { ListObjectsV2Command, S3Client } from '@aws-sdk/client-s3'
|
||||
import { SessionManager } from '../services/user/session-manager'
|
||||
import { ReportAnalyzer, type ParsedReportData } from '../services/report/report-analyzer'
|
||||
|
||||
const log = createLogger('ReportHandler')
|
||||
|
||||
@@ -177,4 +179,85 @@ export function registerReportHandlers(): void {
|
||||
}, 'report:download')
|
||||
}
|
||||
)
|
||||
|
||||
ipcMain.handle(
|
||||
IPC_CHANNELS.REPORT_ANALYZE_ALL,
|
||||
async (): Promise<IpcResult<ParsedReportData[]>> => {
|
||||
return withErrorHandling(async () => {
|
||||
// Check Admin permission
|
||||
const sessionManager = SessionManager.getInstance()
|
||||
if (!sessionManager.isAdmin()) {
|
||||
log.warn('Non-Admin user attempted to access report analysis')
|
||||
throw new Error('Unauthorized: Admin access required')
|
||||
}
|
||||
|
||||
const rustfs = getRustfsService()
|
||||
if (!rustfs) {
|
||||
throw new Error('RustFS is not configured or enabled')
|
||||
}
|
||||
|
||||
const configManager = ConfigManager.getInstance()
|
||||
const config = configManager.getConfig()
|
||||
|
||||
// Create S3Client to list objects
|
||||
const client = new S3Client({
|
||||
region: config.rustfs?.region || 'us-east-1',
|
||||
endpoint: config.rustfs?.endpoint || '',
|
||||
credentials: {
|
||||
accessKeyId: config.rustfs?.accessKey || '',
|
||||
secretAccessKey: config.rustfs?.secretKey || ''
|
||||
},
|
||||
forcePathStyle: true
|
||||
})
|
||||
|
||||
log.info('Fetching and analyzing all reports from RustFS')
|
||||
const input = {
|
||||
Bucket: config.rustfs?.bucket || '',
|
||||
Prefix: 'reports/cleaner/'
|
||||
}
|
||||
|
||||
const command = new ListObjectsV2Command(input)
|
||||
const response = await client.send(command)
|
||||
|
||||
const reports: Array<{ content: string; filename?: string }> = []
|
||||
|
||||
if (response.Contents) {
|
||||
// Download each report file
|
||||
for (const item of response.Contents) {
|
||||
if (item.Key && item.Key.endsWith('.md')) {
|
||||
const parts = item.Key.split('/')
|
||||
if (parts.length >= 4) {
|
||||
const filename = parts.slice(3).join('/')
|
||||
log.debug('Downloading report for analysis', { key: item.Key })
|
||||
const downloadResult = await rustfs.downloadFile(item.Key)
|
||||
|
||||
if (downloadResult.success) {
|
||||
reports.push({
|
||||
content: downloadResult.content.toString('utf-8'),
|
||||
filename
|
||||
})
|
||||
} else {
|
||||
log.warn('Failed to download report for analysis', {
|
||||
key: item.Key,
|
||||
error: downloadResult.error
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Analyze all reports
|
||||
const analyzer = new ReportAnalyzer()
|
||||
const analyzedData = analyzer.analyzeReports(reports)
|
||||
|
||||
log.info('Report analysis completed', {
|
||||
totalReports: reports.length,
|
||||
successfulAnalyses: analyzedData.length
|
||||
})
|
||||
|
||||
return analyzedData
|
||||
}, 'report:analyzeAll')
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user