-
-
-
{user_displayname}
-
{user_email || "Unknown email"}
-
{trimmedOrg(organization)}
+
+
+
+
+
+
+
{user_displayname}
+
{user_email || "Unknown email"}
+
{trimmedOrg(organization)}
+
diff --git a/public/logos/android studio.svg b/public/logos/android studio.svg
new file mode 100644
index 000000000..22ccc6517
--- /dev/null
+++ b/public/logos/android studio.svg
@@ -0,0 +1,481 @@
+
\ No newline at end of file
diff --git a/public/logos/appcode.svg b/public/logos/appcode.svg
new file mode 100644
index 000000000..f4d6f2d18
--- /dev/null
+++ b/public/logos/appcode.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/logos/aqua.svg b/public/logos/aqua.svg
new file mode 100644
index 000000000..7b74f1478
--- /dev/null
+++ b/public/logos/aqua.svg
@@ -0,0 +1,24 @@
+
diff --git a/public/logos/clion.svg b/public/logos/clion.svg
new file mode 100644
index 000000000..001a2da63
--- /dev/null
+++ b/public/logos/clion.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/logos/datagrip.svg b/public/logos/datagrip.svg
new file mode 100644
index 000000000..5931f9125
--- /dev/null
+++ b/public/logos/datagrip.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/logos/dataspell.svg b/public/logos/dataspell.svg
new file mode 100644
index 000000000..44cf2e173
--- /dev/null
+++ b/public/logos/dataspell.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/logos/goland.svg b/public/logos/goland.svg
new file mode 100644
index 000000000..3640b1d89
--- /dev/null
+++ b/public/logos/goland.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/logos/mps.svg b/public/logos/mps.svg
new file mode 100644
index 000000000..e72b81d51
--- /dev/null
+++ b/public/logos/mps.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/logos/rider.svg b/public/logos/rider.svg
new file mode 100644
index 000000000..d1ab1530f
--- /dev/null
+++ b/public/logos/rider.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/logos/rustrover.svg b/public/logos/rustrover.svg
new file mode 100644
index 000000000..bd5621c57
--- /dev/null
+++ b/public/logos/rustrover.svg
@@ -0,0 +1,132 @@
+
diff --git a/types/watermelon.ts b/types/watermelon.ts
index 2adabb226..771d37a09 100644
--- a/types/watermelon.ts
+++ b/types/watermelon.ts
@@ -25,6 +25,8 @@ export type StandardProcessedDataArray = {
link?: string;
number?: number | string;
image?: string;
+ created_at?: string;
+ author?: string;
}[];
export type StandardAPIResponse = {
data?: StandardProcessedDataArray;
diff --git a/utils/actions/detectConsoleLogs.ts b/utils/actions/detectConsoleLogs.ts
new file mode 100644
index 000000000..fcd6bcf66
--- /dev/null
+++ b/utils/actions/detectConsoleLogs.ts
@@ -0,0 +1,194 @@
+const { Configuration, OpenAIApi } = require("openai");
+import { App } from "@octokit/app";
+import { successPosthogTracking } from "../../utils/api/posthogTracking";
+import {
+ failedToFetchResponse,
+ missingParamsResponse,
+} from "../../utils/api/responses";
+import validateParams from "../../utils/api/validateParams";
+import { Octokit } from "octokit";
+
+const configuration = new Configuration({
+ apiKey: process.env.OPEN_AI_KEY,
+});
+const openai = new OpenAIApi(configuration);
+
+const app = new App({
+ appId: process.env.GITHUB_APP_ID!,
+ privateKey: process.env.GITHUB_PRIVATE_KEY!,
+});
+
+function getAdditions(filePatch: string) {
+ const additions: string[] = [];
+
+ // Split the patch into lines
+ const lines = filePatch.split("\n");
+
+ // Track if we are in a deletion block
+ let inDeletionBlock = false;
+
+ // Loop through lines
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i];
+
+ // Check if entering a deletion block
+ if (line.startsWith("-")) {
+ inDeletionBlock = true;
+ continue;
+ }
+
+ // Check if exiting a deletion block
+ if (line.startsWith("+") && inDeletionBlock) {
+ inDeletionBlock = false;
+ continue;
+ }
+
+ // If not in a deletion block, add lines starting with +
+ if (!inDeletionBlock && line.startsWith("+")) {
+ additions.push(line);
+ }
+
+ // Delete the pluses
+ lines[i] = line.replace("+", "");
+
+ // Trim the line to delete leading spaces
+ lines[i] = lines[i].trim();
+
+ // If the line is a comment, remove it
+ if (
+ lines[i].startsWith("#") ||
+ lines[i].startsWith("//") ||
+ lines[i].startsWith("/*")
+ ) {
+ lines.splice(i, 1);
+ i--;
+ }
+ }
+
+ return lines.join("\n");
+}
+
+function getConsoleLogPosition(filePatchAndIndividualLine: any) {
+ let positionInDiff = 1;
+ const { filePatch, individualLine } = filePatchAndIndividualLine;
+
+ // get the position of the indiviudalLine in th filePatch
+ const lines = filePatch.split("\n");
+ for (let i = 0; i < lines.length; i++) {
+ if (lines[i].includes(individualLine)) {
+ positionInDiff = i + 1;
+ break;
+ }
+ }
+
+ return positionInDiff;
+}
+
+export default async function detectConsoleLogs({
+ installationId,
+ owner,
+ repo,
+ issue_number,
+ reqUrl,
+ reqEmail,
+}: {
+ prTitle?: string;
+ businessLogicSummary?: string;
+ installationId: number;
+ owner: string;
+ repo: string;
+ issue_number: number;
+ reqUrl: string;
+ reqEmail: string;
+}) {
+ const octokit = await app.getInstallationOctokit(installationId);
+
+ // get the diffs
+ const { data: diffFiles } = await octokit.request(
+ "GET /repos/{owner}/{repo}/pulls/{pull_number}/files",
+ {
+ owner,
+ repo,
+ pull_number: issue_number,
+ }
+ );
+
+ const commentPromises = diffFiles.map(async (file) => {
+ const additions = getAdditions(file.patch ?? "");
+
+ const consoleLogDetectionPrompt = `This is a list of code additions. Identify
+ if there's a console log or its equivalent in another programming language
+ such as Java, Golang, Python, C, Rust, C++, etc.
+ (console.log(), println(), println!(), System.out.println(), print(), fmt.Println(), and cout << "Print a String" << endl; are some examples).
+ If the console log or its equivalent in another language is in a code comment, don't
+ count it as a detected console log. For example JavaScript comments start with // or /*,
+ Python comments start with #.
+ Other console functions such as console.info() shouldn't be counted as console logs.
+ Ignore code comments from this analysis.
+ If there is a console log, return "true", else return "false".
+ If you return true, return a string that that has 2 values: result (true) and the line of code.
+ The line value, is the actual line in the file that contains the console log.
+ For example: true,console.log("hello world");`;
+
+ // detect if the additions contain console logs or not
+ try {
+ return await openai
+ .createChatCompletion({
+ model: "gpt-3.5-turbo-16k",
+ messages: [
+ {
+ role: "system",
+ content: `${consoleLogDetectionPrompt} \n ${additions}`,
+ },
+ ],
+ })
+ .then((result) => {
+ const openAIResult = result.data.choices[0].message.content.split(",");
+
+ const addtionsHaveConsoleLog = openAIResult[0];
+ const individualLine = openAIResult[1];
+
+ if (addtionsHaveConsoleLog === "true") {
+ const commentFileDiff = () => {
+ return octokit
+ .request("GET /repos/{owner}/{repo}/pulls/{pull_number}", {
+ owner,
+ repo,
+ pull_number: issue_number,
+ })
+ .then((result) => {
+ const latestCommitHash = result.data.head.sha;
+
+ return octokit.request(
+ "POST /repos/{owner}/{repo}/pulls/{pull_number}/reviews",
+ {
+ owner,
+ repo,
+ pull_number: issue_number,
+ commit_id: latestCommitHash,
+ event: "COMMENT",
+ path: file.filename,
+ comments: [
+ {
+ path: file.filename,
+ position: getConsoleLogPosition({
+ filePatch: file.patch ?? "",
+ individualLine
+ }) || 1, // comment at the beggining of the file by default
+ body: "This file contains at least one console log. Please remove any present.",
+ },
+ ],
+ }
+ );
+ });
+ };
+
+ commentFileDiff();
+ }
+ });
+ } catch {}
+ });
+ try {
+ await Promise.allSettled(commentPromises);
+ } catch {}
+}
diff --git a/utils/actions/getAllServices.ts b/utils/actions/getAllServices.ts
index 760545cbd..5d78aa075 100644
--- a/utils/actions/getAllServices.ts
+++ b/utils/actions/getAllServices.ts
@@ -7,6 +7,8 @@ import getNotion from "./getNotion";
import getLinear from "./getLinear";
import getConfluence from "./getConfluence";
import getAsana from "./getAsana";
+import getTeamGitHub from "./getTeamGitHub";
+
export default async function getAllServices({
email,
url,
@@ -15,6 +17,7 @@ export default async function getAllServices({
randomWords,
hardMax,
userLogin,
+ installationId,
}: {
email?: string;
userLogin?: string;
@@ -23,111 +26,238 @@ export default async function getAllServices({
owner: string;
randomWords: string[];
hardMax?: number;
+ installationId?: number;
}) {
let query = "";
if (email) {
query = `EXEC dbo.get_all_user_tokens @watermelon_user='${email}'`;
- }
- if (userLogin) {
- query = `EXEC dbo.get_all_tokens_from_gh_username @github_user='${userLogin}'`;
- }
+ try {
+ let wmUserData = await executeRequest(query);
+ const {
+ github_token,
+ jira_token,
+ jira_refresh_token,
+ confluence_token,
+ confluence_refresh_token,
+ confluence_id,
+ cloudId,
+ slack_token,
+ notion_token,
+ linear_token,
+ asana_token,
+ asana_workspace,
+ user_email,
+ AISummary,
+ JiraTickets,
+ GitHubPRs,
+ SlackMessages,
+ NotionPages,
+ LinearTickets,
+ ConfluencePages,
+ AsanaTasks,
+ watermelon_user,
+ } = wmUserData;
+ const [github, jira, confluence, slack, notion, linear, asana] =
+ await Promise.all([
+ getGitHub({
+ repo,
+ owner,
+ github_token,
+ randomWords,
+ amount: GitHubPRs,
+ }),
+ getJira({
+ user: user_email,
+ token: jira_token,
+ refresh_token: jira_refresh_token,
+ randomWords,
+ amount: JiraTickets,
+ }),
+ getConfluence({
+ token: confluence_token,
+ refresh_token: confluence_refresh_token,
+ cloudId: confluence_id,
+ user: user_email,
+ randomWords,
+ amount: ConfluencePages,
+ }),
+ getSlack({
+ slack_token,
+ searchString: randomWords.join(" "),
+ amount: SlackMessages,
+ }),
+ getNotion({
+ notion_token,
+ randomWords,
+ amount: NotionPages,
+ }),
+ getLinear({
+ linear_token,
+ randomWords,
+ amount: LinearTickets,
+ }),
+ getAsana({
+ access_token: asana_token,
+ user: user_email,
+ randomWords,
+ workspace: asana_workspace,
+ amount: AsanaTasks,
+ }),
+ ]);
- let wmUserData = await executeRequest(query);
- const {
- github_token,
- jira_token,
- jira_refresh_token,
- confluence_token,
- confluence_refresh_token,
- confluence_id,
- cloudId,
- slack_token,
- notion_token,
- linear_token,
- asana_token,
- asana_workspace,
- user_email,
- AISummary,
- JiraTickets,
- GitHubPRs,
- SlackMessages,
- NotionPages,
- LinearTickets,
- ConfluencePages,
- AsanaTasks,
- watermelon_user,
- } = wmUserData;
- try {
- wmUserData = await executeRequest(query);
- } catch (error) {
- console.error(
- "An error occurred while getting user tokens:",
- error.message
- );
- failedPosthogTracking({
- url: url,
- error: error.message,
- email: email,
- });
- return { error: error.message };
- }
- const [github, jira, confluence, slack, notion, linear, asana] =
- await Promise.all([
- getGitHub({
- repo,
- owner,
+ return {
+ github,
+ jira,
+ confluence,
+ slack,
+ notion,
+ linear,
+ asana,
+ watermelon_user,
+ AISummary,
+ user_email,
+ };
+ } catch (error) {
+ console.error(
+ "An error occurred while getting user tokens:",
+ error.message
+ );
+ failedPosthogTracking({
+ url: url,
+ error: error.message,
+ email: email,
+ });
+ return { error: error.message };
+ }
+ } else {
+ query = `EXEC dbo.get_all_tokens_from_gh_username @github_user='${userLogin}'`;
+ try {
+ let wmUserData = await executeRequest(query);
+ const {
github_token,
- randomWords,
- amount: GitHubPRs,
- }),
- getJira({
- user: user_email,
- token: jira_token,
- refresh_token: jira_refresh_token,
- randomWords,
- amount: JiraTickets,
- }),
- getConfluence({
- token: confluence_token,
- refresh_token: confluence_refresh_token,
- cloudId: confluence_id,
- user: user_email,
- randomWords,
- amount: ConfluencePages,
- }),
- getSlack({
+ jira_token,
+ jira_refresh_token,
+ confluence_token,
+ confluence_refresh_token,
+ confluence_id,
+ cloudId,
slack_token,
- searchString: randomWords.join(" "),
- amount: SlackMessages,
- }),
- getNotion({
notion_token,
- randomWords,
- amount: NotionPages,
- }),
- getLinear({
linear_token,
- randomWords,
- amount: LinearTickets,
- }),
- getAsana({
- access_token: asana_token,
- user: user_email,
- randomWords,
- workspace: asana_workspace,
- amount: AsanaTasks,
- }),
- ]);
- return {
- github,
- jira,
- confluence,
- slack,
- notion,
- linear,
- asana,
- watermelon_user,
- AISummary,
- user_email,
- };
+ asana_token,
+ asana_workspace,
+ user_email,
+ AISummary,
+ JiraTickets,
+ GitHubPRs,
+ SlackMessages,
+ NotionPages,
+ LinearTickets,
+ ConfluencePages,
+ AsanaTasks,
+ watermelon_user,
+ } = wmUserData;
+ if (!watermelon_user) {
+ const [github] = await Promise.all([
+ getTeamGitHub({
+ repo,
+ owner,
+ installationId,
+ randomWords,
+ amount: GitHubPRs,
+ }),
+ ]);
+ return {
+ github,
+ asana: { error: "no asana token", data: [], fullData: [] },
+ confluence: { error: "no confluence token", data: [], fullData: [] },
+ jira: { error: "no jira token", data: [], fullData: [] },
+ linear: { error: "no linear token", data: [], fullData: [] },
+ notion: { error: "no notion token", data: [], fullData: [] },
+ slack: { error: "no slack token", data: [], fullData: [] },
+ watermelon_user: "team",
+ AISummary,
+ JiraTickets,
+ GitHubPRs,
+ SlackMessages,
+ NotionPages,
+ LinearTickets,
+ ConfluencePages,
+ AsanaTasks,
+ };
+ } else {
+ const [github, jira, confluence, slack, notion, linear, asana] =
+ await Promise.all([
+ getGitHub({
+ repo,
+ owner,
+ github_token,
+ randomWords,
+ amount: GitHubPRs,
+ }),
+ getJira({
+ user: user_email,
+ token: jira_token,
+ refresh_token: jira_refresh_token,
+ randomWords,
+ amount: JiraTickets,
+ }),
+ getConfluence({
+ token: confluence_token,
+ refresh_token: confluence_refresh_token,
+ cloudId: confluence_id,
+ user: user_email,
+ randomWords,
+ amount: ConfluencePages,
+ }),
+ getSlack({
+ slack_token,
+ searchString: randomWords.join(" "),
+ amount: SlackMessages,
+ }),
+ getNotion({
+ notion_token,
+ randomWords,
+ amount: NotionPages,
+ }),
+ getLinear({
+ linear_token,
+ randomWords,
+ amount: LinearTickets,
+ }),
+ getAsana({
+ access_token: asana_token,
+ user: user_email,
+ randomWords,
+ workspace: asana_workspace,
+ amount: AsanaTasks,
+ }),
+ ]);
+
+ return {
+ github,
+ jira,
+ confluence,
+ slack,
+ notion,
+ linear,
+ asana,
+ watermelon_user,
+ AISummary,
+ user_email,
+ };
+ }
+ } catch (error) {
+ console.error(
+ "An error occurred while getting user tokens:",
+ error.message
+ );
+ failedPosthogTracking({
+ url: url,
+ error: error.message,
+ email: email,
+ });
+ return { error: error.message };
+ }
+ }
}
diff --git a/utils/actions/getGitHub.ts b/utils/actions/getGitHub.ts
index 49c4100ca..8fb0249fe 100644
--- a/utils/actions/getGitHub.ts
+++ b/utils/actions/getGitHub.ts
@@ -21,14 +21,17 @@ async function getGitHub({
type: "pr",
per_page: amount,
});
+
return {
fullData: issues.data?.items,
data:
- issues.data?.items?.map(({ title, body, html_url: link, number }) => ({
+ issues.data?.items?.map(({ title, body, html_url: link, number, created_at, user }) => ({
title,
body,
link,
number,
+ created_at,
+ author: user?.login
})) || [],
};
}
diff --git a/utils/actions/getOpenAISummary.ts b/utils/actions/getOpenAISummary.ts
index df516617d..23b05d22f 100644
--- a/utils/actions/getOpenAISummary.ts
+++ b/utils/actions/getOpenAISummary.ts
@@ -62,20 +62,34 @@ export default async function getOpenAISummary({
body ? `Current PR Body: ${body} \n` : ""
}`;
- const prompt = `Summarize what the ${summaryPrompt} ${
- commitList?.length ? `the ${commitList?.length} commits,` : ""
- } are about. What do they tell us about the business logic? Don't summarize each piece or block of data separately, combine them and process all data. Take into consideration the current PR title and body. Don't look at each part or service of the list as a separate thing, but as a whole. The list will be available to me so you don't need to repeat it. Try to keep the summary to 3 or 4 sentences, but if it's a smaller thing just mention that.
- Here is the list:\n ${promptList} \n`;
+ promptList += commitList;
+
+ const prompt = `Summarize what the ${summaryPrompt} is about. What do they tell us about the business logic? Don't summarize each piece
+ or block of data separately, combine them and process all data. Take into consideration
+ the current PR title and body. Don't look at each part or service of the list as a
+ separate thing, but as a whole. The list will be available to me so you don't need to
+ repeat it. Here is the list:\n ${promptList} \n`;
+
try {
const completion = await openai.createChatCompletion({
model: "gpt-3.5-turbo-16k",
messages: [
{
role: "system",
- content:
- "You are a Technical PM, that understands the business and will help the user know what is going on in this recently opened Pull Request. The user will give you some context and you will summarize it in a succinct but not jargon filled way. You will avoid going over each individual data point, but will reason about the business logic.",
+ content: `You are a Technical PM, that understands the business and will help the user
+ know what is going on in this recently opened Pull Request. The user will give
+ you some context and you will summarize it in a succinct but not jargon filled
+ way. You will avoid going over each individual data point, but will reason about
+ the business logic. Be concise and don't explain step by step. Don't explain the
+ PR title in one sentence, the PR body in another one, etc. Just give a high-level
+ overview of what the PR is doing. Make it less than 45 words.`,
+ },
+ {
+ role: "user",
+ content: `${prompt}
+ Don't explain PR and commit message separately, merge into a single explanation.
+ Don't say "the commit message".`,
},
- { role: "user", content: prompt },
],
});
return completion.data.choices[0].message.content;
diff --git a/utils/actions/getSlack.ts b/utils/actions/getSlack.ts
index a51eea39f..618b289ff 100644
--- a/utils/actions/getSlack.ts
+++ b/utils/actions/getSlack.ts
@@ -15,7 +15,7 @@ async function getSlack({
user_token: slack_token,
count: amount,
});
- slackValue = response.messages.matches.filter(
+ slackValue = response?.messages?.matches?.filter(
(message) => !message.channel.is_private
);
}
diff --git a/utils/actions/getTeamGitHub.ts b/utils/actions/getTeamGitHub.ts
new file mode 100644
index 000000000..a2bf836d6
--- /dev/null
+++ b/utils/actions/getTeamGitHub.ts
@@ -0,0 +1,44 @@
+import { App } from "@octokit/app";
+import { StandardAPIResponse } from "../../types/watermelon";
+async function getTeamGitHub({
+ repo,
+ owner,
+ installationId,
+ randomWords,
+ amount = 3,
+}): Promise
{
+ // create the query with the random words and the owner
+ const q = `${randomWords.join(" OR ")} org:${owner}`;
+ const app = new App({
+ appId: process.env.GITHUB_APP_ID!,
+ privateKey: process.env.GITHUB_PRIVATE_KEY!,
+ });
+ const octokit = await app.getInstallationOctokit(installationId);
+
+ const issues = await octokit
+ .request("GET /search/issues", {
+ q,
+ is: "pr",
+ type: "pr",
+ per_page: amount,
+ })
+ .catch((e) => {
+ console.log(e);
+ });
+
+ return {
+ fullData: issues?.data?.items,
+ data:
+ issues?.data?.items?.map(
+ ({ title, body, html_url: link, number, created_at, user }) => ({
+ title,
+ body,
+ link,
+ number,
+ created_at,
+ author: user?.login,
+ })
+ ) || [],
+ };
+}
+export default getTeamGitHub;
diff --git a/utils/actions/labelPullRequest.ts b/utils/actions/labelPullRequest.ts
index bfdefce9f..d780547da 100644
--- a/utils/actions/labelPullRequest.ts
+++ b/utils/actions/labelPullRequest.ts
@@ -38,22 +38,56 @@ export default async function flagPullRequest({
}) {
const octokit = await app.getInstallationOctokit(installationId);
- const { missingParams } = validateParams("", [
- "prTitle",
- "businessLogicSummary",
- "installationId",
- "owner",
- "repo",
- "issue_number",
- "reqUrl",
- "reqEmail",
- ]);
+ let prompt = `The goal of this PR is to: ${prTitle}. \n The information related to this PR is: ${businessLogicSummary}. \n On a scale of 1(very different)-10(very similar), how similar the PR's goal and the PR's related information are? Take into account semantics. Don't explain your reasoning, just print the rating. Don't give a range for the rating, print a single value.`;
- if (missingParams.length > 0) {
- return missingParamsResponse({ url: reqUrl, missingParams });
- }
+ // Fetch all comments on the PR
+ const comments = await octokit.request(
+ "GET /repos/{owner}/{repo}/issues/{issue_number}/comments?sort=created&direction=desc",
+ {
+ owner,
+ repo,
+ issue_number,
+ headers: {
+ "X-GitHub-Api-Version": "2022-11-28",
+ },
+ }
+ );
+
+ // Find our bot's comment
+ let botComment = comments.data.find((comment) => {
+ if (comment.body.includes("This PR contains console logs")) {
+ // concat to the prompt
+ prompt += "Since the PR contains console logs, make the maximum rating 8.";
+ }
+ });
- const prompt = `The goal of this PR is to: ${prTitle}. \n The information related to this PR is: ${businessLogicSummary}. \n On a scale of 1(very different)-10(very similar), how similar the PR's goal and the PR's related information are? Take into account semantics. Don't explain your reasoning, just print the rating. Don't give a range for the rating, print a single value.`;
+ let labels = {
+ SAFE_TO_MERGE: "🍉 Safe to Merge",
+ TAKE_A_DEEPER_DIVE: "👀 Take a deeper dive",
+ DONT_MERGE: "🚨 Don't Merge",
+ };
+ function deleteLabel(labelName: string) {
+ octokit.request(
+ "DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels/{name}",
+ {
+ owner,
+ repo,
+ issue_number,
+ name: labelName,
+ }
+ );
+ }
+ function addLabel(labelName: string) {
+ octokit.request(
+ "POST /repos/{owner}/{repo}/issues/{issue_number}/labels", //add label
+ {
+ owner,
+ repo,
+ issue_number,
+ labels: [labelName],
+ }
+ );
+ }
try {
return await openai
@@ -79,27 +113,21 @@ export default async function flagPullRequest({
},
});
- if (prRating >= 2) {
- octokit.request(
- "POST /repos/{owner}/{repo}/issues/{issue_number}/labels", //add label
- {
- owner,
- repo,
- issue_number,
- labels: ["🍉 Safe to Merge"],
- }
- );
+ if (prRating >= 9) {
+ deleteLabel(labels.DONT_MERGE);
+ deleteLabel(labels.TAKE_A_DEEPER_DIVE);
+
+ addLabel(labels.SAFE_TO_MERGE);
+ } else if (prRating > 6) {
+ deleteLabel(labels.SAFE_TO_MERGE);
+ deleteLabel(labels.DONT_MERGE);
+
+ addLabel(labels.TAKE_A_DEEPER_DIVE);
} else {
- // remove label
- octokit.request(
- "DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels/{name}",
- {
- owner,
- repo,
- issue_number,
- name: "🍉 Safe to Merge",
- }
- );
+ deleteLabel(labels.SAFE_TO_MERGE);
+ deleteLabel(labels.TAKE_A_DEEPER_DIVE);
+
+ addLabel(labels.DONT_MERGE);
}
});
} catch (error) {
diff --git a/utils/actions/markdownHelpers/helper.ts b/utils/actions/markdownHelpers/helper.ts
index 476e3bcd1..411c7117a 100644
--- a/utils/actions/markdownHelpers/helper.ts
+++ b/utils/actions/markdownHelpers/helper.ts
@@ -1,4 +1,5 @@
import { MarkdownRequest, MarkdownResponse } from "../../../types/watermelon";
+import getRelativeDate from "../../getRelativeDate";
type generalMarkdown = MarkdownRequest & {
systemName: string;
@@ -11,7 +12,7 @@ const generalMarkdownHelper = ({
systemResponseName,
}: generalMarkdown): MarkdownResponse => {
if (!value || value?.data?.length === 0) {
- return `\n ${systemResponseName} deactivated by ${userLogin}`;
+ return `\n No results found in **${systemResponseName}** :( \n`;
}
if (value?.error?.match(/no (\w+) token/)) {
return `\n [Click here to login to ${systemName}](https://app.watermelontools.com)`;
@@ -21,13 +22,13 @@ const generalMarkdownHelper = ({
markdown = `\n #### ${systemResponseName}`;
markdown += (value?.data || [])
.map(
- ({ number, title, link, body }) =>
- `\n - [#${number} - ${title}](${link}) \n`
+ ({ number, title, link, body, author, created_at }) =>
+ `\n - [#${number} - ${title}](${link}) ${
+ author ? ` - By ${author}` : ""
+ } ${created_at ? `${getRelativeDate(created_at ?? "")}` : ""}\n`
)
.join("");
- } else {
- markdown += `\n No results found in **${systemResponseName}** :( \n`;
- }
+ }
return markdown;
};
diff --git a/utils/actions/markdownHelpers/randomText.ts b/utils/actions/markdownHelpers/randomText.ts
new file mode 100644
index 000000000..69b00b823
--- /dev/null
+++ b/utils/actions/markdownHelpers/randomText.ts
@@ -0,0 +1,16 @@
+const randomText = () => {
+ const textList = [
+ "\n[Why not invite more people to your team?](https://app.watermelontools.com/team)",
+ "\n[Have you starred Watermelon?](https://github.com/watermelontools/watermelon)",
+ "\n[Try us on VSCode!](https://marketplace.visualstudio.com/items?itemName=WatermelonTools.watermelon-tools)",
+ "\n[Try us on VSCodium!](https://open-vsx.org/extension/WatermelonTools/watermelon-tools)",
+ "\n[Try us on any JetBrains IDE!](https://plugins.jetbrains.com/plugin/22720-watermelon-context)",
+ ];
+
+ let randomChance = Math.random() * 100;
+ if (randomChance < 50) {
+ return textList[Math.floor(Math.random() * textList.length)];
+ }
+ return "";
+};
+export default randomText;
diff --git a/utils/api/posthogTracking.ts b/utils/api/posthogTracking.ts
index 106395a39..11f80f370 100644
--- a/utils/api/posthogTracking.ts
+++ b/utils/api/posthogTracking.ts
@@ -20,3 +20,10 @@ export function missingParamsPosthogTracking({ url, missingParams }) {
properties: missingParams,
});
}
+export function unauthorizedPosthogTracking({ url, email, error }) {
+ posthog.capture({
+ distinctId: email,
+ event: `${url}-unauthorized`,
+ properties: error,
+ });
+}
diff --git a/utils/api/responses.ts b/utils/api/responses.ts
index 49f568e2e..4a32e0a4d 100644
--- a/utils/api/responses.ts
+++ b/utils/api/responses.ts
@@ -3,6 +3,7 @@ import {
failedPosthogTracking,
missingParamsPosthogTracking,
successPosthogTracking,
+ unauthorizedPosthogTracking,
} from "./posthogTracking";
export function successResponse({ url, email, data }) {
@@ -26,8 +27,10 @@ export function missingParamsResponse({ url, missingParams }) {
);
}
-export function unauthorizedResponse({ email }) {
+export function unauthorizedResponse({ email, url }) {
const responseText = `email: ${email} is not authorized`;
+ unauthorizedPosthogTracking({ email, url, error: responseText });
+
return NextResponse.json(
{
error: responseText,
diff --git a/utils/db/teams/addActionCount.ts b/utils/db/teams/addActionCount.ts
index 44fee9c56..0819ef80d 100644
--- a/utils/db/teams/addActionCount.ts
+++ b/utils/db/teams/addActionCount.ts
@@ -1,8 +1,8 @@
import executeRequest from "../azuredb";
-export default async ({ watermelon_user }) => {
+export default async ({ owner }) => {
let query = await executeRequest(
- `EXEC dbo.increment_github_app_uses @watermelon_user = '${watermelon_user}'`
+ `EXEC dbo.increment_owner_github_app_uses @watermelon_user = '${owner}'`
);
return query;
};
diff --git a/utils/db/teams/createTeamAndMatchUser.ts b/utils/db/teams/createTeamAndMatchUser.ts
new file mode 100644
index 000000000..d5739ad3c
--- /dev/null
+++ b/utils/db/teams/createTeamAndMatchUser.ts
@@ -0,0 +1,8 @@
+import executeRequest from "../azuredb";
+
+export default async ({ name, id, watermelon_user }) => {
+ let query = await executeRequest(
+ `EXEC dbo.create_new_team_and_match_user @name = '${name}', @id = '${id}', @user_id = '${watermelon_user}'`
+ );
+ return query;
+};
diff --git a/utils/getRelativeDate.ts b/utils/getRelativeDate.ts
new file mode 100644
index 000000000..0164e9915
--- /dev/null
+++ b/utils/getRelativeDate.ts
@@ -0,0 +1,41 @@
+export default function getRelativeDate(isoDateString: string): string {
+
+ const date = new Date(isoDateString);
+ const now = new Date();
+
+ const diffInSeconds = (now.getTime() - date.getTime()) / 1000;
+
+ if (diffInSeconds < 60) {
+ return 'Just now';
+ }
+
+ const diffInMinutes = diffInSeconds / 60;
+ if (diffInMinutes < 60) {
+ return `${Math.floor(diffInMinutes)} minutes ago`;
+ }
+
+ const diffInHours = diffInMinutes / 60;
+ if (diffInHours < 24) {
+ return `${Math.floor(diffInHours)} hours ago`;
+ }
+
+ const diffInDays = diffInHours / 24;
+ if (diffInDays < 7) {
+ return `${Math.floor(diffInDays)} days ago`;
+ }
+
+ const diffInWeeks = diffInDays / 7;
+ if (diffInWeeks < 5) {
+ return `${Math.floor(diffInWeeks)} weeks ago`;
+ }
+
+ const diffInMonths = diffInDays / 30;
+ if (diffInMonths < 12) {
+ return `${Math.floor(diffInMonths)} months ago`;
+ }
+
+ const diffInYears = diffInMonths / 12;
+ return `${Math.floor(diffInYears)} years ago`;
+
+ }
+
\ No newline at end of file