Skip to content

Commit

Permalink
Merge branch 'development' into fix/config-props
Browse files Browse the repository at this point in the history
  • Loading branch information
Keyrxng authored Nov 26, 2024
2 parents 44a0043 + a787355 commit cfed1de
Show file tree
Hide file tree
Showing 26 changed files with 7,135 additions and 9,772 deletions.
2 changes: 1 addition & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json",
"version": "0.2",
"ignorePaths": ["**/*.json", "**/*.css", "node_modules", "**/*.log", "./src/adapters/supabase/**/**.ts"],
"ignorePaths": ["**/*.json", "**/*.css", "bun.lockb", "tests/http/**", "node_modules", "**/*.log", "./src/adapters/supabase/**/**.ts"],
"useGitignore": true,
"language": "en",
"words": [
Expand Down
2 changes: 1 addition & 1 deletion .github/knip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const config: KnipConfig = {
ignore: ["src/types/config.ts", "**/__mocks__/**", "**/__fixtures__/**", "src/worker.ts"],
ignoreExportsUsedInFile: true,
// eslint can also be safely ignored as per the docs: https://knip.dev/guides/handling-issues#eslint--jest
ignoreDependencies: ["eslint-config-prettier", "eslint-plugin-prettier", "@types/jest"],
ignoreDependencies: ["eslint-config-prettier", "eslint-plugin-prettier", "ts-node", "@octokit/rest"],
eslint: true,
};

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ To stop a task, a hunter should use the `/stop` command. This will unassign them

#### Note: The command name is `"start"` when configuring your `.ubiquity-os.config.yml` file.

To configure your Ubiquibot to run this plugin, add the following to the `.ubiquity-os.config.yml` file in your organization configuration repository.
To configure your Ubiquity Kernel to run this plugin, add the following to the `.ubiquity-os.config.yml` file in your organization configuration repository.

```yml
- plugin: http://localhost:4000 # or the URL where the plugin is hosted
Expand Down
10 changes: 0 additions & 10 deletions jest.config.json

This file was deleted.

27 changes: 27 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { Config } from "jest";

const cfg: Config = {
transform: {
"^.+\\.tsx?$": [
"ts-jest",
{
useESM: true,
},
],
},
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
coveragePathIgnorePatterns: ["node_modules", "mocks"],
collectCoverage: true,
coverageReporters: ["json", "lcov", "text", "clover", "json-summary"],
reporters: ["default", "jest-junit", "jest-md-dashboard"],
coverageDirectory: "coverage",
testTimeout: 20000,
roots: ["<rootDir>", "tests"],
extensionsToTreatAsEsm: [".ts"],
moduleNameMapper: {
"^(\\.{1,2}/.*)\\.js$": "$1",
},
setupFilesAfterEnv: ["dotenv/config"],
};

export default cfg;
47 changes: 41 additions & 6 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "Start | Stop",
"description": "Assign or un-assign yourself from an issue.",
"description": "Assign or un-assign yourself from an issue/task.",
"ubiquity:listeners": [
"issue_comment.created",
"issues.assigned",
Expand All @@ -11,11 +11,28 @@
"commands": {
"start": {
"ubiquity:example": "/start",
"description": "Assign yourself to the issue."
"description": "Assign yourself and/or others to the issue/task.",
"parameters": {
"type": "object",
"properties": {
"teammates": {
"description": "Users other than yourself to assign to the issue",
"type": "array",
"items": {
"description": "Github username",
"type": "string"
}
}
}
}
},
"stop": {
"ubiquity:example": "/stop",
"description": "Unassign yourself from the issue."
"description": "Unassign yourself from the issue/task.",
"parameters": {
"type": "object",
"properties": {}
}
}
},
"configuration": {
Expand Down Expand Up @@ -75,15 +92,33 @@
},
"rolesWithReviewAuthority": {
"default": [
"COLLABORATOR",
"OWNER",
"ADMIN",
"MEMBER",
"ADMIN"
"COLLABORATOR"
],
"description": "When considering a user for a task: which roles should be considered as having review authority? All others are ignored.",
"uniqueItems": true,
"type": "array",
"items": {
"type": "string"
"anyOf": [
{
"const": "OWNER",
"type": "string"
},
{
"const": "ADMIN",
"type": "string"
},
{
"const": "MEMBER",
"type": "string"
},
{
"const": "COLLABORATOR",
"type": "string"
}
]
}
},
"requiredLabelsToStart": {
Expand Down
32 changes: 16 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,31 +28,30 @@
],
"dependencies": {
"@octokit/graphql-schema": "15.25.0",
"@octokit/plugin-paginate-graphql": "5.2.2",
"@octokit/rest": "20.1.1",
"@octokit/plugin-rest-endpoint-methods": "^13.2.6",
"@octokit/types": "^13.6.1",
"@octokit/webhooks": "13.2.7",
"@sinclair/typebox": "^0.32.5",
"@sinclair/typebox": "0.34.3",
"@supabase/supabase-js": "2.42.0",
"@ubiquity-os/plugin-sdk": "^1.1.0",
"@ubiquity-os/ubiquity-os-logger": "^1.3.2",
"dotenv": "^16.4.4",
"ms": "^2.1.3",
"typebox-validators": "^0.3.5"
"ms": "^2.1.3"
},
"devDependencies": {
"@commitlint/cli": "19.3.0",
"@commitlint/config-conventional": "19.2.2",
"@cspell/dict-node": "5.0.1",
"@cspell/dict-software-terms": "3.4.6",
"@cspell/dict-typescript": "3.1.5",
"@eslint/js": "9.5.0",
"@commitlint/cli": "^19.5.0",
"@commitlint/config-conventional": "^19.5.0",
"@cspell/dict-node": "^5.0.5",
"@cspell/dict-software-terms": "^4.1.15",
"@cspell/dict-typescript": "^3.1.2",
"@eslint/js": "9.14.0",
"@jest/globals": "29.7.0",
"@mswjs/data": "0.16.1",
"@octokit/rest": "20.1.1",
"@types/jest": "29.5.12",
"@types/ms": "^0.7.34",
"@types/node": "20.14.5",
"cspell": "8.9.0",
"eslint": "9.5.0",
"eslint": "9.14.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-check-file": "2.8.0",
"eslint-plugin-prettier": "5.1.3",
Expand All @@ -66,10 +65,11 @@
"npm-run-all": "4.1.5",
"prettier": "3.3.2",
"ts-jest": "29.1.5",
"ts-node": "^10.9.2",
"tsx": "4.15.6",
"typescript": "5.6.2",
"typescript-eslint": "7.13.1",
"wrangler": "^3.79.0"
"typescript-eslint": "8.14.0",
"wrangler": "^3.87.0"
},
"lint-staged": {
"*.ts": [
Expand All @@ -85,5 +85,5 @@
"@commitlint/config-conventional"
]
},
"packageManager": "yarn@4.2.2"
"packageManager": "yarn@1.22.22"
}
3 changes: 2 additions & 1 deletion src/handlers/result-types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
export enum HttpStatusCode {
OK = 200,
NOT_MODIFIED = 304,
BAD_REQUEST = 400,
}

export interface Result {
export interface Result extends Record<string, unknown> {
status: HttpStatusCode;
content?: string;
reason?: string;
Expand Down
4 changes: 2 additions & 2 deletions src/handlers/shared/check-assignments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ async function getUserStopComments(context: Context, username: string): Promise<
const { owner, repo } = getOwnerRepoFromHtmlUrl(html_url);

try {
const comments = await octokit.paginate(octokit.issues.listComments, {
const comments = await octokit.paginate(octokit.rest.issues.listComments, {
owner,
repo,
issue_number: number,
Expand Down Expand Up @@ -60,7 +60,7 @@ async function getAssignmentEvents(context: Context) {
}
const { repository, issue } = context.payload;
try {
const data = await context.octokit.paginate(context.octokit.issues.listEventsForTimeline, {
const data = await context.octokit.paginate(context.octokit.rest.issues.listEventsForTimeline, {
owner: repository.owner.login,
repo: repository.name,
issue_number: issue.number,
Expand Down
2 changes: 1 addition & 1 deletion src/handlers/shared/get-user-task-limit-and-role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export async function getUserRoleAndTaskLimit(context: Context, user: string): P
throw new Error("Invalid organization name");
}

const response = await context.octokit.orgs.getMembershipForUser({
const response = await context.octokit.rest.orgs.getMembershipForUser({
org: orgLogin,
username: user,
});
Expand Down
19 changes: 19 additions & 0 deletions src/handlers/user-start-stop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,25 @@ import { getDeadline } from "./shared/generate-assignment-comment";
import { start } from "./shared/start";
import { stop } from "./shared/stop";

export async function commandHandler(context: Context): Promise<Result> {
if (!isIssueCommentEvent(context)) {
return { status: HttpStatusCode.NOT_MODIFIED };
}
if (!context.command) {
return { status: HttpStatusCode.NOT_MODIFIED };
}
const { issue, sender, repository } = context.payload;

if (context.command.name === "stop") {
return await stop(context, issue, sender, repository);
} else if (context.command.name === "start") {
const teammates = context.command.parameters.teammates ?? [];
return await start(context, issue, sender, teammates);
} else {
return { status: HttpStatusCode.BAD_REQUEST };
}
}

export async function userStartStop(context: Context): Promise<Result> {
if (!isIssueCommentEvent(context)) {
return { status: HttpStatusCode.NOT_MODIFIED };
Expand Down
80 changes: 26 additions & 54 deletions src/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,60 +1,32 @@
import { paginateGraphQL } from "@octokit/plugin-paginate-graphql";
import { Octokit } from "@octokit/rest";
import { createClient } from "@supabase/supabase-js";
import { LogReturn, Logs } from "@ubiquity-os/ubiquity-os-logger";
import { createAdapters } from "./adapters";
import { userPullRequest, userSelfAssign, userStartStop, userUnassigned } from "./handlers/user-start-stop";
import { Context, Env, PluginInputs } from "./types";
import { addCommentToIssue } from "./utils/issue";
import { commandHandler, userPullRequest, userSelfAssign, userStartStop, userUnassigned } from "./handlers/user-start-stop";
import { Context } from "./types";
import { listOrganizations } from "./utils/list-organizations";
import { HttpStatusCode } from "./handlers/result-types";
import { createAdapters } from "./adapters";
import { createClient } from "@supabase/supabase-js";

export async function startStopTask(inputs: PluginInputs, env: Env) {
const customOctokit = Octokit.plugin(paginateGraphQL);
const octokit = new customOctokit({ auth: inputs.authToken });
const supabase = createClient(env.SUPABASE_URL, env.SUPABASE_KEY);

const context: Context = {
eventName: inputs.eventName,
payload: inputs.eventPayload,
config: inputs.settings,
organizations: [],
octokit,
env,
logger: new Logs("info"),
adapters: {} as ReturnType<typeof createAdapters>,
};

context.adapters = createAdapters(supabase, context);

try {
const organizations = await listOrganizations(context);
context.organizations = organizations;
export async function startStopTask(context: Context) {
context.adapters = createAdapters(createClient(context.env.SUPABASE_URL, context.env.SUPABASE_KEY), context as Context);
const organizations = await listOrganizations(context);
context.organizations = organizations;

switch (context.eventName) {
case "issue_comment.created":
return await userStartStop(context);
case "issues.assigned":
return await userSelfAssign(context as Context<"issues.assigned">);
case "pull_request.opened":
return await userPullRequest(context as Context<"pull_request.opened">);
case "pull_request.edited":
return await userPullRequest(context as Context<"pull_request.edited">);
case "issues.unassigned":
return await userUnassigned(context as Context<"issues.unassigned">);
default:
context.logger.error(`Unsupported event: ${context.eventName}`);
}
} catch (err) {
let errorMessage;
if (err instanceof LogReturn) {
errorMessage = err;
await addCommentToIssue(context, `${errorMessage?.logMessage.diff}\n<!--\n${sanitizeMetadata(errorMessage?.metadata)}\n-->`);
} else {
context.logger.error("An error occurred", { err });
}
if (context.command) {
return await commandHandler(context);
}
}

function sanitizeMetadata(obj: LogReturn["metadata"]): string {
return JSON.stringify(obj, null, 2).replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/--/g, "&#45;&#45;");
switch (context.eventName) {
case "issue_comment.created":
return await userStartStop(context as Context<"issue_comment.created">);
case "issues.assigned":
return await userSelfAssign(context as Context<"issues.assigned">);
case "pull_request.opened":
return await userPullRequest(context as Context<"pull_request.opened">);
case "pull_request.edited":
return await userPullRequest(context as Context<"pull_request.edited">);
case "issues.unassigned":
return await userUnassigned(context as Context<"issues.unassigned">);
default:
context.logger.error(`Unsupported event: ${context.eventName}`);
return { status: HttpStatusCode.BAD_REQUEST };
}
}
17 changes: 17 additions & 0 deletions src/types/command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Type as T } from "@sinclair/typebox";
import { StaticDecode } from "@sinclair/typebox";

export const startCommandSchema = T.Object({
name: T.Literal("start"),
parameters: T.Object({
teammates: T.Array(T.String()),
}),
});

export const stopCommandSchema = T.Object({
name: T.Literal("stop"),
});

export const commandSchema = T.Union([startCommandSchema, stopCommandSchema]);

export type Command = StaticDecode<typeof commandSchema>;
Loading

0 comments on commit cfed1de

Please sign in to comment.