Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions backend/src/controllers/setup.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,140 @@
}
}

async validateInstallations(req: Request, res: Response) {
try {
const diagnostics = {
timestamp: new Date().toISOString(),
appConnected: !!app.github.app,
totalInstallations: app.github.installations.length,
installations: [] as any[],

Check failure

Code scanning / ESLint

Disallow the `any` type Error

Unexpected any. Specify a different type.
errors: [] as string[],
appInfo: null as any,

Check failure

Code scanning / ESLint

Disallow the `any` type Error

Unexpected any. Specify a different type.
summary: {
validInstallations: 0,
invalidInstallations: 0,
organizationNames: [] as string[],
accountTypes: {} as Record<string, number>
}
};

// Basic app validation
if (!app.github.app) {
diagnostics.errors.push('GitHub App is not initialized');
return res.json(diagnostics);
}

// Validate each installation
for (let i = 0; i < app.github.installations.length; i++) {
const { installation, octokit } = app.github.installations[i];

const installationDiag = {
index: i,
installationId: installation.id,
accountLogin: installation.account?.login || 'MISSING',
accountId: installation.account?.id || 'MISSING',
accountType: installation.account?.type || 'MISSING',
accountAvatarUrl: installation.account?.avatar_url || 'MISSING',
appId: installation.app_id,
appSlug: installation.app_slug,
targetType: installation.target_type,
permissions: installation.permissions,
events: installation.events,
createdAt: installation.created_at,
updatedAt: installation.updated_at,
suspendedAt: installation.suspended_at,
suspendedBy: installation.suspended_by,
hasOctokit: !!octokit,
octokitTest: null as any,

Check failure

Code scanning / ESLint

Disallow the `any` type Error

Unexpected any. Specify a different type.
isValid: true,
validationErrors: [] as string[]
};

// Validate required fields
if (!installation.account?.login) {
installationDiag.isValid = false;
installationDiag.validationErrors.push('Missing account.login (organization name)');
}

if (!installation.account?.id) {
installationDiag.isValid = false;
installationDiag.validationErrors.push('Missing account.id');
}

if (!installation.account?.type) {
installationDiag.isValid = false;
installationDiag.validationErrors.push('Missing account.type');
}

// Test Octokit functionality
if (octokit) {
try {
// Test basic API call with the installation's octokit
const authTest = await octokit.rest.apps.getAuthenticated();
installationDiag.octokitTest = {
success: true,
appName: authTest.data?.name || 'Unknown',
appOwner: (authTest.data?.owner as any)?.login || 'Unknown',

Check failure

Code scanning / ESLint

Disallow the `any` type Error

Unexpected any. Specify a different type.
permissions: authTest.data?.permissions || {}
};
} catch (error) {
installationDiag.octokitTest = {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
installationDiag.isValid = false;
installationDiag.validationErrors.push(`Octokit API test failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
} else {
installationDiag.isValid = false;
installationDiag.validationErrors.push('Octokit instance is missing');
}

// Update summary
if (installationDiag.isValid) {
diagnostics.summary.validInstallations++;
if (installation.account?.login) {
diagnostics.summary.organizationNames.push(installation.account.login);
}
} else {
diagnostics.summary.invalidInstallations++;
}

// Track account types
const accountType = installation.account?.type || 'Unknown';
diagnostics.summary.accountTypes[accountType] = (diagnostics.summary.accountTypes[accountType] || 0) + 1;

diagnostics.installations.push(installationDiag);
}

// Additional app-level diagnostics
try {
const appInfo = await app.github.app.octokit.rest.apps.getAuthenticated();
diagnostics.appInfo = {
name: appInfo.data?.name || 'Unknown',
description: appInfo.data?.description || 'No description',
owner: (appInfo.data?.owner as any)?.login || 'Unknown',

Check failure

Code scanning / ESLint

Disallow the `any` type Error

Unexpected any. Specify a different type.
htmlUrl: appInfo.data?.html_url || 'Unknown',
permissions: appInfo.data?.permissions || {},
events: appInfo.data?.events || []
};
} catch (error) {
diagnostics.errors.push(`Failed to get app info: ${error instanceof Error ? error.message : 'Unknown error'}`);
}

// Sort organization names for easier reading
diagnostics.summary.organizationNames.sort();

res.json(diagnostics);
} catch (error) {
logger.error('Installation validation failed', error);
res.status(500).json({
error: 'Installation validation failed',
details: error instanceof Error ? error.message : 'Unknown error'
});
}
}


}

Expand Down
1 change: 1 addition & 0 deletions backend/src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ router.get('/setup/manifest', setupController.getManifest);
router.post('/setup/existing-app', setupController.addExistingApp);
router.post('/setup/db', setupController.setupDB);
router.get('/setup/status', setupController.setupStatus);
router.get('/setup/validate-installations', setupController.validateInstallations);

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

Expand Down
118 changes: 118 additions & 0 deletions backend/src/services/value_modeling_doc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
**Value Modeling & Targeting Documentation**

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.

---

### Org Metrics

**Seats**

- **Logic**: Based on the average total active seats(licenses) across top 10 recent days for the organization.
- **Max**: Set to known total developer headcount.

**Adopted Devs**

- **Logic**: Average of total active developers using AI tooling (e.g. Copilot) from top 10 recent days for the organization.
- **Max**: Total known developer count.

**Monthly Devs Reporting Time Savings**

- **Logic**: Count of distinct users who responded to time-savings surveys in past 30 days.
- **Target**: Double the current, indicating intent to increase reporting.

**% of Seats Reporting Time Savings**

- **Logic**: (Monthly reporting users / total seats) \* 100.
- **Purpose**: Shows how broadly time savings are captured.

**% of Seats Adopted**

- **Logic**: (Adopted Devs / Total Seats) \* 100.
- **Use**: Adoption penetration relative to seat assignments.

**% of Max Adopted**

- **Logic**: (Adopted Devs / Total Developer Count) \* 100.
- **Use**: Indicates potential ceiling for adoption.

---

### Daily User Metrics

**Daily IDE Suggestions**

- **Logic**: Averaged from last 5 valid daily records.
- **Target/Max**: Calibrated based on observed high-performing usage.

**Daily IDE Acceptances**

- **Logic**: Suggestions \* 30% (default assumed acceptance rate).
- **Target/Max**: Reflects healthy usage from productive orgs.

**Daily IDE Chat Turns**

- **Logic**: Average of chat turns per day per user from recent week.
- **Target/Max**: Reflects healthy usage from productive orgs.

**Daily Dot-Com Chats**

- **Logic**: Chat Turns \* 33% (estimated portion on dot-com).
- **Target**: Not yet set pending more data.

**Weekly PR Summaries**

- **Logic**: Total PR summaries / daily active users from last week.

**Weekly Time Saved**

- **Logic**: Weekly average from time savings reports per developer.\
// Calculate weekly hours saved based on settings and average percent

const weeklyHours = hoursPerYear / 50; // Assuming 50 working weeks

const weeklyDevHours = weeklyHours \* (percentCoding / 100);

const avgWeeklyTimeSaved = weeklyDevHours \* (avgPercentTimeSaved / 100);

---

### Calculated Impacts

**Monthly Time Savings (hrs)**

- **Formula**: Adopted Devs \* Weekly Time Saved \* 4.
- **Max**: 80 hours/month \* total seats (full work month).

**Annual Time Savings (Dollars)**

- **Formula**: Weekly Time Saved \* 50 weeks \* \$100/hr \* Adopted Devs.
- **Note**: \$100/hr is assumed average developer cost.

**Productivity / Throughput Boost**

- **Formula**: ((40 + Weekly Time Saved) / 40 - 1) \* 100.
- **Purpose**: Estimates effective increase in output per dev.

---

### Source of Calculations

All calculations were derived from one or more of:

- Recent metric exports (5 most recent days)
- Monthly time-savings surveys
- Developer seat and activity data
- Assumed baselines (e.g., 40-hr weeks, \$100/hr, 70% acceptance)

Targets are either:

- Reflective of past top 10 org benchmarks
- Strategically aspirational (2x current, known limits)

---

This model provides a structured framework for tracking usage, estimating impact, and guiding adoption investments.

> Edits can include notes on thresholds, cohort segmentation, or more nuanced modeling (e.g., p50/p90 range breakdowns).

2 changes: 2 additions & 0 deletions frontend/src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { CopilotSeatComponent } from './main/copilot/copilot-seats/copilot-seat/
import { DatabaseComponent } from './database/database.component';
import { ErrorComponent } from './error/error.component';
import { CopilotValueModelingComponent } from './main/copilot/copilot-value-modeling/copilot-value-modeling.component';
import { MainDiagnosticsComponent } from './main/diagnostics/main-diagnostics.component';

export const routes: Routes = [
{ path: 'setup', component: InstallComponent },
Expand All @@ -38,6 +39,7 @@ export const routes: Routes = [
{ path: 'copilot/surveys/:id', component: CopilotSurveyComponent, title: 'Survey' },
{ path: 'copilot/value-modeling', component: CopilotValueModelingComponent, title: 'Value Modeling' },
{ path: 'settings', component: SettingsComponent, title: 'Settings' },
{ path: 'diagnostics', component: MainDiagnosticsComponent, title: 'Diagnostics' },
{ path: '', redirectTo: 'copilot', pathMatch: 'full' }
]
},
Expand Down
Loading
Loading