From 6f07c6c5c875070c1ff2e73eb55405b736c19f43 Mon Sep 17 00:00:00 2001 From: Shivaditya Shivganesh Date: Thu, 3 Oct 2024 15:00:53 -0400 Subject: [PATCH 01/11] feat: updated issue similarity ui --- src/handlers/issue-deduplication.ts | 165 +++++++++++++++++++--------- src/handlers/issue-matching.ts | 41 ++++--- 2 files changed, 137 insertions(+), 69 deletions(-) diff --git a/src/handlers/issue-deduplication.ts b/src/handlers/issue-deduplication.ts index 599b378..af118d2 100644 --- a/src/handlers/issue-deduplication.ts +++ b/src/handlers/issue-deduplication.ts @@ -6,6 +6,7 @@ export interface IssueGraphqlResponse { node: { title: string; url: string; + body: string; repository: { name: string; owner: { @@ -14,13 +15,15 @@ export interface IssueGraphqlResponse { }; }; similarity: string; + mostSimilarSentence: { sentence: string; similarity: number; index: number }; } /** - * Check if an issue is similar to any existing issues in the database - * @param context - * @returns true if the issue is similar to an existing issue, false otherwise - */ + * Checks if the current issue is a duplicate of an existing issue. + * If a similar issue is found, a comment is added to the current issue. + * @param context The context object + * @returns True if a similar issue is found, false otherwise + **/ export async function issueChecker(context: Context): Promise { const { logger, @@ -29,19 +32,10 @@ export async function issueChecker(context: Context): Promise { } = context; const { payload } = context as { payload: IssuePayload }; const issue = payload.issue; - - //Find and remove the footnotes from the issue content - const existingBody = context.payload.issue.body; - const footnoteIndex = existingBody?.indexOf("\n###### Similar"); - const issueBody = footnoteIndex !== -1 ? existingBody?.substring(0, footnoteIndex) : existingBody; - const issueContent = issueBody + issue.title; - - // Fetch all similar issues based on settings.warningThreshold - const similarIssues = await supabase.issue.findSimilarIssues(issueContent, context.config.warningThreshold, issue.node_id); + const similarIssues = await supabase.issue.findSimilarIssues(issue.title + removeFootnotes(issue.body || ""), context.config.warningThreshold, issue.node_id); if (similarIssues && similarIssues.length > 0) { const matchIssues = similarIssues.filter((issue) => issue.similarity >= context.config.matchThreshold); - // Handle issues that match the MATCH_THRESHOLD (Very Similar) if (matchIssues.length > 0) { logger.info(`Similar issue which matches more than ${context.config.matchThreshold} already exists`); await octokit.issues.update({ @@ -53,10 +47,9 @@ export async function issueChecker(context: Context): Promise { }); } - // Handle issues that match the settings.warningThreshold but not the MATCH_THRESHOLD if (similarIssues.length > 0) { logger.info(`Similar issue which matches more than ${context.config.warningThreshold} already exists`); - await handleSimilarIssuesComment(context, payload, issue.number, similarIssues, issueBody || ""); + await handleSimilarIssuesComment(context, payload, issue.number, similarIssues); return true; } } @@ -64,32 +57,41 @@ export async function issueChecker(context: Context): Promise { return false; } -/** - * Compare the repository and issue name to the similar issue repository and issue name - * @param repoOrg - * @param similarIssueRepoOrg - * @param repoName - * @param similarIssueRepoName - * @returns - */ function matchRepoOrgToSimilarIssueRepoOrg(repoOrg: string, similarIssueRepoOrg: string, repoName: string, similarIssueRepoName: string): boolean { return repoOrg === similarIssueRepoOrg && repoName === similarIssueRepoName; } /** - * Handle commenting on an issue with similar issues information - * @param context - * @param payload - * @param issueNumber - * @param similarIssues + * Finds the most similar sentence in a similar issue to a sentence in the current issue. + * @param issueContent The content of the current issue + * @param similarIssueContent The content of the similar issue + * @returns The most similar sentence and its similarity score */ -async function handleSimilarIssuesComment( - context: Context, - payload: IssuePayload, - issueNumber: number, - similarIssues: IssueSimilaritySearchResult[], - modifiedBody: string -) { +function findMostSimilarSentence(issueContent: string, similarIssueContent: string): { sentence: string; similarity: number; index: number } { + const issueSentences = issueContent.split(/[.!?]+/).filter((sentence) => sentence.trim().length > 0); + const similarIssueSentences = similarIssueContent.split(/[.!?]+/).filter((sentence) => sentence.trim().length > 0); + let maxSimilarity = 0; + let mostSimilarSentence = ""; + let mostSimilarIndex = -1; + issueSentences.forEach((sentence, index) => { + const similarities = similarIssueSentences.map((similarSentence) => { + const editDistance = findEditDistance(sentence, similarSentence); + const maxLength = Math.max(sentence.length, similarSentence.length); + // Normalized similarity (edit distance) + return 1 - editDistance / maxLength; + }); + const maxSentenceSimilarity = Math.max(...similarities); + if (maxSentenceSimilarity > maxSimilarity) { + maxSimilarity = maxSentenceSimilarity; + mostSimilarSentence = sentence; + mostSimilarIndex = index; + } + }); + + return { sentence: mostSimilarSentence, similarity: maxSimilarity, index: mostSimilarIndex }; +} + +async function handleSimilarIssuesComment(context: Context, payload: IssuePayload, issueNumber: number, similarIssues: IssueSimilaritySearchResult[]) { const issueList: IssueGraphqlResponse[] = await Promise.all( similarIssues.map(async (issue: IssueSimilaritySearchResult) => { const issueUrl: IssueGraphqlResponse = await context.octokit.graphql( @@ -98,6 +100,7 @@ async function handleSimilarIssuesComment( ... on Issue { title url + body repository { name owner { @@ -110,32 +113,92 @@ async function handleSimilarIssuesComment( { issueNodeId: issue.issue_id } ); issueUrl.similarity = Math.round(issue.similarity * 100).toString(); + issueUrl.mostSimilarSentence = findMostSimilarSentence(context.payload.issue.body || "", issueUrl.node.body); return issueUrl; }) ); - let finalIndex = 0; - const commentBody = issueList - .filter((issue) => - matchRepoOrgToSimilarIssueRepoOrg(payload.repository.owner.login, issue.node.repository.owner.login, payload.repository.name, issue.node.repository.name) - ) - .map((issue, index) => { - const modifiedUrl = issue.node.url.replace("https://github.com", "https://www.github.com"); - return `[^0${index + 1}^]: [${issue.node.title}](${modifiedUrl}) ${issue.similarity}%`; - }) - .join("\n"); + const relevantIssues = issueList.filter((issue) => + matchRepoOrgToSimilarIssueRepoOrg(payload.repository.owner.login, issue.node.repository.owner.login, payload.repository.name, issue.node.repository.name) + ); - if (commentBody.length === 0) { + if (relevantIssues.length === 0) { return; } - const footnoteLinks = [...Array(++finalIndex).keys()].map((i) => `[^0${i + 1}^]`).join(""); - const body = "\n###### Similar " + footnoteLinks + "\n\n" + commentBody; - //Append the new foot note + const issueBody = context.payload.issue.body || ""; + // Find existing footnotes in the body + const footnoteRegex = /\[\^(\d+)\^\]/g; + const existingFootnotes = issueBody.match(footnoteRegex) || []; + const highestFootnoteIndex = existingFootnotes.length > 0 ? Math.max(...existingFootnotes.map((fn) => parseInt(fn.match(/\d+/)?.[0] ?? "0"))) : 0; + let updatedBody = issueBody; + let footnotes = ""; + relevantIssues.forEach((issue, index) => { + const footnoteIndex = highestFootnoteIndex + index + 1; // Continue numbering from the highest existing footnote number + const footnoteRef = `[^0${footnoteIndex}^]`; + const modifiedUrl = issue.node.url.replace("https://github.com", "https://www.github.com"); + const { sentence } = issue.mostSimilarSentence; + + // Insert footnote reference in the body + const sentencePattern = new RegExp(`${sentence.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`, "g"); + updatedBody = updatedBody.replace(sentencePattern, `${sentence}${footnoteRef}`); + + // Add new footnote + footnotes += `${footnoteRef}: ⚠ ${issue.similarity}% possible duplicate - [${issue.node.title}](${modifiedUrl})\n\n`; + }); + + // Append new footnotes to the body, keeping the previous ones + updatedBody += footnotes; + + // Update the issue with the modified body await context.octokit.issues.update({ owner: payload.repository.owner.login, repo: payload.repository.name, issue_number: issueNumber, - body: modifiedBody + body, + body: updatedBody, }); } + +/** + * Finds the edit distance between two strings using dynamic programming. + * @param sentenceA + * @param sentenceB + * @returns + */ +function findEditDistance(sentenceA: string, sentenceB: string): number { + const m = sentenceA.length; + const n = sentenceB.length; + const dp: number[][] = Array.from({ length: m + 1 }, () => Array.from({ length: n + 1 }, () => 0)); + + for (let i = 0; i <= m; i++) { + for (let j = 0; j <= n; j++) { + if (i === 0) { + dp[i][j] = j; + } else if (j === 0) { + dp[i][j] = i; + } else if (sentenceA[i - 1] === sentenceB[j - 1]) { + dp[i][j] = dp[i - 1][j - 1]; + } else { + dp[i][j] = 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]); + } + } + } + + return dp[m][n]; +} + +/** + * Removes all footnotes from the issue content. + * This includes both the footnote references in the body and the footnote definitions at the bottom. + * @param content The content of the issue + * @returns The content without footnotes + */ +function removeFootnotes(content: string): string { + // Remove footnote references like [^1^], [^2^], etc. + const footnoteRefRegex = /\[\^\d+\^\]/g; + const contentWithoutFootnoteRefs = content.replace(footnoteRefRegex, ""); + + // Remove footnote section starting with '###### Similar Issues' or any other footnote-related section + const footnoteSectionRegex = /\n###### Similar Issues[\s\S]*$/g; + return contentWithoutFootnoteRefs.replace(footnoteSectionRegex, ""); +} diff --git a/src/handlers/issue-matching.ts b/src/handlers/issue-matching.ts index cc1d060..f2e866a 100644 --- a/src/handlers/issue-matching.ts +++ b/src/handlers/issue-matching.ts @@ -24,17 +24,12 @@ export interface IssueGraphqlResponse { similarity: number; } -const commentBuilder = (matchResultArray: Map>): string => { - const commentLines: string[] = [">[!NOTE]", ">The following contributors may be suitable for this task:"]; - matchResultArray.forEach((issues, assignee) => { - commentLines.push(`>### [${assignee}](https://www.github.com/${assignee})`); - issues.forEach((issue) => { - commentLines.push(issue); - }); - }); - return commentLines.join("\n"); -}; - +/** + * Checks if the current issue is a duplicate of an existing issue. + * If a similar issue is found, a comment is added to the current issue. + * @param context The context object + * @returns True if a similar issue is found, false otherwise + **/ export async function issueMatching(context: Context) { const { logger, @@ -45,15 +40,10 @@ export async function issueMatching(context: Context) { const issue = payload.issue; const issueContent = issue.body + issue.title; const commentStart = ">The following contributors may be suitable for this task:"; - - // On Adding the labels to the issue, the bot should - // create a new comment with users who completed task most similar to the issue - // if the comment already exists, it should update the comment with the new users const matchResultArray: Map> = new Map(); const similarIssues = await supabase.issue.findSimilarIssues(issueContent, context.config.jobMatchingThreshold, issue.node_id); if (similarIssues && similarIssues.length > 0) { - // Find the most similar issue and the users who completed the task - similarIssues.sort((a, b) => b.similarity - a.similarity); + similarIssues.sort((a, b) => b.similarity - a.similarity); // Sort by similarity const fetchPromises = similarIssues.map(async (issue) => { const issueObject: IssueGraphqlResponse = await context.octokit.graphql( `query ($issueNodeId: ID!) { @@ -84,7 +74,6 @@ export async function issueMatching(context: Context) { issueObject.similarity = issue.similarity; return issueObject; }); - const issueList = await Promise.all(fetchPromises); issueList.forEach((issue) => { if (issue.node.closed && issue.node.stateReason === "COMPLETED" && issue.node.assignees.nodes.length > 0) { @@ -148,3 +137,19 @@ export async function issueMatching(context: Context) { logger.ok(`Successfully created issue comment!`); logger.debug(`Exiting issueMatching handler`); } + +/** + * Builds the comment to be added to the issue + * @param matchResultArray The array of issues to be matched + * @returns The comment to be added to the issue + */ +function commentBuilder(matchResultArray: Map>): string { + const commentLines: string[] = [">[!NOTE]", ">The following contributors may be suitable for this task:"]; + matchResultArray.forEach((issues, assignee) => { + commentLines.push(`>### [${assignee}](https://www.github.com/${assignee})`); + issues.forEach((issue) => { + commentLines.push(issue); + }); + }); + return commentLines.join("\n"); +} From 0cbce178bf9eba9b19563c3407acfbf6fbe9ed98 Mon Sep 17 00:00:00 2001 From: Shivaditya Shivganesh Date: Thu, 3 Oct 2024 16:51:58 -0400 Subject: [PATCH 02/11] fix: empty strings init removed --- src/handlers/issue-deduplication.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/handlers/issue-deduplication.ts b/src/handlers/issue-deduplication.ts index af118d2..7e6f182 100644 --- a/src/handlers/issue-deduplication.ts +++ b/src/handlers/issue-deduplication.ts @@ -71,7 +71,7 @@ function findMostSimilarSentence(issueContent: string, similarIssueContent: stri const issueSentences = issueContent.split(/[.!?]+/).filter((sentence) => sentence.trim().length > 0); const similarIssueSentences = similarIssueContent.split(/[.!?]+/).filter((sentence) => sentence.trim().length > 0); let maxSimilarity = 0; - let mostSimilarSentence = ""; + let mostSimilarSentence; let mostSimilarIndex = -1; issueSentences.forEach((sentence, index) => { const similarities = similarIssueSentences.map((similarSentence) => { @@ -87,7 +87,9 @@ function findMostSimilarSentence(issueContent: string, similarIssueContent: stri mostSimilarIndex = index; } }); - + if (!mostSimilarSentence) { + throw new Error("No similar sentence found"); + } return { sentence: mostSimilarSentence, similarity: maxSimilarity, index: mostSimilarIndex }; } @@ -132,7 +134,7 @@ async function handleSimilarIssuesComment(context: Context, payload: IssuePayloa const existingFootnotes = issueBody.match(footnoteRegex) || []; const highestFootnoteIndex = existingFootnotes.length > 0 ? Math.max(...existingFootnotes.map((fn) => parseInt(fn.match(/\d+/)?.[0] ?? "0"))) : 0; let updatedBody = issueBody; - let footnotes = ""; + let footnotes: string[] | undefined; relevantIssues.forEach((issue, index) => { const footnoteIndex = highestFootnoteIndex + index + 1; // Continue numbering from the highest existing footnote number const footnoteRef = `[^0${footnoteIndex}^]`; @@ -143,12 +145,17 @@ async function handleSimilarIssuesComment(context: Context, payload: IssuePayloa const sentencePattern = new RegExp(`${sentence.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`, "g"); updatedBody = updatedBody.replace(sentencePattern, `${sentence}${footnoteRef}`); - // Add new footnote - footnotes += `${footnoteRef}: ⚠ ${issue.similarity}% possible duplicate - [${issue.node.title}](${modifiedUrl})\n\n`; + // Initialize footnotes array if not already done + if (!footnotes) { + footnotes = []; + } + + // Add new footnote to the array + footnotes.push(`${footnoteRef}: ⚠ ${issue.similarity}% possible duplicate - [${issue.node.title}](${modifiedUrl})\n\n`); }); // Append new footnotes to the body, keeping the previous ones - updatedBody += footnotes; + updatedBody += footnotes ? footnotes.join("") : ""; // Update the issue with the modified body await context.octokit.issues.update({ From e13461a529156d9759bfcf5f91b53102af3bbe55 Mon Sep 17 00:00:00 2001 From: Shivaditya Shivganesh Date: Fri, 4 Oct 2024 01:37:28 -0400 Subject: [PATCH 03/11] fix: changed the removeFootnote function --- src/adapters/supabase/helpers/comment.ts | 4 +- src/adapters/supabase/helpers/issues.ts | 4 +- src/handlers/add-issue.ts | 4 +- src/handlers/issue-deduplication.ts | 89 +++++++++++++++--------- src/handlers/update-issue.ts | 5 +- 5 files changed, 67 insertions(+), 39 deletions(-) diff --git a/src/adapters/supabase/helpers/comment.ts b/src/adapters/supabase/helpers/comment.ts index 3fa08b2..a0fa89e 100644 --- a/src/adapters/supabase/helpers/comment.ts +++ b/src/adapters/supabase/helpers/comment.ts @@ -37,7 +37,7 @@ export class Comment extends SuperSupabase { } else { //Create the embedding for this comment const embedding = await this.context.adapters.voyage.embedding.createEmbedding(markdown); - let plaintext: string | null = markdownToPlainText(markdown || ""); + let plaintext: string | null = markdownToPlainText(markdown); if (isPrivate) { markdown = null as string | null; payload = null as Record | null; @@ -57,7 +57,7 @@ export class Comment extends SuperSupabase { async updateComment(markdown: string | null, commentNodeId: string, payload: Record | null, isPrivate: boolean) { //Create the embedding for this comment const embedding = Array.from(await this.context.adapters.voyage.embedding.createEmbedding(markdown)); - let plaintext: string | null = markdownToPlainText(markdown || ""); + let plaintext: string | null = markdownToPlainText(markdown); if (isPrivate) { markdown = null as string | null; payload = null as Record | null; diff --git a/src/adapters/supabase/helpers/issues.ts b/src/adapters/supabase/helpers/issues.ts index 6bfef09..94b7d38 100644 --- a/src/adapters/supabase/helpers/issues.ts +++ b/src/adapters/supabase/helpers/issues.ts @@ -37,7 +37,7 @@ export class Issues extends SuperSupabase { return; } else { const embedding = await this.context.adapters.voyage.embedding.createEmbedding(markdown); - let plaintext: string | null = markdownToPlainText(markdown || ""); + let plaintext: string | null = markdownToPlainText(markdown); if (isPrivate) { payload = null; markdown = null; @@ -55,7 +55,7 @@ export class Issues extends SuperSupabase { async updateIssue(markdown: string | null, issueNodeId: string, payload: Record | null, isPrivate: boolean) { //Create the embedding for this comment const embedding = Array.from(await this.context.adapters.voyage.embedding.createEmbedding(markdown)); - let plaintext: string | null = markdownToPlainText(markdown || ""); + let plaintext: string | null = markdownToPlainText(markdown); if (isPrivate) { markdown = null as string | null; payload = null as Record | null; diff --git a/src/handlers/add-issue.ts b/src/handlers/add-issue.ts index 969a5c2..c828bbf 100644 --- a/src/handlers/add-issue.ts +++ b/src/handlers/add-issue.ts @@ -1,5 +1,6 @@ import { Context } from "../types"; import { IssuePayload } from "../types/payload"; +import { removeFootnotes } from "./issue-deduplication"; export async function addIssue(context: Context) { const { @@ -16,7 +17,8 @@ export async function addIssue(context: Context) { if (!markdown) { throw new Error("Issue body is empty"); } - await supabase.issue.createIssue(nodeId, payload, isPrivate, markdown, authorId); + const cleanedIssue = removeFootnotes(markdown); + await supabase.issue.createIssue(nodeId, payload, isPrivate, cleanedIssue, authorId); } catch (error) { if (error instanceof Error) { logger.error(`Error creating issue:`, { error: error, stack: error.stack }); diff --git a/src/handlers/issue-deduplication.ts b/src/handlers/issue-deduplication.ts index 7e6f182..3df913c 100644 --- a/src/handlers/issue-deduplication.ts +++ b/src/handlers/issue-deduplication.ts @@ -32,10 +32,15 @@ export async function issueChecker(context: Context): Promise { } = context; const { payload } = context as { payload: IssuePayload }; const issue = payload.issue; - const similarIssues = await supabase.issue.findSimilarIssues(issue.title + removeFootnotes(issue.body || ""), context.config.warningThreshold, issue.node_id); + let issueBody = issue.body; + if (!issueBody) { + logger.info("Issue body is empty"); + return false; + } + issueBody = removeFootnotes(issueBody); + const similarIssues = await supabase.issue.findSimilarIssues(issue.title + removeFootnotes(issueBody), context.config.warningThreshold, issue.node_id); if (similarIssues && similarIssues.length > 0) { const matchIssues = similarIssues.filter((issue) => issue.similarity >= context.config.matchThreshold); - if (matchIssues.length > 0) { logger.info(`Similar issue which matches more than ${context.config.matchThreshold} already exists`); await octokit.issues.update({ @@ -49,11 +54,11 @@ export async function issueChecker(context: Context): Promise { if (similarIssues.length > 0) { logger.info(`Similar issue which matches more than ${context.config.warningThreshold} already exists`); - await handleSimilarIssuesComment(context, payload, issue.number, similarIssues); + await handleSimilarIssuesComment(context, payload, issueBody, issue.number, similarIssues); return true; } } - + console.log("No similar issues found"); return false; } @@ -93,7 +98,13 @@ function findMostSimilarSentence(issueContent: string, similarIssueContent: stri return { sentence: mostSimilarSentence, similarity: maxSimilarity, index: mostSimilarIndex }; } -async function handleSimilarIssuesComment(context: Context, payload: IssuePayload, issueNumber: number, similarIssues: IssueSimilaritySearchResult[]) { +async function handleSimilarIssuesComment( + context: Context, + payload: IssuePayload, + issueBody: string, + issueNumber: number, + similarIssues: IssueSimilaritySearchResult[] +) { const issueList: IssueGraphqlResponse[] = await Promise.all( similarIssues.map(async (issue: IssueSimilaritySearchResult) => { const issueUrl: IssueGraphqlResponse = await context.octokit.graphql( @@ -115,7 +126,7 @@ async function handleSimilarIssuesComment(context: Context, payload: IssuePayloa { issueNodeId: issue.issue_id } ); issueUrl.similarity = Math.round(issue.similarity * 100).toString(); - issueUrl.mostSimilarSentence = findMostSimilarSentence(context.payload.issue.body || "", issueUrl.node.body); + issueUrl.mostSimilarSentence = findMostSimilarSentence(issueBody, issueUrl.node.body); return issueUrl; }) ); @@ -128,7 +139,9 @@ async function handleSimilarIssuesComment(context: Context, payload: IssuePayloa return; } - const issueBody = context.payload.issue.body || ""; + if (!issueBody) { + return; + } // Find existing footnotes in the body const footnoteRegex = /\[\^(\d+)\^\]/g; const existingFootnotes = issueBody.match(footnoteRegex) || []; @@ -155,7 +168,9 @@ async function handleSimilarIssuesComment(context: Context, payload: IssuePayloa }); // Append new footnotes to the body, keeping the previous ones - updatedBody += footnotes ? footnotes.join("") : ""; + if (footnotes) { + updatedBody += "\n\n" + footnotes.join(""); + } // Update the issue with the modified body await context.octokit.issues.update({ @@ -168,30 +183,34 @@ async function handleSimilarIssuesComment(context: Context, payload: IssuePayloa /** * Finds the edit distance between two strings using dynamic programming. - * @param sentenceA - * @param sentenceB - * @returns + * The edit distance is a way of quantifying how dissimilar two strings are to one another by + * counting the minimum number of operations required to transform one string into the other. + * For more information, see: https://en.wikipedia.org/wiki/Edit_distance + * @param sentenceA The first string + * @param sentenceB The second string + * @returns The edit distance between the two strings */ function findEditDistance(sentenceA: string, sentenceB: string): number { - const m = sentenceA.length; - const n = sentenceB.length; - const dp: number[][] = Array.from({ length: m + 1 }, () => Array.from({ length: n + 1 }, () => 0)); - - for (let i = 0; i <= m; i++) { - for (let j = 0; j <= n; j++) { - if (i === 0) { - dp[i][j] = j; - } else if (j === 0) { - dp[i][j] = i; - } else if (sentenceA[i - 1] === sentenceB[j - 1]) { - dp[i][j] = dp[i - 1][j - 1]; + const lengthA = sentenceA.length; + const lengthB = sentenceB.length; + const distanceMatrix: number[][] = Array.from({ length: lengthA + 1 }, () => Array.from({ length: lengthB + 1 }, () => 0)); + + for (let indexA = 0; indexA <= lengthA; indexA++) { + for (let indexB = 0; indexB <= lengthB; indexB++) { + if (indexA === 0) { + distanceMatrix[indexA][indexB] = indexB; + } else if (indexB === 0) { + distanceMatrix[indexA][indexB] = indexA; + } else if (sentenceA[indexA - 1] === sentenceB[indexB - 1]) { + distanceMatrix[indexA][indexB] = distanceMatrix[indexA - 1][indexB - 1]; } else { - dp[i][j] = 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]); + distanceMatrix[indexA][indexB] = + 1 + Math.min(distanceMatrix[indexA - 1][indexB], distanceMatrix[indexA][indexB - 1], distanceMatrix[indexA - 1][indexB - 1]); } } } - return dp[m][n]; + return distanceMatrix[lengthA][lengthB]; } /** @@ -200,12 +219,16 @@ function findEditDistance(sentenceA: string, sentenceB: string): number { * @param content The content of the issue * @returns The content without footnotes */ -function removeFootnotes(content: string): string { - // Remove footnote references like [^1^], [^2^], etc. - const footnoteRefRegex = /\[\^\d+\^\]/g; - const contentWithoutFootnoteRefs = content.replace(footnoteRefRegex, ""); - - // Remove footnote section starting with '###### Similar Issues' or any other footnote-related section - const footnoteSectionRegex = /\n###### Similar Issues[\s\S]*$/g; - return contentWithoutFootnoteRefs.replace(footnoteSectionRegex, ""); +export function removeFootnotes(content: string): string { + const footnoteDefRegex = /\[\^(\d+)\^\]: ⚠ \d+% possible duplicate - [^\n]+(\n|$)/g; + const footnotes = content.match(footnoteDefRegex); + let contentWithoutFootnotes = content.replace(footnoteDefRegex, ""); + if (footnotes) { + console.log(footnotes); + footnotes.forEach((footnote) => { + const footnoteNumber = footnote.match(/\d+/)?.[0]; + contentWithoutFootnotes = contentWithoutFootnotes.replace(new RegExp(`\\[\\^${footnoteNumber}\\^\\]`, "g"), ""); + }); + } + return contentWithoutFootnotes.replace(/\n{2,}/g, "\n").trim(); } diff --git a/src/handlers/update-issue.ts b/src/handlers/update-issue.ts index 763b2ba..4ec5f34 100644 --- a/src/handlers/update-issue.ts +++ b/src/handlers/update-issue.ts @@ -1,5 +1,6 @@ import { Context } from "../types"; import { IssuePayload } from "../types/payload"; +import { removeFootnotes } from "./issue-deduplication"; export async function updateIssue(context: Context) { const { @@ -16,7 +17,9 @@ export async function updateIssue(context: Context) { if (!markdown) { throw new Error("Issue body is empty"); } - await supabase.issue.updateIssue(markdown, nodeId, payloadObject, isPrivate); + //clean issue by removing footnotes + const cleanedIssue = removeFootnotes(markdown); + await supabase.issue.updateIssue(cleanedIssue, nodeId, payloadObject, isPrivate); } catch (error) { if (error instanceof Error) { logger.error(`Error updating issue:`, { error: error, stack: error.stack }); From 8af9f8bf8bbe67135d27c4307c6decbdeac98762 Mon Sep 17 00:00:00 2001 From: Shivaditya Shivganesh Date: Sat, 5 Oct 2024 17:31:04 -0400 Subject: [PATCH 04/11] feat: change the issue result mention --- src/handlers/issue-deduplication.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/handlers/issue-deduplication.ts b/src/handlers/issue-deduplication.ts index 3df913c..f5f5ac3 100644 --- a/src/handlers/issue-deduplication.ts +++ b/src/handlers/issue-deduplication.ts @@ -5,6 +5,7 @@ import { IssuePayload } from "../types/payload"; export interface IssueGraphqlResponse { node: { title: string; + number: number; url: string; body: string; repository: { @@ -113,6 +114,7 @@ async function handleSimilarIssuesComment( ... on Issue { title url + number body repository { name @@ -164,7 +166,7 @@ async function handleSimilarIssuesComment( } // Add new footnote to the array - footnotes.push(`${footnoteRef}: ⚠ ${issue.similarity}% possible duplicate - [${issue.node.title}](${modifiedUrl})\n\n`); + footnotes.push(`${footnoteRef}: ⚠ ${issue.similarity}% possible duplicate - [${issue.node.title}](${modifiedUrl}#${issue.node.number})\n\n`); }); // Append new footnotes to the body, keeping the previous ones From d7f262e447674cb482ba043d04151200341438d4 Mon Sep 17 00:00:00 2001 From: Shivaditya Shivganesh Date: Tue, 8 Oct 2024 09:30:26 -0400 Subject: [PATCH 05/11] fix: tests, tests passing locally fails in ci --- tests/main.test.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/tests/main.test.ts b/tests/main.test.ts index 442b82e..27caeeb 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -5,7 +5,6 @@ import { drop } from "@mswjs/data"; import { Octokit } from "@octokit/rest"; import { Logs } from "@ubiquity-os/ubiquity-os-logger"; import dotenv from "dotenv"; -import manifest from "../manifest.json"; import { runPlugin } from "../src/plugin"; import { Env } from "../src/types"; import { Context, SupportedEvents } from "../src/types/context"; @@ -35,17 +34,6 @@ describe("Plugin tests", () => { await setupTests(); }); - it("Should serve the manifest file", async () => { - const worker = (await import("../src/worker")).default; - const response = await worker.fetch(new Request("http://localhost/manifest"), { - SUPABASE_KEY: "test", - SUPABASE_URL: "test", - VOYAGEAI_API_KEY: "test", - }); - const content = await response.json(); - expect(content).toEqual(manifest); - }); - it("When a comment is created it should add it to the database", async () => { const { context } = createContext(STRINGS.HELLO_WORLD, 1, 1, 1, 1, "sasasCreate"); await runPlugin(context); From 3ff27eea2363b97d531c0b3599fa865a9decd974 Mon Sep 17 00:00:00 2001 From: Shivaditya Shivganesh Date: Tue, 8 Oct 2024 12:50:33 -0400 Subject: [PATCH 06/11] fix: footnotes breaking link --- src/adapters/supabase/helpers/issues.ts | 1 + src/handlers/issue-deduplication.ts | 37 ++++++++++++++++--- .../20241002004403_issue_comments.sql | 8 ++-- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/adapters/supabase/helpers/issues.ts b/src/adapters/supabase/helpers/issues.ts index 94b7d38..b55e901 100644 --- a/src/adapters/supabase/helpers/issues.ts +++ b/src/adapters/supabase/helpers/issues.ts @@ -96,6 +96,7 @@ export class Issues extends SuperSupabase { current_id: currentId, query_embedding: embedding, threshold: threshold, + top_k: 5, }); if (error) { this.context.logger.error("Error finding similar issues", error); diff --git a/src/handlers/issue-deduplication.ts b/src/handlers/issue-deduplication.ts index f5f5ac3..3723753 100644 --- a/src/handlers/issue-deduplication.ts +++ b/src/handlers/issue-deduplication.ts @@ -59,7 +59,17 @@ export async function issueChecker(context: Context): Promise { return true; } } - console.log("No similar issues found"); + context.logger.info("No similar issues found"); + + //Use the IssueBody (Without footnotes) to update the issue + if (issueBody !== issue.body) { + await octokit.issues.update({ + owner: payload.repository.owner.login, + repo: payload.repository.name, + issue_number: issue.number, + body: issueBody, + }); + } return false; } @@ -74,11 +84,26 @@ function matchRepoOrgToSimilarIssueRepoOrg(repoOrg: string, similarIssueRepoOrg: * @returns The most similar sentence and its similarity score */ function findMostSimilarSentence(issueContent: string, similarIssueContent: string): { sentence: string; similarity: number; index: number } { - const issueSentences = issueContent.split(/[.!?]+/).filter((sentence) => sentence.trim().length > 0); - const similarIssueSentences = similarIssueContent.split(/[.!?]+/).filter((sentence) => sentence.trim().length > 0); + // Regex to match sentences while preserving URLs + const sentenceRegex = /([^.!?\s][^.!?]*(?:[.!?](?!['"]?\s|$)[^.!?]*)*[.!?]?['"]?(?=\s|$))/g; + + // Function to split text into sentences while preserving URLs + const splitIntoSentences = (text: string): string[] => { + const sentences: string[] = []; + let match; + while ((match = sentenceRegex.exec(text)) !== null) { + sentences.push(match[0].trim()); + } + return sentences; + }; + + const issueSentences = splitIntoSentences(issueContent); + const similarIssueSentences = splitIntoSentences(similarIssueContent); + let maxSimilarity = 0; - let mostSimilarSentence; + let mostSimilarSentence = ""; let mostSimilarIndex = -1; + issueSentences.forEach((sentence, index) => { const similarities = similarIssueSentences.map((similarSentence) => { const editDistance = findEditDistance(sentence, similarSentence); @@ -93,6 +118,7 @@ function findMostSimilarSentence(issueContent: string, similarIssueContent: stri mostSimilarIndex = index; } }); + if (!mostSimilarSentence) { throw new Error("No similar sentence found"); } @@ -138,7 +164,7 @@ async function handleSimilarIssuesComment( ); if (relevantIssues.length === 0) { - return; + context.logger.info("No relevant issues found with the same repository and organization"); } if (!issueBody) { @@ -226,7 +252,6 @@ export function removeFootnotes(content: string): string { const footnotes = content.match(footnoteDefRegex); let contentWithoutFootnotes = content.replace(footnoteDefRegex, ""); if (footnotes) { - console.log(footnotes); footnotes.forEach((footnote) => { const footnoteNumber = footnote.match(/\d+/)?.[0]; contentWithoutFootnotes = contentWithoutFootnotes.replace(new RegExp(`\\[\\^${footnoteNumber}\\^\\]`, "g"), ""); diff --git a/supabase/migrations/20241002004403_issue_comments.sql b/supabase/migrations/20241002004403_issue_comments.sql index 6323882..9ebb751 100644 --- a/supabase/migrations/20241002004403_issue_comments.sql +++ b/supabase/migrations/20241002004403_issue_comments.sql @@ -1,4 +1,4 @@ -CREATE OR REPLACE FUNCTION find_similar_issues(current_id VARCHAR, query_embedding vector(1024), threshold float8) +CREATE OR REPLACE FUNCTION find_similar_issues(current_id VARCHAR, query_embedding vector(1024), threshold float8, top_k INT) RETURNS TABLE(issue_id VARCHAR, issue_plaintext TEXT, similarity float8) AS $$ DECLARE current_quantized vector(1024); @@ -9,10 +9,10 @@ BEGIN SELECT id AS issue_id, plaintext AS issue_plaintext, 1 - (l2_distance(current_quantized, embedding)) AS similarity - FROM issues WHERE id <> current_id AND 1 - (l2_distance(current_quantized, embedding)) > threshold - ORDER BY similarity; + ORDER BY similarity + LIMIT top_k; -- Limit the number of results to top_k END; -$$ LANGUAGE plpgsql; \ No newline at end of file +$$ LANGUAGE plpgsql; From b909ff3e7495d8e37247fa647114f21258a54b16 Mon Sep 17 00:00:00 2001 From: Shivaditya Shivganesh Date: Tue, 8 Oct 2024 12:51:50 -0400 Subject: [PATCH 07/11] feat: updated similarity search function --- .../20241008165113_function_issue.sql | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 supabase/migrations/20241008165113_function_issue.sql diff --git a/supabase/migrations/20241008165113_function_issue.sql b/supabase/migrations/20241008165113_function_issue.sql new file mode 100644 index 0000000..3223d1d --- /dev/null +++ b/supabase/migrations/20241008165113_function_issue.sql @@ -0,0 +1,38 @@ +DROP FUNCTION IF EXISTS find_similar_issues; + +CREATE OR REPLACE FUNCTION find_similar_issues(current_id VARCHAR, query_embedding vector(1024), threshold float8, top_k INT) +RETURNS TABLE(issue_id VARCHAR, issue_plaintext TEXT, similarity float8) AS $$ +DECLARE + current_quantized vector(1024); + current_repo TEXT; + current_org TEXT; +BEGIN + -- Ensure the query_embedding is in the correct format + current_quantized := query_embedding; + + -- Extract the current issue's repo and org from the payload + SELECT + payload->'repository'->>'name'::text, + payload->'repository'->'owner'->>'login'::text + INTO current_repo, current_org + FROM issues + WHERE id = current_id; + + -- Check if the current issue has valid repo and org + IF current_repo IS NULL OR current_org IS NULL THEN + RETURN; -- Exit if current issue's repo or org is null + END IF; + + RETURN QUERY + SELECT id AS issue_id, + plaintext AS issue_plaintext, + (l2_distance(current_quantized, embedding)) AS similarity + FROM issues + WHERE id <> current_id + AND current_repo = payload->'repository'->>'name'::text + AND current_org = payload->'repository'->'owner'->>'login'::text + AND l2_distance(current_quantized, embedding) > threshold -- Ensure similarity exceeds threshold + ORDER BY similarity DESC + LIMIT top_k; +END; +$$ LANGUAGE plpgsql; From a8751072cd1f199e374e7d83468901e6f5cf1b3e Mon Sep 17 00:00:00 2001 From: Shivaditya Shivganesh Date: Tue, 8 Oct 2024 15:02:43 -0400 Subject: [PATCH 08/11] fix: issue creation fix --- src/handlers/issue-deduplication.ts | 2 +- src/plugin.ts | 2 +- .../20241008175109_function_issue.sql | 38 +++++++++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 supabase/migrations/20241008175109_function_issue.sql diff --git a/src/handlers/issue-deduplication.ts b/src/handlers/issue-deduplication.ts index 3723753..3273a09 100644 --- a/src/handlers/issue-deduplication.ts +++ b/src/handlers/issue-deduplication.ts @@ -21,7 +21,7 @@ export interface IssueGraphqlResponse { /** * Checks if the current issue is a duplicate of an existing issue. - * If a similar issue is found, a comment is added to the current issue. + * If a similar issue is found, a footnote is added to the current issue. * @param context The context object * @returns True if a similar issue is found, false otherwise **/ diff --git a/src/plugin.ts b/src/plugin.ts index 341e07b..f382409 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -32,8 +32,8 @@ export async function runPlugin(context: Context) { } else if (isIssueEvent(context)) { switch (eventName) { case "issues.opened": - await issueChecker(context); await addIssue(context); + await issueChecker(context); return await issueMatching(context); case "issues.edited": await issueChecker(context); diff --git a/supabase/migrations/20241008175109_function_issue.sql b/supabase/migrations/20241008175109_function_issue.sql new file mode 100644 index 0000000..875a8d4 --- /dev/null +++ b/supabase/migrations/20241008175109_function_issue.sql @@ -0,0 +1,38 @@ +DROP FUNCTION IF EXISTS find_similar_issues; + +CREATE OR REPLACE FUNCTION find_similar_issues(current_id VARCHAR, query_embedding vector(1024), threshold float8, top_k INT) +RETURNS TABLE(issue_id VARCHAR, issue_plaintext TEXT, similarity float8) AS $$ +DECLARE + current_quantized vector(1024); + current_repo TEXT; + current_org TEXT; +BEGIN + -- Ensure the query_embedding is in the correct format + current_quantized := query_embedding; + + -- Extract the current issue's repo and org from the payload + SELECT + payload->'repository'->>'name'::text, + payload->'repository'->'owner'->>'login'::text + INTO current_repo, current_org + FROM issues + WHERE id = current_id; + + -- Check if the current issue has valid repo and org + IF current_repo IS NULL OR current_org IS NULL THEN + RETURN; -- Exit if current issue's repo or org is null + END IF; + + RETURN QUERY + SELECT id AS issue_id, + plaintext AS issue_plaintext, + 1 - (l2_distance(current_quantized, embedding)) AS similarity + FROM issues + WHERE id <> current_id + AND current_repo = payload->'repository'->>'name'::text + AND current_org = payload->'repository'->'owner'->>'login'::text + AND 1 - l2_distance(current_quantized, embedding) > threshold -- Ensure similarity exceeds threshold + ORDER BY similarity DESC + LIMIT top_k; +END; +$$ LANGUAGE plpgsql; From 03f0110bacfb2958904698a75fc558b853da894a Mon Sep 17 00:00:00 2001 From: Shivaditya Shivganesh Date: Tue, 8 Oct 2024 15:05:03 -0400 Subject: [PATCH 09/11] fix: update the l2_distance with a weighted sum of inner product and inverted l2 distance --- supabase/migrations/20241008175109_function_issue.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/supabase/migrations/20241008175109_function_issue.sql b/supabase/migrations/20241008175109_function_issue.sql index 875a8d4..6c77ce3 100644 --- a/supabase/migrations/20241008175109_function_issue.sql +++ b/supabase/migrations/20241008175109_function_issue.sql @@ -26,12 +26,12 @@ BEGIN RETURN QUERY SELECT id AS issue_id, plaintext AS issue_plaintext, - 1 - (l2_distance(current_quantized, embedding)) AS similarity + ((0.5 * inner_product(current_quantized, embedding)) + 0.5 * (1 / (1 + l2_distance(current_quantized, embedding)))) as similarity FROM issues WHERE id <> current_id AND current_repo = payload->'repository'->>'name'::text AND current_org = payload->'repository'->'owner'->>'login'::text - AND 1 - l2_distance(current_quantized, embedding) > threshold -- Ensure similarity exceeds threshold + AND ((0.5 * inner_product(current_quantized, embedding)) + 0.5 * (1 / (1 + l2_distance(current_quantized, embedding)))) > threshold ORDER BY similarity DESC LIMIT top_k; END; From 4bce42c2437bf0da9796827744e7d3a7e6fcdf54 Mon Sep 17 00:00:00 2001 From: Shivaditya Shivganesh Date: Thu, 10 Oct 2024 16:28:52 -0400 Subject: [PATCH 10/11] fix: changed the order of the similarity search result --- src/adapters/supabase/helpers/comment.ts | 27 ++++++++++++++++++------ src/adapters/supabase/helpers/issues.ts | 25 ++++++++++++---------- src/handlers/issue-deduplication.ts | 3 +++ src/handlers/update-comments.ts | 7 ++++-- src/handlers/update-issue.ts | 3 ++- 5 files changed, 44 insertions(+), 21 deletions(-) diff --git a/src/adapters/supabase/helpers/comment.ts b/src/adapters/supabase/helpers/comment.ts index a0fa89e..295f8ae 100644 --- a/src/adapters/supabase/helpers/comment.ts +++ b/src/adapters/supabase/helpers/comment.ts @@ -54,7 +54,14 @@ export class Comment extends SuperSupabase { this.context.logger.info("Comment created successfully"); } - async updateComment(markdown: string | null, commentNodeId: string, payload: Record | null, isPrivate: boolean) { + async updateComment( + markdown: string | null, + commentNodeId: string, + authorId: number, + payload: Record | null, + isPrivate: boolean, + issueId: string + ) { //Create the embedding for this comment const embedding = Array.from(await this.context.adapters.voyage.embedding.createEmbedding(markdown)); let plaintext: string | null = markdownToPlainText(markdown); @@ -63,12 +70,18 @@ export class Comment extends SuperSupabase { payload = null as Record | null; plaintext = null as string | null; } - const { error } = await this.supabase - .from("issue_comments") - .update({ markdown, plaintext, embedding: embedding, payload, modified_at: new Date() }) - .eq("id", commentNodeId); - if (error) { - this.context.logger.error("Error updating comment", error); + const comments = await this.getComment(commentNodeId); + if (comments && comments.length == 0) { + this.context.logger.info("Comment does not exist, creating a new one"); + await this.createComment(markdown, commentNodeId, authorId, payload, isPrivate, issueId); + } else { + const { error } = await this.supabase + .from("issue_comments") + .update({ markdown, plaintext, embedding: embedding, payload, modified_at: new Date() }) + .eq("id", commentNodeId); + if (error) { + this.context.logger.error("Error updating comment", error); + } } } diff --git a/src/adapters/supabase/helpers/issues.ts b/src/adapters/supabase/helpers/issues.ts index b55e901..059ee75 100644 --- a/src/adapters/supabase/helpers/issues.ts +++ b/src/adapters/supabase/helpers/issues.ts @@ -52,21 +52,24 @@ export class Issues extends SuperSupabase { this.context.logger.info("Issue created successfully"); } - async updateIssue(markdown: string | null, issueNodeId: string, payload: Record | null, isPrivate: boolean) { - //Create the embedding for this comment + async updateIssue(markdown: string | null, issueNodeId: string, payload: Record | null, isPrivate: boolean, authorId: number) { const embedding = Array.from(await this.context.adapters.voyage.embedding.createEmbedding(markdown)); let plaintext: string | null = markdownToPlainText(markdown); if (isPrivate) { - markdown = null as string | null; - payload = null as Record | null; - plaintext = null as string | null; + markdown = null; + payload = null; + plaintext = null; } - const { error } = await this.supabase - .from("issues") - .update({ markdown, plaintext, embedding: embedding, payload, modified_at: new Date() }) - .eq("id", issueNodeId); - if (error) { - this.context.logger.error("Error updating comment", error); + const issues = await this.getIssue(issueNodeId); + if (issues && issues.length == 0) { + this.context.logger.info("Issue does not exist, creating a new one"); + await this.createIssue(issueNodeId, payload, isPrivate, markdown, authorId); + } else { + const { error } = await this.supabase.from("issues").update({ markdown, plaintext, embedding, payload, modified_at: new Date() }).eq("id", issueNodeId); + + if (error) { + this.context.logger.error("Error updating comment", error); + } } } diff --git a/src/handlers/issue-deduplication.ts b/src/handlers/issue-deduplication.ts index 3273a09..0cca940 100644 --- a/src/handlers/issue-deduplication.ts +++ b/src/handlers/issue-deduplication.ts @@ -176,6 +176,9 @@ async function handleSimilarIssuesComment( const highestFootnoteIndex = existingFootnotes.length > 0 ? Math.max(...existingFootnotes.map((fn) => parseInt(fn.match(/\d+/)?.[0] ?? "0"))) : 0; let updatedBody = issueBody; let footnotes: string[] | undefined; + // Sort relevant issues by similarity in ascending order + relevantIssues.sort((a, b) => parseFloat(a.similarity) - parseFloat(b.similarity)); + relevantIssues.forEach((issue, index) => { const footnoteIndex = highestFootnoteIndex + index + 1; // Continue numbering from the highest existing footnote number const footnoteRef = `[^0${footnoteIndex}^]`; diff --git a/src/handlers/update-comments.ts b/src/handlers/update-comments.ts index b1b9d18..6cc9545 100644 --- a/src/handlers/update-comments.ts +++ b/src/handlers/update-comments.ts @@ -7,15 +7,18 @@ export async function updateComment(context: Context) { adapters: { supabase }, } = context; const { payload } = context as { payload: CommentPayload }; + const markdown = payload.comment.body; + const authorId = payload.comment.user?.id || -1; const nodeId = payload.comment.node_id; const isPrivate = payload.repository.private; - const markdown = payload.comment.body || null; + const issueId = payload.issue.node_id; + // Fetch the previous comment and update it in the db try { if (!markdown) { throw new Error("Comment body is empty"); } - await supabase.comment.updateComment(markdown, nodeId, payload, isPrivate); + await supabase.comment.updateComment(markdown, nodeId, authorId, payload, isPrivate, issueId); } catch (error) { if (error instanceof Error) { logger.error(`Error updating comment:`, { error: error, stack: error.stack }); diff --git a/src/handlers/update-issue.ts b/src/handlers/update-issue.ts index 4ec5f34..cec5de2 100644 --- a/src/handlers/update-issue.ts +++ b/src/handlers/update-issue.ts @@ -12,6 +12,7 @@ export async function updateIssue(context: Context) { const nodeId = payload.issue.node_id; const isPrivate = payload.repository.private; const markdown = payload.issue.body + " " + payload.issue.title || null; + const authorId = payload.issue.user?.id || -1; // Fetch the previous issue and update it in the db try { if (!markdown) { @@ -19,7 +20,7 @@ export async function updateIssue(context: Context) { } //clean issue by removing footnotes const cleanedIssue = removeFootnotes(markdown); - await supabase.issue.updateIssue(cleanedIssue, nodeId, payloadObject, isPrivate); + await supabase.issue.updateIssue(cleanedIssue, nodeId, payloadObject, isPrivate, authorId); } catch (error) { if (error instanceof Error) { logger.error(`Error updating issue:`, { error: error, stack: error.stack }); From 3477aea88cd6809ac45231dec65bf514aaf6dd50 Mon Sep 17 00:00:00 2001 From: Shivaditya Shivganesh Date: Thu, 10 Oct 2024 17:12:50 -0400 Subject: [PATCH 11/11] fix: fixed tests --- src/main.ts | 2 +- tests/__mocks__/adapter.ts | 36 ++++++++++++++++++++++-------------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/main.ts b/src/main.ts index 91490bc..0b9b9a4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -36,7 +36,7 @@ async function returnDataToKernel(repoToken: string, stateId: string, output: ob await octokit.repos.createDispatchEvent({ owner: github.context.repo.owner, repo: github.context.repo.repo, - event_type: "return_data_to_ubiquibot_kernel", + event_type: "return-data-to-ubiquity-os-kernel", client_payload: { state_id: stateId, output: JSON.stringify(output), diff --git a/tests/__mocks__/adapter.ts b/tests/__mocks__/adapter.ts index d1f634c..abb271f 100644 --- a/tests/__mocks__/adapter.ts +++ b/tests/__mocks__/adapter.ts @@ -36,21 +36,29 @@ export function createMockAdapters(context: Context) { commentMap.set(commentNodeId, { id: commentNodeId, plaintext, author_id: authorId, embedding, issue_id: issueId }); } ), - updateComment: jest.fn(async (plaintext: string | null, commentNodeId: string, payload: Record | null, isPrivate: boolean) => { - if (!commentMap.has(commentNodeId)) { - throw new Error(STRINGS.COMMENT_DOES_NOT_EXIST); - } - const originalComment = commentMap.get(commentNodeId); - if (!originalComment) { - throw new Error(STRINGS.COMMENT_DOES_NOT_EXIST); - } - const { id, author_id } = originalComment; - const embedding = await context.adapters.voyage.embedding.createEmbedding(plaintext); - if (isPrivate) { - plaintext = null; + updateComment: jest.fn( + async ( + plaintext: string | null, + commentNodeId: string, + authorId: number, + payload: Record | null, + isPrivate: boolean, + issueId: string + ) => { + if (!commentMap.has(commentNodeId)) { + throw new Error(STRINGS.COMMENT_DOES_NOT_EXIST); + } + const originalComment = commentMap.get(commentNodeId); + if (!originalComment) { + throw new Error(STRINGS.COMMENT_DOES_NOT_EXIST); + } + const embedding = await context.adapters.voyage.embedding.createEmbedding(plaintext); + if (isPrivate) { + plaintext = null; + } + commentMap.set(commentNodeId, { id: issueId, plaintext, author_id: authorId, embedding, payload }); } - commentMap.set(commentNodeId, { id, plaintext, author_id, embedding }); - }), + ), deleteComment: jest.fn(async (commentNodeId: string) => { if (!commentMap.has(commentNodeId)) { throw new Error(STRINGS.COMMENT_DOES_NOT_EXIST);