feat: implement comprehensive diagnostics export system
Diagnostics Export Utility (diagnostics-export.ts): - ComprehensiveDiagnostics interface with detailed system information - System info: screen resolution, color depth, pixel ratio, viewport size - Network info: connection type, effective type, downlink, RTT - Storage info: localStorage, sessionStorage, IndexedDB, WebSQL availability - Performance metrics: load time, memory usage, connection type - Browser/WebView info: user agent, language, platform, hardware concurrency - Error context: error state, messages, timestamps - Plugin availability and status information DiagnosticsExporter class: - collectDiagnostics(): comprehensive data collection - exportAsJSON(): formatted JSON export - exportAsCSV(): CSV format for spreadsheet analysis - copyToClipboard(): clipboard integration with format selection - Performance timing and memory usage collection - Storage availability testing - Network connection detection StatusView Integration: - Updated to use comprehensive diagnostics collector - Enhanced diagnostics display with system information - Improved error handling and user feedback - Maintains existing functionality with added depth Key features: - Real-time system information collection - Multiple export formats (JSON, CSV) - Clipboard integration with user feedback - Performance metrics and timing - Comprehensive error context - Storage and network capability detection This completes the comprehensive diagnostics export from the implementation plan.
This commit is contained in:
318
test-apps/daily-notification-test/src/lib/diagnostics-export.ts
Normal file
318
test-apps/daily-notification-test/src/lib/diagnostics-export.ts
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
/**
|
||||||
|
* Diagnostics Export Utility
|
||||||
|
*
|
||||||
|
* Comprehensive diagnostics collection and export for the DailyNotification plugin
|
||||||
|
* Provides detailed system information for debugging and support
|
||||||
|
*
|
||||||
|
* @author Matthew Raymer
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
PermissionStatus,
|
||||||
|
NotificationStatus,
|
||||||
|
ExactAlarmStatus
|
||||||
|
} from './bridge'
|
||||||
|
|
||||||
|
export interface ComprehensiveDiagnostics {
|
||||||
|
// Basic app information
|
||||||
|
appVersion: string
|
||||||
|
platform: string
|
||||||
|
apiLevel: string
|
||||||
|
timezone: string
|
||||||
|
lastUpdated: string
|
||||||
|
|
||||||
|
// Core status matrix fields
|
||||||
|
postNotificationsGranted: boolean
|
||||||
|
exactAlarmGranted: boolean
|
||||||
|
channelEnabled: boolean
|
||||||
|
batteryOptimizationsIgnored: boolean
|
||||||
|
canScheduleNow: boolean
|
||||||
|
lastError?: string
|
||||||
|
|
||||||
|
// Detailed capabilities
|
||||||
|
capabilities: {
|
||||||
|
notificationStatus: NotificationStatus
|
||||||
|
permissions: PermissionStatus
|
||||||
|
exactAlarmStatus: ExactAlarmStatus
|
||||||
|
|
||||||
|
// Browser/WebView information
|
||||||
|
userAgent: string
|
||||||
|
language: string
|
||||||
|
platform: string
|
||||||
|
cookieEnabled: boolean
|
||||||
|
onLine: boolean
|
||||||
|
hardwareConcurrency: number
|
||||||
|
maxTouchPoints: number
|
||||||
|
|
||||||
|
// Timing information
|
||||||
|
timestamp: number
|
||||||
|
timezoneOffset: number
|
||||||
|
|
||||||
|
// Plugin availability
|
||||||
|
pluginAvailable: boolean
|
||||||
|
|
||||||
|
// Error context
|
||||||
|
errorContext: {
|
||||||
|
hasError: boolean
|
||||||
|
errorMessage: string | null
|
||||||
|
lastCheckTime: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Performance metrics
|
||||||
|
performanceMetrics: {
|
||||||
|
loadTime: number
|
||||||
|
memoryUsage?: number
|
||||||
|
connectionType?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// System information
|
||||||
|
systemInfo: {
|
||||||
|
screenResolution: string
|
||||||
|
colorDepth: number
|
||||||
|
pixelRatio: number
|
||||||
|
viewportSize: string
|
||||||
|
devicePixelRatio: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// Network information
|
||||||
|
networkInfo: {
|
||||||
|
connectionType: string
|
||||||
|
effectiveType?: string
|
||||||
|
downlink?: number
|
||||||
|
rtt?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// Storage information
|
||||||
|
storageInfo: {
|
||||||
|
localStorageAvailable: boolean
|
||||||
|
sessionStorageAvailable: boolean
|
||||||
|
indexedDBAvailable: boolean
|
||||||
|
webSQLAvailable: boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DiagnosticsExporter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect comprehensive diagnostics
|
||||||
|
*/
|
||||||
|
async collectDiagnostics(
|
||||||
|
notificationStatus: NotificationStatus,
|
||||||
|
permissions: PermissionStatus,
|
||||||
|
exactAlarmStatus: ExactAlarmStatus,
|
||||||
|
pluginAvailable: boolean
|
||||||
|
): Promise<ComprehensiveDiagnostics> {
|
||||||
|
|
||||||
|
const startTime = performance.now()
|
||||||
|
|
||||||
|
// Collect system information
|
||||||
|
const systemInfo = this.collectSystemInfo()
|
||||||
|
|
||||||
|
// Collect network information
|
||||||
|
const networkInfo = this.collectNetworkInfo()
|
||||||
|
|
||||||
|
// Collect storage information
|
||||||
|
const storageInfo = this.collectStorageInfo()
|
||||||
|
|
||||||
|
// Calculate performance metrics
|
||||||
|
const loadTime = performance.now() - startTime
|
||||||
|
|
||||||
|
return {
|
||||||
|
appVersion: '1.0.0', // TODO: Get from app info
|
||||||
|
platform: 'Android', // TODO: Detect platform
|
||||||
|
apiLevel: 'Unknown', // TODO: Get from device info
|
||||||
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||||
|
lastUpdated: new Date().toLocaleString(),
|
||||||
|
postNotificationsGranted: permissions.notifications === 'granted',
|
||||||
|
exactAlarmGranted: exactAlarmStatus.enabled,
|
||||||
|
channelEnabled: notificationStatus.isEnabled,
|
||||||
|
batteryOptimizationsIgnored: false, // TODO: Check battery optimization status
|
||||||
|
canScheduleNow: notificationStatus.isEnabled && permissions.notifications === 'granted',
|
||||||
|
lastError: notificationStatus.error,
|
||||||
|
capabilities: {
|
||||||
|
notificationStatus,
|
||||||
|
permissions,
|
||||||
|
exactAlarmStatus,
|
||||||
|
userAgent: navigator.userAgent,
|
||||||
|
language: navigator.language,
|
||||||
|
platform: navigator.platform,
|
||||||
|
cookieEnabled: navigator.cookieEnabled,
|
||||||
|
onLine: navigator.onLine,
|
||||||
|
hardwareConcurrency: navigator.hardwareConcurrency || 0,
|
||||||
|
maxTouchPoints: navigator.maxTouchPoints || 0,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
timezoneOffset: new Date().getTimezoneOffset(),
|
||||||
|
pluginAvailable,
|
||||||
|
errorContext: {
|
||||||
|
hasError: !!notificationStatus.error,
|
||||||
|
errorMessage: notificationStatus.error || null,
|
||||||
|
lastCheckTime: new Date().toISOString()
|
||||||
|
},
|
||||||
|
performanceMetrics: {
|
||||||
|
loadTime,
|
||||||
|
memoryUsage: this.getMemoryUsage(),
|
||||||
|
connectionType: networkInfo.connectionType
|
||||||
|
}
|
||||||
|
},
|
||||||
|
systemInfo,
|
||||||
|
networkInfo,
|
||||||
|
storageInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect system information
|
||||||
|
*/
|
||||||
|
private collectSystemInfo() {
|
||||||
|
return {
|
||||||
|
screenResolution: `${screen.width}x${screen.height}`,
|
||||||
|
colorDepth: screen.colorDepth,
|
||||||
|
pixelRatio: window.devicePixelRatio || 1,
|
||||||
|
viewportSize: `${window.innerWidth}x${window.innerHeight}`,
|
||||||
|
devicePixelRatio: window.devicePixelRatio || 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect network information
|
||||||
|
*/
|
||||||
|
private collectNetworkInfo() {
|
||||||
|
const connection = (navigator as any).connection || (navigator as any).mozConnection || (navigator as any).webkitConnection
|
||||||
|
|
||||||
|
return {
|
||||||
|
connectionType: connection?.type || 'unknown',
|
||||||
|
effectiveType: connection?.effectiveType,
|
||||||
|
downlink: connection?.downlink,
|
||||||
|
rtt: connection?.rtt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect storage information
|
||||||
|
*/
|
||||||
|
private collectStorageInfo() {
|
||||||
|
return {
|
||||||
|
localStorageAvailable: this.isStorageAvailable('localStorage'),
|
||||||
|
sessionStorageAvailable: this.isStorageAvailable('sessionStorage'),
|
||||||
|
indexedDBAvailable: this.isStorageAvailable('indexedDB'),
|
||||||
|
webSQLAvailable: this.isStorageAvailable('openDatabase')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if storage is available
|
||||||
|
*/
|
||||||
|
private isStorageAvailable(type: string): boolean {
|
||||||
|
try {
|
||||||
|
const storage = (window as any)[type]
|
||||||
|
if (!storage) return false
|
||||||
|
|
||||||
|
if (type === 'indexedDB') {
|
||||||
|
return !!storage
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'openDatabase') {
|
||||||
|
return !!storage
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test localStorage/sessionStorage
|
||||||
|
const test = '__storage_test__'
|
||||||
|
storage.setItem(test, test)
|
||||||
|
storage.removeItem(test)
|
||||||
|
return true
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get memory usage (if available)
|
||||||
|
*/
|
||||||
|
private getMemoryUsage(): number | undefined {
|
||||||
|
const memory = (performance as any).memory
|
||||||
|
return memory ? memory.usedJSHeapSize : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export diagnostics as JSON string
|
||||||
|
*/
|
||||||
|
exportAsJSON(diagnostics: ComprehensiveDiagnostics): string {
|
||||||
|
return JSON.stringify(diagnostics, null, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export diagnostics as CSV
|
||||||
|
*/
|
||||||
|
exportAsCSV(diagnostics: ComprehensiveDiagnostics): string {
|
||||||
|
const rows = [
|
||||||
|
['Field', 'Value'],
|
||||||
|
['App Version', diagnostics.appVersion],
|
||||||
|
['Platform', diagnostics.platform],
|
||||||
|
['API Level', diagnostics.apiLevel],
|
||||||
|
['Timezone', diagnostics.timezone],
|
||||||
|
['Last Updated', diagnostics.lastUpdated],
|
||||||
|
['Post Notifications Granted', diagnostics.postNotificationsGranted.toString()],
|
||||||
|
['Exact Alarm Granted', diagnostics.exactAlarmGranted.toString()],
|
||||||
|
['Channel Enabled', diagnostics.channelEnabled.toString()],
|
||||||
|
['Battery Optimizations Ignored', diagnostics.batteryOptimizationsIgnored.toString()],
|
||||||
|
['Can Schedule Now', diagnostics.canScheduleNow.toString()],
|
||||||
|
['Last Error', diagnostics.lastError || 'None'],
|
||||||
|
['Plugin Available', diagnostics.capabilities.pluginAvailable.toString()],
|
||||||
|
['User Agent', diagnostics.capabilities.userAgent],
|
||||||
|
['Language', diagnostics.capabilities.language],
|
||||||
|
['Platform', diagnostics.capabilities.platform],
|
||||||
|
['Online', diagnostics.capabilities.onLine.toString()],
|
||||||
|
['Hardware Concurrency', diagnostics.capabilities.hardwareConcurrency.toString()],
|
||||||
|
['Max Touch Points', diagnostics.capabilities.maxTouchPoints.toString()],
|
||||||
|
['Screen Resolution', diagnostics.systemInfo.screenResolution],
|
||||||
|
['Color Depth', diagnostics.systemInfo.colorDepth.toString()],
|
||||||
|
['Pixel Ratio', diagnostics.systemInfo.pixelRatio.toString()],
|
||||||
|
['Viewport Size', diagnostics.systemInfo.viewportSize],
|
||||||
|
['Connection Type', diagnostics.networkInfo.connectionType],
|
||||||
|
['Local Storage Available', diagnostics.storageInfo.localStorageAvailable.toString()],
|
||||||
|
['Session Storage Available', diagnostics.storageInfo.sessionStorageAvailable.toString()],
|
||||||
|
['IndexedDB Available', diagnostics.storageInfo.indexedDBAvailable.toString()],
|
||||||
|
['WebSQL Available', diagnostics.storageInfo.webSQLAvailable.toString()]
|
||||||
|
]
|
||||||
|
|
||||||
|
return rows.map(row => row.map(cell => `"${cell}"`).join(',')).join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy diagnostics to clipboard
|
||||||
|
*/
|
||||||
|
async copyToClipboard(diagnostics: ComprehensiveDiagnostics, format: 'json' | 'csv' = 'json'): Promise<void> {
|
||||||
|
const content = format === 'json'
|
||||||
|
? this.exportAsJSON(diagnostics)
|
||||||
|
: this.exportAsCSV(diagnostics)
|
||||||
|
|
||||||
|
await navigator.clipboard.writeText(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Singleton instance
|
||||||
|
export const diagnosticsExporter = new DiagnosticsExporter()
|
||||||
|
|
||||||
|
// Utility functions
|
||||||
|
export async function collectDiagnostics(
|
||||||
|
notificationStatus: NotificationStatus,
|
||||||
|
permissions: PermissionStatus,
|
||||||
|
exactAlarmStatus: ExactAlarmStatus,
|
||||||
|
pluginAvailable: boolean
|
||||||
|
): Promise<ComprehensiveDiagnostics> {
|
||||||
|
return diagnosticsExporter.collectDiagnostics(notificationStatus, permissions, exactAlarmStatus, pluginAvailable)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function exportDiagnosticsAsJSON(diagnostics: ComprehensiveDiagnostics): string {
|
||||||
|
return diagnosticsExporter.exportAsJSON(diagnostics)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function exportDiagnosticsAsCSV(diagnostics: ComprehensiveDiagnostics): string {
|
||||||
|
return diagnosticsExporter.exportAsCSV(diagnostics)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function copyDiagnosticsToClipboard(diagnostics: ComprehensiveDiagnostics, format: 'json' | 'csv' = 'json'): Promise<void> {
|
||||||
|
return diagnosticsExporter.copyToClipboard(diagnostics, format)
|
||||||
|
}
|
||||||
@@ -89,6 +89,7 @@
|
|||||||
import { Vue, Component, toNative } from 'vue-facing-decorator'
|
import { Vue, Component, toNative } from 'vue-facing-decorator'
|
||||||
import StatusCard from '../components/cards/StatusCard.vue'
|
import StatusCard from '../components/cards/StatusCard.vue'
|
||||||
import { createTypedPlugin, type PermissionStatus, type NotificationStatus, type ExactAlarmStatus } from '../lib/typed-plugin'
|
import { createTypedPlugin, type PermissionStatus, type NotificationStatus, type ExactAlarmStatus } from '../lib/typed-plugin'
|
||||||
|
import { collectDiagnostics, copyDiagnosticsToClipboard, type ComprehensiveDiagnostics } from '../lib/diagnostics-export'
|
||||||
|
|
||||||
interface StatusItem {
|
interface StatusItem {
|
||||||
key: string
|
key: string
|
||||||
@@ -102,20 +103,8 @@ interface StatusItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Diagnostics {
|
// Use ComprehensiveDiagnostics from diagnostics-export
|
||||||
appVersion: string
|
type Diagnostics = ComprehensiveDiagnostics
|
||||||
platform: string
|
|
||||||
apiLevel: string
|
|
||||||
timezone: string
|
|
||||||
lastUpdated: string
|
|
||||||
postNotificationsGranted: boolean
|
|
||||||
exactAlarmGranted: boolean
|
|
||||||
channelEnabled: boolean
|
|
||||||
batteryOptimizationsIgnored: boolean
|
|
||||||
canScheduleNow: boolean
|
|
||||||
lastError?: string
|
|
||||||
capabilities: Record<string, any>
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
@@ -136,7 +125,40 @@ class StatusView extends Vue {
|
|||||||
channelEnabled: false,
|
channelEnabled: false,
|
||||||
batteryOptimizationsIgnored: false,
|
batteryOptimizationsIgnored: false,
|
||||||
canScheduleNow: false,
|
canScheduleNow: false,
|
||||||
capabilities: {}
|
lastError: undefined,
|
||||||
|
capabilities: {
|
||||||
|
notificationStatus: { isEnabled: false, isScheduled: false, pending: false },
|
||||||
|
permissions: { notifications: 'denied', notificationsEnabled: false },
|
||||||
|
exactAlarmStatus: { enabled: false, supported: false },
|
||||||
|
userAgent: '',
|
||||||
|
language: '',
|
||||||
|
platform: '',
|
||||||
|
cookieEnabled: false,
|
||||||
|
onLine: false,
|
||||||
|
hardwareConcurrency: 0,
|
||||||
|
maxTouchPoints: 0,
|
||||||
|
timestamp: 0,
|
||||||
|
timezoneOffset: 0,
|
||||||
|
pluginAvailable: false,
|
||||||
|
errorContext: { hasError: false, errorMessage: null, lastCheckTime: '' },
|
||||||
|
performanceMetrics: { loadTime: 0 }
|
||||||
|
},
|
||||||
|
systemInfo: {
|
||||||
|
screenResolution: '0x0',
|
||||||
|
colorDepth: 0,
|
||||||
|
pixelRatio: 1,
|
||||||
|
viewportSize: '0x0',
|
||||||
|
devicePixelRatio: 1
|
||||||
|
},
|
||||||
|
networkInfo: {
|
||||||
|
connectionType: 'unknown'
|
||||||
|
},
|
||||||
|
storageInfo: {
|
||||||
|
localStorageAvailable: false,
|
||||||
|
sessionStorageAvailable: false,
|
||||||
|
indexedDBAvailable: false,
|
||||||
|
webSQLAvailable: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get statusItems(): StatusItem[] {
|
get statusItems(): StatusItem[] {
|
||||||
@@ -228,25 +250,13 @@ class StatusView extends Vue {
|
|||||||
typedPlugin.getExactAlarmStatus()
|
typedPlugin.getExactAlarmStatus()
|
||||||
])
|
])
|
||||||
|
|
||||||
// Update diagnostics
|
// Collect comprehensive diagnostics
|
||||||
this.diagnostics = {
|
this.diagnostics = await collectDiagnostics(
|
||||||
appVersion: '1.0.0', // TODO: Get from app info
|
notificationStatus,
|
||||||
platform: 'Android', // TODO: Detect platform
|
permissions,
|
||||||
apiLevel: 'Unknown', // TODO: Get from device info
|
exactAlarmStatus,
|
||||||
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
!!typedPlugin
|
||||||
lastUpdated: new Date().toLocaleString(),
|
)
|
||||||
postNotificationsGranted: permissions.notifications === 'granted',
|
|
||||||
exactAlarmGranted: exactAlarmStatus.enabled,
|
|
||||||
channelEnabled: notificationStatus.isEnabled,
|
|
||||||
batteryOptimizationsIgnored: false, // TODO: Check battery optimization status
|
|
||||||
canScheduleNow: notificationStatus.isEnabled && permissions.notifications === 'granted',
|
|
||||||
lastError: notificationStatus.error,
|
|
||||||
capabilities: {
|
|
||||||
notificationStatus,
|
|
||||||
permissions,
|
|
||||||
exactAlarmStatus
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('✅ Status matrix refreshed successfully')
|
console.log('✅ Status matrix refreshed successfully')
|
||||||
|
|
||||||
@@ -299,8 +309,8 @@ class StatusView extends Vue {
|
|||||||
|
|
||||||
async exportDiagnostics() {
|
async exportDiagnostics() {
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(this.diagnosticsJson)
|
await copyDiagnosticsToClipboard(this.diagnostics, 'json')
|
||||||
console.log('📋 Diagnostics copied to clipboard')
|
console.log('📋 Comprehensive diagnostics copied to clipboard')
|
||||||
|
|
||||||
// Show success feedback
|
// Show success feedback
|
||||||
const button = document.querySelector('.action-button.export') as HTMLButtonElement
|
const button = document.querySelector('.action-button.export') as HTMLButtonElement
|
||||||
|
|||||||
Reference in New Issue
Block a user