Skip to content

Commit febd03a

Browse files
committed
added /diagnostics page for installation / org list visibility
1 parent 9684fb7 commit febd03a

7 files changed

Lines changed: 744 additions & 0 deletions

File tree

backend/src/controllers/setup.controller.ts

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,140 @@ class SetupController {
112112
}
113113
}
114114

115+
async validateInstallations(req: Request, res: Response) {
116+
try {
117+
const diagnostics = {
118+
timestamp: new Date().toISOString(),
119+
appConnected: !!app.github.app,
120+
totalInstallations: app.github.installations.length,
121+
installations: [] as any[],
122+
errors: [] as string[],
123+
appInfo: null as any,
124+
summary: {
125+
validInstallations: 0,
126+
invalidInstallations: 0,
127+
organizationNames: [] as string[],
128+
accountTypes: {} as Record<string, number>
129+
}
130+
};
131+
132+
// Basic app validation
133+
if (!app.github.app) {
134+
diagnostics.errors.push('GitHub App is not initialized');
135+
return res.json(diagnostics);
136+
}
137+
138+
// Validate each installation
139+
for (let i = 0; i < app.github.installations.length; i++) {
140+
const { installation, octokit } = app.github.installations[i];
141+
142+
const installationDiag = {
143+
index: i,
144+
installationId: installation.id,
145+
accountLogin: installation.account?.login || 'MISSING',
146+
accountId: installation.account?.id || 'MISSING',
147+
accountType: installation.account?.type || 'MISSING',
148+
accountAvatarUrl: installation.account?.avatar_url || 'MISSING',
149+
appId: installation.app_id,
150+
appSlug: installation.app_slug,
151+
targetType: installation.target_type,
152+
permissions: installation.permissions,
153+
events: installation.events,
154+
createdAt: installation.created_at,
155+
updatedAt: installation.updated_at,
156+
suspendedAt: installation.suspended_at,
157+
suspendedBy: installation.suspended_by,
158+
hasOctokit: !!octokit,
159+
octokitTest: null as any,
160+
isValid: true,
161+
validationErrors: [] as string[]
162+
};
163+
164+
// Validate required fields
165+
if (!installation.account?.login) {
166+
installationDiag.isValid = false;
167+
installationDiag.validationErrors.push('Missing account.login (organization name)');
168+
}
169+
170+
if (!installation.account?.id) {
171+
installationDiag.isValid = false;
172+
installationDiag.validationErrors.push('Missing account.id');
173+
}
174+
175+
if (!installation.account?.type) {
176+
installationDiag.isValid = false;
177+
installationDiag.validationErrors.push('Missing account.type');
178+
}
179+
180+
// Test Octokit functionality
181+
if (octokit) {
182+
try {
183+
// Test basic API call with the installation's octokit
184+
const authTest = await octokit.rest.apps.getAuthenticated();
185+
installationDiag.octokitTest = {
186+
success: true,
187+
appName: authTest.data?.name || 'Unknown',
188+
appOwner: (authTest.data?.owner as any)?.login || 'Unknown',
189+
permissions: authTest.data?.permissions || {}
190+
};
191+
} catch (error) {
192+
installationDiag.octokitTest = {
193+
success: false,
194+
error: error instanceof Error ? error.message : 'Unknown error'
195+
};
196+
installationDiag.isValid = false;
197+
installationDiag.validationErrors.push(`Octokit API test failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
198+
}
199+
} else {
200+
installationDiag.isValid = false;
201+
installationDiag.validationErrors.push('Octokit instance is missing');
202+
}
203+
204+
// Update summary
205+
if (installationDiag.isValid) {
206+
diagnostics.summary.validInstallations++;
207+
if (installation.account?.login) {
208+
diagnostics.summary.organizationNames.push(installation.account.login);
209+
}
210+
} else {
211+
diagnostics.summary.invalidInstallations++;
212+
}
213+
214+
// Track account types
215+
const accountType = installation.account?.type || 'Unknown';
216+
diagnostics.summary.accountTypes[accountType] = (diagnostics.summary.accountTypes[accountType] || 0) + 1;
217+
218+
diagnostics.installations.push(installationDiag);
219+
}
220+
221+
// Additional app-level diagnostics
222+
try {
223+
const appInfo = await app.github.app.octokit.rest.apps.getAuthenticated();
224+
diagnostics.appInfo = {
225+
name: appInfo.data?.name || 'Unknown',
226+
description: appInfo.data?.description || 'No description',
227+
owner: (appInfo.data?.owner as any)?.login || 'Unknown',
228+
htmlUrl: appInfo.data?.html_url || 'Unknown',
229+
permissions: appInfo.data?.permissions || {},
230+
events: appInfo.data?.events || []
231+
};
232+
} catch (error) {
233+
diagnostics.errors.push(`Failed to get app info: ${error instanceof Error ? error.message : 'Unknown error'}`);
234+
}
235+
236+
// Sort organization names for easier reading
237+
diagnostics.summary.organizationNames.sort();
238+
239+
res.json(diagnostics);
240+
} catch (error) {
241+
logger.error('Installation validation failed', error);
242+
res.status(500).json({
243+
error: 'Installation validation failed',
244+
details: error instanceof Error ? error.message : 'Unknown error'
245+
});
246+
}
247+
}
248+
115249

116250
}
117251

backend/src/routes/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ router.get('/setup/manifest', setupController.getManifest);
5252
router.post('/setup/existing-app', setupController.addExistingApp);
5353
router.post('/setup/db', setupController.setupDB);
5454
router.get('/setup/status', setupController.setupStatus);
55+
router.get('/setup/validate-installations', setupController.validateInstallations);
5556

5657
router.get('/status', setupController.getStatus);
5758

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
**Value Modeling & Targeting Documentation**
2+
3+
This document outlines the rationale and logic behind the calculated metrics and targets used in the "Value Modeling & Targeting" dashboard. Each section corresponds to a category of metrics displayed in the dashboard.
4+
5+
---
6+
7+
### Org Metrics
8+
9+
**Seats**
10+
11+
- **Logic**: Based on the average total active seats(licenses) across top 10 recent days for the organization.
12+
- **Max**: Set to known total developer headcount.
13+
14+
**Adopted Devs**
15+
16+
- **Logic**: Average of total active developers using AI tooling (e.g. Copilot) from top 10 recent days for the organization.
17+
- **Max**: Total known developer count.
18+
19+
**Monthly Devs Reporting Time Savings**
20+
21+
- **Logic**: Count of distinct users who responded to time-savings surveys in past 30 days.
22+
- **Target**: Double the current, indicating intent to increase reporting.
23+
24+
**% of Seats Reporting Time Savings**
25+
26+
- **Logic**: (Monthly reporting users / total seats) \* 100.
27+
- **Purpose**: Shows how broadly time savings are captured.
28+
29+
**% of Seats Adopted**
30+
31+
- **Logic**: (Adopted Devs / Total Seats) \* 100.
32+
- **Use**: Adoption penetration relative to seat assignments.
33+
34+
**% of Max Adopted**
35+
36+
- **Logic**: (Adopted Devs / Total Developer Count) \* 100.
37+
- **Use**: Indicates potential ceiling for adoption.
38+
39+
---
40+
41+
### Daily User Metrics
42+
43+
**Daily IDE Suggestions**
44+
45+
- **Logic**: Averaged from last 5 valid daily records.
46+
- **Target/Max**: Calibrated based on observed high-performing usage.
47+
48+
**Daily IDE Acceptances**
49+
50+
- **Logic**: Suggestions \* 30% (default assumed acceptance rate).
51+
- **Target/Max**: Reflects healthy usage from productive orgs.
52+
53+
**Daily IDE Chat Turns**
54+
55+
- **Logic**: Average of chat turns per day per user from recent week.
56+
- **Target/Max**: Reflects healthy usage from productive orgs.
57+
58+
**Daily Dot-Com Chats**
59+
60+
- **Logic**: Chat Turns \* 33% (estimated portion on dot-com).
61+
- **Target**: Not yet set pending more data.
62+
63+
**Weekly PR Summaries**
64+
65+
- **Logic**: Total PR summaries / daily active users from last week.
66+
67+
**Weekly Time Saved**
68+
69+
- **Logic**: Weekly average from time savings reports per developer.\
70+
// Calculate weekly hours saved based on settings and average percent
71+
72+
const weeklyHours = hoursPerYear / 50; // Assuming 50 working weeks
73+
74+
const weeklyDevHours = weeklyHours \* (percentCoding / 100);
75+
76+
const avgWeeklyTimeSaved = weeklyDevHours \* (avgPercentTimeSaved / 100);
77+
78+
---
79+
80+
### Calculated Impacts
81+
82+
**Monthly Time Savings (hrs)**
83+
84+
- **Formula**: Adopted Devs \* Weekly Time Saved \* 4.
85+
- **Max**: 80 hours/month \* total seats (full work month).
86+
87+
**Annual Time Savings (Dollars)**
88+
89+
- **Formula**: Weekly Time Saved \* 50 weeks \* \$100/hr \* Adopted Devs.
90+
- **Note**: \$100/hr is assumed average developer cost.
91+
92+
**Productivity / Throughput Boost**
93+
94+
- **Formula**: ((40 + Weekly Time Saved) / 40 - 1) \* 100.
95+
- **Purpose**: Estimates effective increase in output per dev.
96+
97+
---
98+
99+
### Source of Calculations
100+
101+
All calculations were derived from one or more of:
102+
103+
- Recent metric exports (5 most recent days)
104+
- Monthly time-savings surveys
105+
- Developer seat and activity data
106+
- Assumed baselines (e.g., 40-hr weeks, \$100/hr, 70% acceptance)
107+
108+
Targets are either:
109+
110+
- Reflective of past top 10 org benchmarks
111+
- Strategically aspirational (2x current, known limits)
112+
113+
---
114+
115+
This model provides a structured framework for tracking usage, estimating impact, and guiding adoption investments.
116+
117+
> Edits can include notes on thresholds, cohort segmentation, or more nuanced modeling (e.g., p50/p90 range breakdowns).
118+

frontend/src/app/app.routes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { CopilotSeatComponent } from './main/copilot/copilot-seats/copilot-seat/
1515
import { DatabaseComponent } from './database/database.component';
1616
import { ErrorComponent } from './error/error.component';
1717
import { CopilotValueModelingComponent } from './main/copilot/copilot-value-modeling/copilot-value-modeling.component';
18+
import { MainDiagnosticsComponent } from './main/diagnostics/main-diagnostics.component';
1819

1920
export const routes: Routes = [
2021
{ path: 'setup', component: InstallComponent },
@@ -38,6 +39,7 @@ export const routes: Routes = [
3839
{ path: 'copilot/surveys/:id', component: CopilotSurveyComponent, title: 'Survey' },
3940
{ path: 'copilot/value-modeling', component: CopilotValueModelingComponent, title: 'Value Modeling' },
4041
{ path: 'settings', component: SettingsComponent, title: 'Settings' },
42+
{ path: 'diagnostics', component: MainDiagnosticsComponent, title: 'Diagnostics' },
4143
{ path: '', redirectTo: 'copilot', pathMatch: 'full' }
4244
]
4345
},

0 commit comments

Comments
 (0)