WPE Usage Metrics Developer Guide
Architecture
src/
├── main/ # Main process (Node.js/Electron)
│ ├── index.ts # Entry point, registers IPC handlers
│ └── ipc-handlers.ts # IPC handlers for WPE API calls
├── renderer/ # Renderer process (React)
│ ├── index.tsx # Entry point, registers UI hooks
│ └── components/ # React components
│ ├── UsageMetricsPanel.tsx # Main panel component
│ ├── MetricCard.tsx # Individual metric display
│ ├── DateRangeSelector.tsx # Date range dropdown
│ └── icons.tsx # SVG icon components
└── common/ # Shared code
├── constants.ts # IPC channels, API config
├── types.ts # TypeScript interfaces
├── theme.ts # Theme detection utilities
└── formatters.ts # Number/date formatters
Key Concepts
IPC Communication
The addon uses Electron IPC for main/renderer communication:
// Main process (ipc-handlers.ts)
ipcMain.handle('wpe-usage-metrics:get-metrics', async (event, data) => {
// Fetch from WPE API
return { success: true, data: metrics };
});
// Renderer process (UsageMetricsPanel.tsx)
const response = await electron.ipcRenderer.invoke('wpe-usage-metrics:get-metrics', {
siteId: site.id,
installId: wpeSiteId,
dateRange: 30,
});
Authentication
The addon uses Basic Auth with WP Engine API credentials stored securely in the OS keychain:
// Get credentials from secure storage
const credentials = await secureStorage.getCredentials();
if (credentials) {
const encoded = Buffer.from(`${credentials.username}:${credentials.password}`).toString('base64');
return `Basic ${encoded}`;
}
Users configure their API credentials in Local Preferences → WP Engine Usage Metrics.
WP Engine API
The addon calls these WP Engine API endpoints:
| Endpoint | Purpose |
|---|---|
GET /installs |
List installs to find install ID from site ID |
GET /installs/{id}/usage |
Fetch usage metrics for an install |
Important: The remoteSiteId from Local’s hostConnections is a Site ID, not an Install ID. We must look up the Install ID first.
Theme Support
Dark mode is detected via DOM observation:
export function isDarkMode(): boolean {
return document.documentElement.classList.contains('Theme__Dark');
}
export function onThemeChange(callback: (isDark: boolean) => void): () => void {
const observer = new MutationObserver(() => callback(isDarkMode()));
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class']
});
return () => observer.disconnect();
}
React Components
Important: Use React class components, not hooks. Local’s React environment doesn’t support hooks properly.
// ✅ Correct
export class MyComponent extends React.Component<Props, State> {
componentDidMount() { ... }
render() { return <div>...</div>; }
}
// ❌ Wrong - hooks don't work
export function MyComponent() {
const [state, setState] = useState();
return <div>...</div>;
}
Adding New Features
Adding a New Metric
- Update
types.tswith the metric type - Add the metric to
UsageMetricsPanel.tsx - Add formatting in
formatters.tsif needed - Add an icon in
icons.tsx
Adding a New IPC Handler
- Add the channel name to
constants.ts:export const IPC_CHANNELS = { ... NEW_ACTION: `${ADDON_SLUG}:new-action`, }; -
Add request/response types to
types.ts - Implement the handler in
ipc-handlers.ts:ipcMain.handle(IPC_CHANNELS.NEW_ACTION, async (event, data) => { // Implementation }); - Call from renderer:
await electron.ipcRenderer.invoke(IPC_CHANNELS.NEW_ACTION, data);
Testing
# Run all tests
npm test
# Run with coverage
npm run test:coverage
# Watch mode
npm run test:watch
Mocking Local APIs
Tests use mocks in tests/__mocks__/:
// tests/__mocks__/local-main.ts
export const getServiceContainer = jest.fn().mockReturnValue({
cradle: {
siteData: { getSite: jest.fn() },
},
});
Debugging
Enable Console Logging
The addon logs to console with [WPE Usage] prefix:
console.log('[WPE Usage] Fetching metrics from:', url);
View logs in:
- Main process: Local’s DevTools (Help > Toggle Developer Tools)
- Renderer process: Site panel’s DevTools (right-click > Inspect)
Common Issues
| Issue | Cause | Solution |
|---|---|---|
| IPC handlers not called | Wrong channel name | Check IPC_CHANNELS constants match |
| 404 from API | Install ID vs Site ID | Use findInstallBySiteId() lookup |
| UI not updating | Missing state update | Call setState() after async operations |
| Theme not detected | DOM not ready | Wait for componentDidMount() |