Skip to content

Commit

Permalink
Added AI Label viewer
Browse files Browse the repository at this point in the history
  • Loading branch information
craigloewen-msft committed Oct 2, 2024
1 parent f490b3d commit f7ce83a
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 24 deletions.
1 change: 1 addition & 0 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ const IssueInfo = new Schema({
body: String,
userMentionsList: [String],
userCommentsList: [String],
aiLabels: [String],
}, { toJSON: { virtuals: true }, collation: { locale: "en_US", strength: 2 } });

IssueInfo.index({ 'repository_url': 1 });
Expand Down
147 changes: 147 additions & 0 deletions backendsrc/oneOffScriptHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,152 @@ module.exports = {
console.log(err);
}
}
},

async backFillAILabels(inStartDate, inIssueDetails, inRepoDetails, inRepoShortURL, inAILabelHandler) {
try {
let repo = await inRepoDetails.findOne({ shortURL: inRepoShortURL });

if (!repo) {
return;
}

let repoName = repo.shortURL.split("/").pop();

let totalIssues = await inIssueDetails.countDocuments({
repoRef: repo._id,
created_at: { $gte: inStartDate }
});

console.log(`Total issues for ${inRepoShortURL}: ${totalIssues}`)
let pageSize = 20;
let pages = Math.ceil(totalIssues / pageSize);

for (let i = 0; i < pages; i++) {
let issueList = await inIssueDetails.find({
repoRef: repo._id,
created_at: { $gte: inStartDate }
}).sort({ number: 1 }).skip((i * pageSize)).limit(pageSize);

let labelPromises = issueList.map(async (issue) => {
const aiLabelsString = await inAILabelHandler.generateAILabels(repoName, issue.title, issue.body);
const aiLabelsData = aiLabelsString.replace(/Output labels?: /i, "").split(/[\s,]+/);
const aiLabels = aiLabelsData.map((label) => {
return label.trim();
});
console.log(`Adding AI labels for ${inRepoShortURL}, issue ${issue.number}`);
return inIssueDetails.updateOne({ _id: issue._id }, { $set: { aiLabels: aiLabels } });
});

await Promise.all(labelPromises);

// If any promise wasn't returned true then throw an error
if (labelPromises.some((result) => !result)) {
throw new Error("Error updating AI labels");
}

let percentComplete = ((i + 1) / pages) * 100;
console.log(`Adding AI labels for ${inRepoShortURL} (${percentComplete.toFixed(2)}% complete)`);
}

} catch (err) {
console.log(err);
}
},

async getAILabelAccuracy(inStartDate, inEndDate, inIssueDetails, inRepoDetails, inRepoShortURL) {
try {
let returnStats = {};
let initLabelStats = function (inLabel) {
if (!returnStats[inLabel]) {
returnStats[inLabel] = {
total: 0,
correctlyApplied: 0,
incorrectlyApplied: 0,
missing: 0
};
}
}

let repo = await inRepoDetails.findOne({ shortURL: inRepoShortURL });

if (!repo) {
return;
}

let repoName = repo.shortURL.split("/").pop();

let totalIssues = await inIssueDetails.countDocuments({
repoRef: repo._id,
created_at: { $gte: inStartDate, $lte: inEndDate}
});

console.log(`Total issues for ${inRepoShortURL}: ${totalIssues}`)
let pageSize = 20;
let pages = Math.ceil(totalIssues / pageSize);

for (let i = 0; i < pages; i++) {
let issueList = await inIssueDetails.find({
repoRef: repo._id,
created_at: { $gte: inStartDate, $lte: inEndDate}
}).sort({ number: 1 }).skip((i * pageSize)).limit(pageSize);

let labelPromises = issueList.map(async (issue) => {
let aiLabels = issue.aiLabels;
let actualLabels = issue.labels.map((label) => {
return label.name;
});

for (let j = 0; j < aiLabels.length; j++) {
let label = aiLabels[j];
initLabelStats(label);

if (actualLabels.includes(label)) {
returnStats[label].correctlyApplied++;
} else {
returnStats[label].incorrectlyApplied++;
}
}

for (let j = 0 ; j < actualLabels.length; j++) {
let label = actualLabels[j];
initLabelStats(label);

if (!aiLabels.includes(label)) {
returnStats[label].missing++;
}
}
});

await Promise.all(labelPromises);

let percentComplete = ((i + 1) / pages) * 100;
console.log(`Adding AI labels for ${inRepoShortURL} (${percentComplete.toFixed(2)}% complete)`);
}

let finalReturnObject = [];

let returnStatsLabels = Object.keys(returnStats);
for (let i = 0; i < returnStatsLabels.length; i++) {
let returnLabel = returnStatsLabels[i];
let returnLabelData = returnStats[returnLabel];

let percentCorrect = returnLabelData.correctlyApplied * 100.0 / (returnLabelData.correctlyApplied + returnLabelData.incorrectlyApplied + returnLabelData.missing);

returnLabelData.percentCorrect = percentCorrect;
returnLabelData.name = returnLabel;
finalReturnObject.push(returnLabelData);
}

// Sort by highest correctly applied
finalReturnObject.sort((a, b) => {
return b.correctlyApplied - a.correctlyApplied;
});

return finalReturnObject;

} catch (err) {
console.log(err);
}
}
}
44 changes: 42 additions & 2 deletions backendsrc/webDataHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -1891,6 +1891,7 @@ class WebDataHandler {
"labels": 1,
"url": 1,
"created_at": 1,
"aiLabels": 1
}
},
]);
Expand Down Expand Up @@ -2020,6 +2021,7 @@ class WebDataHandler {
"labels": 1,
"url": 1,
"created_at": 1,
"aiLabels": 1
}
},
]);
Expand Down Expand Up @@ -2071,7 +2073,7 @@ class WebDataHandler {

if (!addedLabelList.includes(labelVisitor.name)) {
addedLabelList.push(labelVisitor.name);
let labelURL = labelVisitor.url.replace("https://api.github.com/repos/", "https://github.com/").replace("labels/","issues/?q=label%3A");
let labelURL = labelVisitor.url.replace("https://api.github.com/repos/", "https://github.com/").replace("labels/", "issues/?q=label%3A");
let labelNode = { id: labelVisitor.name, name: labelVisitor.name, totalVal: 1, graphVal: 1, url: labelURL, group: "label" };
nodeReturnDictionary[labelVisitor.name] = labelNode;
}
Expand All @@ -2080,6 +2082,44 @@ class WebDataHandler {
nodeReturnDictionary[labelVisitor.name].totalVal += issueNode.totalVal;
}
}

// Add aiLabels to node list
if (nodeVisitor.aiLabels) {
for (let j = 0; j < nodeVisitor.aiLabels.length; j++) {
let aiLabelVisitor = nodeVisitor.aiLabels[j];

let shouldProcessLabel = false;

// Check if we should process label by checking if it is in issueGraphLabelsToIncludeList
if (issueGraphLabelsToIncludeList.length != 0) {
for (let k = 0; k < issueGraphLabelsToIncludeList.length; k++) {
if (aiLabelVisitor.name == issueGraphLabelsToIncludeList[k]) {
shouldProcessLabel = true;
break;
}
}
} else {
// If no labels are specified then process all labels
shouldProcessLabel = true;
}

if (shouldProcessLabel) {

let aiLabelID = "aiLabel_" + aiLabelVisitor;

linkReturnList.push({ "source": nodeVisitor._id.toString(), "target": aiLabelID });

if (!addedLabelList.includes(aiLabelID)) {
addedLabelList.push(aiLabelID);
let labelNode = { id: aiLabelID, name: "✨ " + aiLabelVisitor, totalVal: 1, graphVal: 1, group: "aiLabel" };
nodeReturnDictionary[aiLabelID] = labelNode;
}

// Add the total value of the issue to the label
nodeReturnDictionary[aiLabelID].totalVal += issueNode.totalVal;
}
}
}
}

for (let i = 0; i < linkReturnList.length; i++) {
Expand Down Expand Up @@ -2146,7 +2186,7 @@ class WebDataHandler {
async getSimilarIssues(queryData) {
const { organizationName, repoName, issueTitle, issueBody } = queryData;

let issueDescription = GetDescription(issueTitle, issueBody)
let issueDescription = GetDescription(issueTitle, issueBody)

let dbRepoName = (organizationName + "/" + repoName).toLowerCase();

Expand Down
8 changes: 0 additions & 8 deletions label_lists/wsl.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@
"labelName": "ARM",
"labelDescription": "Issues related to ARM64. Should only be applied to issues that only affect ARM64 machines."
},
{
"labelName": "bug",
"labelDescription": "If WSL is not behaving as expected, and the error is in WSL itself, then it should be marked as a 'bug'."
},
{
"labelName": "console",
"labelDescription": "Issue related to the console output of WSL. This label should be applied to issues "
Expand Down Expand Up @@ -74,9 +70,5 @@
{
"labelName": "wsl1",
"labelDescription": "This label should only be applied to issues that explicitly mention they are using WSL 1."
},
{
"labelName": "wsl2",
"labelDescription": "This label should onoly be applied to issues that explicitly mention they are using WSL 2."
}
]
13 changes: 8 additions & 5 deletions temp.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ const IssueInfo = new Schema({
body: String,
userMentionsList: [String],
userCommentsList: [String],
aiLabels: [String],
}, { toJSON: { virtuals: true }, collation: { locale: "en_US", strength: 2 } });

IssueInfo.index({ 'repository_url': 1 });
Expand Down Expand Up @@ -339,12 +340,14 @@ const dataHandler = new WebDataHandler(RepoDetails, IssueDetails, UserDetails, s
const teamDataHandler = new TeamsDataHandler(RepoDetails, IssueDetails, UserDetails, siteIssueLabelDetails, IssueCommentDetails, IssueCommentMentionDetails,
IssueReadDetails, SearchQueryDetails, MentionQueryDetails, config.ghToken, TeamDetails, TeamTriageDetails, dataHandler);

async function main() {
let inputDate = new Date('2024-05-01');
let response = await RepoDetails.updateMany({}, { lastIssuesCompleteUpdate: inputDate });
let response2 = await RepoDetails.updateMany({}, { lastCommentsCompleteUpdate: inputDate });

console.log(response);
// Temp.js code only
const scriptHelpers = require('./backendsrc/oneOffScriptHelpers');

async function main() {
// await scriptHelpers.backFillAILabels(new Date('2024-07-20'), IssueDetails, RepoDetails, 'microsoft/powertoys', dataHandler.aiLabelHandler);
await scriptHelpers.getAILabelAccuracy(new Date('2024-08-01'), new Date('2024-09-01'), IssueDetails, RepoDetails, 'microsoft/powertoys');
console.log('Hello');
}

main()
2 changes: 1 addition & 1 deletion webinterface/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"serve": "vue-cli-service serve --host 0.0.0.0",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
Expand Down
2 changes: 1 addition & 1 deletion webinterface/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<div class="container footer-box">
<div class="row">
<div class="col-6">
<span class="text-muted">GitGudIssues 2021 v1.38</span>
<span class="text-muted">GitGudIssues 2021 v1.39</span>
</div>
<div class="col-6 footer-box-right">
<a href="https://github.com/craigloewen-msft/GitGudIssues">Open Sourced at:
Expand Down
17 changes: 10 additions & 7 deletions webinterface/src/views/RepoIssueGraph.vue
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,10 @@ export default {
name: "RepoIssueGraph",
data() {
return {
selected: ["labels"],
selected: ["labels", "aiLabels"],
options: [
{ text: "Include Labels", value: "labels" },
{ text: "Include AI Labels", value: "aiLabels" },
],
loading: false,
inputQuery: {
Expand Down Expand Up @@ -239,11 +240,12 @@ export default {
this.filteredData = JSON.parse(JSON.stringify(this.myData));
this.nodeList = {};
const isLabelsSelected = this.selected.includes("labels");
const isAILabelsSelected = this.selected.includes("aiLabels");
// Get searchable node list
for (let i = this.filteredData.nodes.length - 1; i >= 0; i--) {
let nodeVisitor = this.filteredData.nodes[i];
if (!isLabelsSelected && nodeVisitor.group === "label") {
if ((!isLabelsSelected && nodeVisitor.group === "label") || (!isAILabelsSelected && nodeVisitor.group === "aiLabel")) {
this.filteredData.nodes.splice(i, 1);
} else {
this.nodeList[nodeVisitor.id] = nodeVisitor;
Expand All @@ -261,14 +263,12 @@ export default {
let source = this.nodeList[linkVisitor.source];
let target = this.nodeList[linkVisitor.target];
if (
!isLabelsSelected &&
(source === undefined || target === undefined)
) {
if (source === undefined || target === undefined) {
this.filteredData.links.splice(i, 1);
continue;
}
if (!source.neighbors) {
source.neighbors = [];
}
Expand Down Expand Up @@ -301,11 +301,14 @@ export default {
if (this.selected.includes("labels")) {
issueTableNodeGroups.push("label");
}
if (this.selected.includes("aiLabels")) {
issueTableNodeGroups.push("aiLabel");
}
this.issueTableData = this.filteredData.nodes
.filter((node) => issueTableNodeGroups.includes(node.group))
.sort((a, b) => b.totalVal - a.totalVal)
.slice(0, 25);
.slice(0, 100);
},
renderGraph: function () {
let linkColorFunction = function (link) {
Expand Down

0 comments on commit f7ce83a

Please sign in to comment.