Skip to content

Commit

Permalink
feat: error messages, probot context in adapters
Browse files Browse the repository at this point in the history
  • Loading branch information
whilefoo committed Nov 10, 2023
1 parent 3c67919 commit 33b955c
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 102 deletions.
3 changes: 1 addition & 2 deletions src/adapters/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createClient } from "@supabase/supabase-js";
import { Context } from "../types";

Check failure on line 2 in src/adapters/index.ts

View workflow job for this annotation

GitHub Actions / build

'Context' is declared but its value is never read.

Check failure on line 2 in src/adapters/index.ts

View workflow job for this annotation

GitHub Actions / e2e-test

'Context' is declared but its value is never read.
import { Context as ProbotContext } from "probot";
import { Access } from "./supabase/helpers/tables/access";
import { Label } from "./supabase/helpers/tables/label";
Expand All @@ -10,7 +11,6 @@ import { User } from "./supabase/helpers/tables/user";
import { Wallet } from "./supabase/helpers/tables/wallet";
import { Database } from "./supabase/types";
import { env } from "../bindings/env";
import OpenAI from "openai";

const supabaseClient = createClient<Database>(env.SUPABASE_URL, env.SUPABASE_KEY, { auth: { persistSession: false } });

Expand All @@ -27,6 +27,5 @@ export function createAdapters(context: ProbotContext) {
locations: new Locations(supabaseClient, context),
super: new Super(supabaseClient, context),
},
openAi: context.config.keys.openAi ? new OpenAI({ apiKey: context.config.keys.openAi }) : null,
};
}
1 change: 0 additions & 1 deletion src/adapters/supabase/helpers/tables/access.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { GitHubNode } from "../client";
import { Super } from "./super";
import { UserRow } from "./user";
import { Context as ProbotContext } from "probot";

type AccessRow = Database["public"]["Tables"]["access"]["Row"];
type AccessInsert = Database["public"]["Tables"]["access"]["Insert"];
type UserWithAccess = (UserRow & { access: AccessRow | null })[];
Expand Down
2 changes: 1 addition & 1 deletion src/adapters/supabase/helpers/tables/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { prettyLogs } from "../pretty-logs";
import { Super } from "./super";
import { execSync } from "child_process";
import { LogLevel } from "../../../../types/logs";
import Runtime from "../../../../bindings/bot-runtime";
import { Context as ProbotContext } from "probot";
import Runtime from "../../../../bindings/bot-runtime";

type LogFunction = (message: string, metadata?: any) => void;
type LogInsert = Database["public"]["Tables"]["logs"]["Insert"];
Expand Down
7 changes: 2 additions & 5 deletions src/adapters/supabase/helpers/tables/wallet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ dotenv.config();

import { Context as ProbotContext } from "probot";
import { createAdapters } from "../../..";
import { loadConfiguration } from "../../../../bindings/config";
import { Context, User } from "../../../../types";
import { User } from "../../../../types";
const SUPABASE_URL = process.env.SUPABASE_URL;
if (!SUPABASE_URL) throw new Error("SUPABASE_URL is not defined");
const SUPABASE_KEY = process.env.SUPABASE_KEY;
Expand All @@ -13,9 +12,7 @@ if (!SUPABASE_KEY) throw new Error("SUPABASE_KEY is not defined");
const mockContext = { supabase: { url: SUPABASE_URL, key: SUPABASE_KEY } } as unknown as ProbotContext;

async function getWalletAddressAndUrlTest(eventContext: ProbotContext) {
const botConfig = await loadConfiguration(eventContext);
const context: Context = { event: eventContext, config: botConfig };
const { wallet } = createAdapters(context).supabase;
const { wallet } = createAdapters(eventContext).supabase;
const userId = 4975670 as User["id"];
const results = [] as unknown[];
try {
Expand Down
61 changes: 31 additions & 30 deletions src/bindings/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { ajv } from "../utils/ajv";
import Runtime from "./bot-runtime";
import { loadConfiguration } from "./config";
import { Context } from "../types/context";
import OpenAI from "openai";
import { BotConfig } from "../types";

const allowedEvents = Object.values(GitHubEvent) as string[];

Expand All @@ -26,31 +28,12 @@ const validatePayload = ajv.compile(PayloadSchema);

export async function bindEvents(eventContext: ProbotContext) {
const runtime = Runtime.getState();
runtime.adapters = createAdapters(eventContext);
runtime.logger = runtime.adapters.supabase.logs;

const payload = eventContext.payload as Payload;
const eventName = payload?.action ? `${eventContext.name}.${payload?.action}` : eventContext.name; // some events wont have actions as this grows

if (eventName === GitHubEvent.PUSH_EVENT) {
await validateConfigChange(eventContext);
}

const botConfig = await loadConfiguration(eventContext);
const context: Context = {
event: eventContext,
config: botConfig,
};

runtime.adapters = createAdapters(context);
runtime.logger = runtime.adapters.supabase.logs;

if (!context.config.keys.evmPrivateEncrypted) {
runtime.logger.warn("No EVM private key found");
}

if (!runtime.logger) {
throw new Error("Failed to create logger");
}

runtime.logger.info("Event received", { id: eventContext.id, name: eventName });

if (!allowedEvents.includes(eventName)) {
Expand All @@ -61,24 +44,42 @@ export async function bindEvents(eventContext: ProbotContext) {
// Skip validation for installation event and push
if (!NO_VALIDATION.includes(eventName)) {
// Validate payload
// console.trace({ payload });
const valid = validatePayload(payload);
if (!valid) {
// runtime.logger.info("Payload schema validation failed!", payload);
if (validatePayload.errors) {
console.dir(payload, { depth: null, colors: true });
return runtime.logger.error("validation errors", validatePayload.errors);
}
// return;
if (!valid && validatePayload.errors) {
return runtime.logger.error("Payload schema validation failed!", validatePayload.errors);
}

// Check if we should skip the event
const should = shouldSkip(context);
const should = shouldSkip(eventContext);
if (should.stop) {
return runtime.logger.info("Skipping the event.", { reason: should.reason });
}
}

if (eventName === GitHubEvent.PUSH_EVENT) {
await validateConfigChange(eventContext);
}

let botConfig: BotConfig;
try {
botConfig = await loadConfiguration(eventContext);
} catch (error) {
return;
}
const context: Context = {
event: eventContext,
config: botConfig,
openAi: botConfig.keys.openAi ? new OpenAI({ apiKey: botConfig.keys.openAi }) : null,
};

if (!context.config.keys.evmPrivateEncrypted) {
runtime.logger.warn("No EVM private key found");
}

if (!runtime.logger) {
throw new Error("Failed to create logger");
}

// Get the handlers for the action
const handlers = processors[eventName];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import OpenAI from "openai";
import { encodingForModel } from "js-tiktoken";
import Decimal from "decimal.js";
import Runtime from "../../../../bindings/bot-runtime";
import { Context } from "../../../../types";

export async function calculateQualScore(issue: Issue, contributorComments: Comment[]) {
const sumOfConversationTokens = countTokensOfConversation(issue, contributorComments);
Expand Down Expand Up @@ -49,13 +50,13 @@ export function countTokensOfConversation(issue: Issue, comments: Comment[]) {
}

export async function gptRelevance(
context: Context,
model: string,
ISSUE_SPECIFICATION_BODY: string,
CONVERSATION_STRINGS: string[],
ARRAY_LENGTH = CONVERSATION_STRINGS.length
) {
const runtime = Runtime.getState();
const openAi = runtime.adapters.openAi;
const openAi = context.openAi;
if (!openAi) throw new Error("OpenAI adapter is not defined");
const PROMPT = `I need to evaluate the relevance of GitHub contributors' comments to a specific issue specification. Specifically, I'm interested in how much each comment helps to further define the issue specification or contributes new information or research relevant to the issue. Please provide a float between 0 and 1 to represent the degree of relevance. A score of 1 indicates that the comment is entirely relevant and adds significant value to the issue, whereas a score of 0 indicates no relevance or added value. Each contributor's comment is on a new line.\n\nIssue Specification:\n\`\`\`\n${ISSUE_SPECIFICATION_BODY}\n\`\`\`\n\nConversation:\n\`\`\`\n${CONVERSATION_STRINGS.join(
"\n"
Expand Down
44 changes: 36 additions & 8 deletions src/handlers/push/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Context as ProbotContext } from "probot";
import Runtime from "../../bindings/bot-runtime";
import { createCommitComment, getFileContent } from "../../helpers";
import { CommitsPayload, PushPayload, validateBotConfig } from "../../types";
import { generateValidationError, parseYaml, transformConfig } from "../../utils/generate-configuration";
import { BotConfig, CommitsPayload, PushPayload, validateBotConfig } from "../../types";
import { parseYaml, transformConfig } from "../../utils/generate-configuration";
import { DefinedError } from "ajv";

export const ZERO_SHA = "0000000000000000000000000000000000000000";
Expand Down Expand Up @@ -63,17 +63,24 @@ export async function validateConfigChange(context: ProbotContext) {
if (configFileContent) {
const decodedConfig = Buffer.from(configFileContent, "base64").toString();
const config = parseYaml(decodedConfig);
const result = validateBotConfig(config);
const valid = validateBotConfig(config);
let errorMsg: string | undefined;
if (!result) {
const err = generateValidationError(validateBotConfig.errors as DefinedError[]);
errorMsg = `@${payload.sender.login} ${err.toString()}`;

if (!valid) {
const errMsg = generateValidationError(validateBotConfig.errors as DefinedError[]);
errorMsg = `@${payload.sender.login} ${errMsg}`;
}

try {
transformConfig(config);
transformConfig(config as BotConfig);
} catch (err) {
errorMsg = `@${payload.sender.login} Config validation failed! ${JSON.stringify(err)}`;
if (errorMsg) {
errorMsg += `\nConfig tranformation failed:\n${err}`;
} else {
errorMsg = `@${payload.sender.login} Config tranformation failed:\n${err}`;
}
}

if (errorMsg) {
logger.info("Config validation failed!", errorMsg);
await createCommitComment(context, errorMsg, commitSha, BASE_RATE_FILE);
Expand All @@ -85,3 +92,24 @@ export async function validateConfigChange(context: ProbotContext) {
logger.debug(`Skipping push events, file change doesnt include config file: ${JSON.stringify(changes)}`);
}
}

function generateValidationError(errors: DefinedError[]) {
const errorsWithoutStrict = errors.filter((error) => error.keyword !== "additionalProperties");
const errorsOnlyStrict = errors.filter((error) => error.keyword === "additionalProperties");
const isValid = errorsWithoutStrict.length === 0;
const errorMsg = isValid
? ""
: errorsWithoutStrict.map((error) => error.instancePath.replaceAll("/", ".") + " " + error.message).join("\n");
const warningMsg =
errorsOnlyStrict.length > 0
? "Warning! Unneccesary properties: \n" +
errorsOnlyStrict
.map(
(error) =>
error.keyword === "additionalProperties" &&
error.instancePath.replaceAll("/", ".") + "." + error.params.additionalProperty
)
.join("\n")
: "";
return `${isValid ? "Valid" : "Invalid"} configuration. \n${errorMsg}\n${warningMsg}`;
}
11 changes: 6 additions & 5 deletions src/helpers/shared.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import ms from "ms";
import { Label, Payload, UserType, Context } from "../types";
import { Label, Payload, UserType } from "../types";
import { Context as ProbotContext } from "probot";

const contextNamesToSkip = ["workflow_run"];

export function shouldSkip(context: Context) {
const payload = context.event.payload as Payload;
export function shouldSkip(context: ProbotContext) {
const payload = context.payload as Payload;
const response = { stop: false, reason: null } as { stop: boolean; reason: string | null };

if (contextNamesToSkip.includes(context.event.name)) {
if (contextNamesToSkip.includes(context.name)) {
response.stop = true;
response.reason = `excluded context name: "${context.event.name}"`;
response.reason = `excluded context name: "${context.name}"`;
} else if (payload.sender.type === UserType.Bot) {
response.stop = true;
response.reason = "sender is a bot";
Expand Down
2 changes: 2 additions & 0 deletions src/types/context.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Context as ProbotContext } from "probot";
import { BotConfig } from "./";
import OpenAI from "openai";

export interface Context {
event: ProbotContext;
config: BotConfig;
openAi: OpenAI | null;
}
Loading

0 comments on commit 33b955c

Please sign in to comment.