Skip to content

Commit

Permalink
Merge pull request #3 from austenstone/lots
Browse files Browse the repository at this point in the history
Lots
  • Loading branch information
austenstone authored Oct 23, 2024
2 parents bb4580b + 62d46f1 commit aa28010
Show file tree
Hide file tree
Showing 25 changed files with 512 additions and 118 deletions.
20 changes: 19 additions & 1 deletion backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
"proxy": "npx smee -t http://127.0.0.1:3000/api/github/webhooks"
},
"dependencies": {
"@octokit/webhooks": "^13.3.0",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"eventsource": "^2.0.2",
"express": "^4.17.1",
"mysql2": "^3.11.3",
"node-cron": "^3.0.3",
"octokit": "^4.0.2",
"sequelize": "^6.37.4",
"smee-client": "^2.0.3",
Expand All @@ -28,6 +28,7 @@
"@types/eventsource": "^1.1.15",
"@types/express": "^4.17.13",
"@types/node": "^16.11.7",
"@types/node-cron": "^3.0.11",
"nodemon": "^2.0.15",
"ts-node": "^10.9.2",
"tsx": "^4.19.1",
Expand Down
9 changes: 6 additions & 3 deletions backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@ import bodyParser from 'body-parser';
import apiRoutes from "./routes/index"
import { createNodeMiddleware } from "octokit";
import { setupWebhookListeners } from './controllers/webhook.controller';
import octokit from './services/octokit';
import github from './services/octokit';
import cors from 'cors';
import { queryCopilotMetrics } from './services/metrics.service';

const app = express();
const PORT = Number(process.env.PORT) || 3000;

app.use(cors());

// Setup webhook listeners
setupWebhookListeners(octokit);
app.use(createNodeMiddleware(octokit));
setupWebhookListeners(github);
app.use(createNodeMiddleware(github));

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
Expand All @@ -24,3 +25,5 @@ app.use('/api', apiRoutes);
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});

queryCopilotMetrics();
2 changes: 1 addition & 1 deletion backend/src/controllers/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from './survery.controller';
export * from './survery.controller';
36 changes: 36 additions & 0 deletions backend/src/controllers/metrics.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Request, Response } from 'express';
import { Metrics, Breakdown } from '../models/metrics.model';

class MetricsController {
// Get all metrics 📊
async getAllMetrics(req: Request, res: Response): Promise<void> {
try {
const metrics = await Metrics.findAll({
include: [Breakdown]
});
res.status(200).json(metrics); // 🎉 All metrics retrieved!
} catch (error) {
res.status(500).json(error); // 🚨 Error handling
}
}

// Get metrics by day 📅
async getMetricsByDay(req: Request, res: Response): Promise<void> {
try {
const { day } = req.params;
const metrics = await Metrics.findOne({
where: { day },
include: [Breakdown]
});
if (metrics) {
res.status(200).json(metrics); // 🎉 Metrics found!
} else {
res.status(404).json({ error: 'Metrics not found' }); // 🚨 Metrics not found
}
} catch (error) {
res.status(500).json(error); // 🚨 Error handling
}
}
}

export default new MetricsController();
28 changes: 25 additions & 3 deletions backend/src/controllers/survery.controller.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,35 @@
import { Request, Response } from 'express';
import Survey from '../models/survey.model';
import github from '../services/octokit';
import { Octokit } from 'octokit';

class SurveyController {
// Create a new survey 🎨
async createSurvey(req: Request, res: Response): Promise<void> {
try {
const { daytime, userId, usedCopilot, pctTimesaved, timeUsedFor } = req.body;
const survey = await Survey.create({ daytime, userId, usedCopilot, pctTimesaved, timeUsedFor });
res.status(201).json(survey); // 🎉 Survey created!
const survey = await Survey.create(req.body);
res.status(201).json(survey);
try {
const octokit = await github.getInstallationOctokit(
Number(process.env.GITHUB_APP_INSTALLATION_ID)
);
const comments = await octokit.rest.issues.listComments({
owner: survey.owner,
repo: survey.repo,
issue_number: survey.prNumber
})
const comment = comments.data.find(comment => comment.user?.login === 'demonstrate-value[bot]');
if (comment) {
octokit.rest.issues.updateComment({
owner: survey.owner,
repo: survey.repo,
comment_id: comment.id,
body: `Thanks for filling out the copilot survey @${survey.userId}!`
})
}
} catch (error) {
console.log('error', error);
}
} catch (error) {
res.status(500).json(error); // 🚨 Error handling
}
Expand Down
13 changes: 10 additions & 3 deletions backend/src/controllers/webhook.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,21 @@ webhooks.onAny(({ id, name, payload }) => {
// Process the webhook event here
});

export const setupWebhookListeners = (octokit: App) => {
octokit.webhooks.on("pull_request.opened", ({ octokit, payload }) => {
export const setupWebhookListeners = (github: App) => {
github.webhooks.on("pull_request.opened", ({ octokit, payload }) => {
console.log("Pull request opened", payload);

const surveyUrl = new URL(`${webUrl}/surveys/new`);
surveyUrl.searchParams.append('url', payload.pull_request.html_url);
surveyUrl.searchParams.append('author', payload.pull_request.user.login);

return octokit.rest.issues.createComment({
owner: payload.repository.owner.login,
repo: payload.repository.name,
issue_number: payload.pull_request.number,
body: `Hi @${payload.pull_request.user.login}! Please fill out this [survey](${webUrl}/copilot-survey) to help us understand if you leveraged Copilot in your pull request.`
body: `Hi @${payload.pull_request.user.login}! \
Please fill out this [survey](${surveyUrl.toString()}) \
to help us understand if you leveraged Copilot in your pull request.`
});
});
}
Expand Down
96 changes: 96 additions & 0 deletions backend/src/models/metrics.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { DataTypes } from 'sequelize';
import sequelize from '../database';

const Metrics = sequelize.define('Metrics', {
day: {
type: DataTypes.DATEONLY,
primaryKey: true,
allowNull: false,
},
totalSuggestionsCount: {
type: DataTypes.INTEGER,
allowNull: false,
},
totalAcceptancesCount: {
type: DataTypes.INTEGER,
allowNull: false,
},
totalLinesSuggested: {
type: DataTypes.INTEGER,
allowNull: false,
},
totalLinesAccepted: {
type: DataTypes.INTEGER,
allowNull: false,
},
totalActiveUsers: {
type: DataTypes.INTEGER,
allowNull: false,
},
totalChatAcceptances: {
type: DataTypes.INTEGER,
allowNull: false,
},
totalChatTurns: {
type: DataTypes.INTEGER,
allowNull: false,
},
totalActiveChatUsers: {
type: DataTypes.INTEGER,
allowNull: false,
},
}, {
tableName: 'metrics',
timestamps: false,
});

const Breakdown = sequelize.define('Breakdown', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
metricsDay: {
type: DataTypes.DATEONLY,
references: {
model: Metrics,
key: 'day',
},
},
language: {
type: DataTypes.STRING,
allowNull: false,
},
editor: {
type: DataTypes.STRING,
allowNull: false,
},
suggestionsCount: {
type: DataTypes.INTEGER,
allowNull: false,
},
acceptancesCount: {
type: DataTypes.INTEGER,
allowNull: false,
},
linesSuggested: {
type: DataTypes.INTEGER,
allowNull: false,
},
linesAccepted: {
type: DataTypes.INTEGER,
allowNull: false,
},
activeUsers: {
type: DataTypes.INTEGER,
allowNull: false,
},
}, {
tableName: 'breakdown',
timestamps: false,
});

Metrics.hasMany(Breakdown, { foreignKey: 'metricsDay' });
Breakdown.belongsTo(Metrics, { foreignKey: 'metricsDay' });

export { Metrics, Breakdown };
18 changes: 16 additions & 2 deletions backend/src/models/survey.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ class Survey extends Model {
public usedCopilot!: boolean;
public percentTimeSaved!: number;
public timeUsedFor!: string;
public owner!: string;
public repo!: string;
public prNumber!: number;
}

Survey.init({
Expand All @@ -21,7 +24,7 @@ Survey.init({
allowNull: false,
},
userId: {
type: DataTypes.INTEGER,
type: DataTypes.STRING,
allowNull: false,
},
usedCopilot: {
Expand All @@ -40,7 +43,18 @@ Survey.init({
type: DataTypes.STRING,
allowNull: false,
},

owner: {
type: DataTypes.STRING,
allowNull: false,
},
repo: {
type: DataTypes.STRING,
allowNull: false,
},
prNumber: {
type: DataTypes.INTEGER,
allowNull: false,
}
}, {
sequelize,
modelName: 'Survey',
Expand Down
14 changes: 9 additions & 5 deletions backend/src/routes/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { Router } from 'express';
import SurveyController from '../controllers/survery.controller';
import metricsController from '../controllers/metrics.controller';

const router = Router();

router.get('/', (req, res) => res.send('🎉 Welcome to the Survey API! 🚀✨'));

router.get('/get-survey', SurveyController.getAllSurveys);
router.post('/create-survey', SurveyController.createSurvey);
router.get('/get-survey/:id', SurveyController.getSurveyById);
router.put('/update-survey/:id', SurveyController.updateSurvey);
router.delete('/delete-survey/:id', SurveyController.deleteSurvey);
router.get('/survey', SurveyController.getAllSurveys);
router.post('/survey', SurveyController.createSurvey);
router.get('/survey/:id', SurveyController.getSurveyById);
router.put('/survey/:id', SurveyController.updateSurvey);
router.delete('/survey/:id', SurveyController.deleteSurvey);

router.get('/metrics', metricsController.getAllMetrics);
router.get('/metrics/:day', metricsController.getMetricsByDay);

export const webUrl = process.env.WEB_URL || 'http://localhost';

Expand Down
Loading

0 comments on commit aa28010

Please sign in to comment.