Skip to content

Commit

Permalink
chore(WIP): simplifying roles to only contributor and collaborator
Browse files Browse the repository at this point in the history
  • Loading branch information
gentlementlegen committed Jan 4, 2025
1 parent 3edaa94 commit 3d6f4af
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 44 deletions.
31 changes: 22 additions & 9 deletions src/handlers/shared/get-user-task-limit-and-role.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,33 @@
import { Context } from "../../types";
import { ADMIN_ROLES, COLLABORATOR_ROLES, Context, PluginSettings } from "../../types";

interface MatchingUserProps {
role: string;
limit: number;
}

export function isAdminRole(role: string) {
return ADMIN_ROLES.includes(role);
}

export function isCollaboratorRole(role: string) {
return COLLABORATOR_ROLES.includes(role);
}

export function getUserTaskLimit(maxConcurrentTasks: PluginSettings["maxConcurrentTasks"], role: string) {
if (isAdminRole(role)) {
return Infinity;
}
if (isCollaboratorRole(role)) {
return maxConcurrentTasks.collaborator;
}
return maxConcurrentTasks.contributor;
}

export async function getUserRoleAndTaskLimit(context: Context, user: string): Promise<MatchingUserProps> {
const orgLogin = context.payload.organization?.login;
const { config, logger, octokit } = context;
const { maxConcurrentTasks } = config;

const minUserTaskLimit = Object.entries(maxConcurrentTasks).reduce((minTask, [role, limit]) => (limit < minTask.limit ? { role, limit } : minTask), {
role: "",
limit: Infinity,
} as MatchingUserProps);

try {
// Validate the organization login
if (typeof orgLogin !== "string" || orgLogin.trim() === "") {
Expand All @@ -30,7 +43,7 @@ export async function getUserRoleAndTaskLimit(context: Context, user: string): P
username: user,
});
role = response.data.role.toLowerCase();
limit = maxConcurrentTasks[role] ?? Infinity;
limit = getUserTaskLimit(maxConcurrentTasks, role);
return { role, limit };
} catch (err) {
logger.error("Could not get user membership", { err });
Expand All @@ -51,11 +64,11 @@ export async function getUserRoleAndTaskLimit(context: Context, user: string): P
role,
data: permissionLevel.data,
});
limit = maxConcurrentTasks[role] ?? Infinity;
limit = getUserTaskLimit(maxConcurrentTasks, role);

return { role, limit };
} catch (err) {
logger.error("Could not get user role", { err });
return minUserTaskLimit;
return { role: "unknown", limit: maxConcurrentTasks.contributor };
}
}
52 changes: 17 additions & 35 deletions src/types/plugin-input.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { StaticDecode, TLiteral, Type as T, Union } from "@sinclair/typebox";
import { StaticDecode, Type as T } from "@sinclair/typebox";

export enum AssignedIssueScope {
ORG = "org",
Expand All @@ -13,6 +13,11 @@ export enum Role {
COLLABORATOR = "COLLABORATOR",
}

// These correspond to getMembershipForUser and getCollaboratorPermissionLevel for a user.
// Anything outside these values is considered to be a contributor (external user).
export const ADMIN_ROLES = ["admin", "owner", "billing_manager"];
export const COLLABORATOR_ROLES = ["write", "member"];

const rolesWithReviewAuthority = T.Array(T.Enum(Role), {
default: [Role.OWNER, Role.ADMIN, Role.MEMBER, Role.COLLABORATOR],
uniqueItems: true,
Expand All @@ -23,47 +28,24 @@ const rolesWithReviewAuthority = T.Array(T.Enum(Role), {
],
});

const maxConcurrentTasks = T.Transform(
T.Record(T.String(), T.Integer(), {
default: { member: 10, contributor: 2 },
const maxConcurrentTasks = T.Object(
{ collaborator: T.Number({ default: 10 }), contributor: T.Number({ default: 5 }) },
{
description: "The maximum number of tasks a user can have assigned to them at once, based on their role.",
examples: [{ member: 5, contributor: 1 }],
})
)
.Decode((obj) => {
// normalize the role keys to lowercase
obj = Object.keys(obj).reduce(
(acc, key) => {
acc[key.toLowerCase()] = obj[key];
return acc;
},
{} as Record<string, number>
);

// If admin is omitted, defaults to infinity
if (!obj["admin"]) {
obj["admin"] = Infinity;
}

return obj;
})
.Encode((value) => value);

type IntoStringLiteralUnion<T> = { [K in keyof T]: T[K] extends string ? TLiteral<T[K]> : never };

export function stringLiteralUnion<T extends string[]>(values: [...T]): Union<IntoStringLiteralUnion<T>> {
const literals = values.map((value) => T.Literal(value));
return T.Union(literals) as Union<IntoStringLiteralUnion<T>>;
}
examples: [{ collaborator: 5, contributor: 1 }],
default: {},
}
);

const roles = stringLiteralUnion(["admin", "member", "collaborator", "contributor", "owner", "billing_manager", "read", "write"]);
const roles = T.KeyOf(maxConcurrentTasks);

const requiredLabel = T.Object({
name: T.String({ description: "The name of the required labels to start the task." }),
roles: T.Array(roles, {
allowedRoles: T.Array(roles, {
description: "The list of allowed roles to start the task with the given label.",
uniqueItems: true,
default: ["admin", "member", "collaborator", "contributor", "owner", "billing_manager", "write", "read"],
default: ["collaborator", "contributor"],
examples: [["collaborator", "contributor"]],
}),
});

Expand Down

0 comments on commit 3d6f4af

Please sign in to comment.