Skip to content

g1t/packages/contracts/src/teams.ts

178 lines6,326 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1/**
2 * Teams: groups of a workspace's members, given roles on repositories
3 * together, mentioned together and asked to review together. Kept by the
4 * identity service; mirrors `crates/contracts/src/teams.rs`.
5 */
6import type { RepoRole } from "./access";
7import type { User } from "./identity";
8import type { Result } from "./result";
9
10export type TeamVisibility = "visible" | "secret";
Merge branch 'worktree-agent-ad7c6d88d93adc817'11
12/**
13 * Who may create a workspace's teams: any member (the default) or its
14 * owners only. Owners change it in the workspace's settings.
15 */
16export type TeamCreation = "members" | "owners";
17
18/** Whether someone with `role` in a workspace may create its teams. */
19export function mayCreateTeams(setting: TeamCreation | null | undefined, role: "owner" | "member" | null | undefined): boolean {
20 if (!role) return false;
21 return (setting ?? "members") === "members" || role === "owner";
22}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar23export type TeamRole = "member" | "maintainer";
24export type ReviewAlgorithm = "round_robin" | "load_balance";
25
26export const TEAM_VISIBILITY_LABELS: Record<TeamVisibility, string> = {
27 visible: "Visible",
28 secret: "Secret",
29};
30
31export const TEAM_VISIBILITY_SUMMARIES: Record<TeamVisibility, string> = {
32 visible: "Every member of the workspace can see it and mention it.",
33 secret: "Only its own people and the workspace's owners can see it. Secret teams cannot be nested.",
34};
35
36export const REVIEW_ALGORITHM_LABELS: Record<ReviewAlgorithm, string> = {
37 round_robin: "Round robin",
38 load_balance: "Load balance",
39};
40
41export const REVIEW_ALGORITHM_SUMMARIES: Record<ReviewAlgorithm, string> = {
42 round_robin: "Whoever this team asked least recently goes first.",
43 load_balance: "Whoever has the fewest pull requests waiting on their review goes first.",
44};
45
46/** The most people review assignment picks for one request. */
47export const MAX_ASSIGNED = 10;
48
49export type ReviewAssignment = {
50 enabled: boolean;
51 algorithm: ReviewAlgorithm;
52 count: number;
53 skip_busy: boolean;
54 busy_at: number;
55 include_child_teams: boolean;
56 excluded: string[];
57 notify_team: boolean;
58};
59
60export const DEFAULT_REVIEW_ASSIGNMENT: ReviewAssignment = {
61 enabled: false,
62 algorithm: "round_robin",
63 count: 1,
64 skip_busy: false,
65 busy_at: 5,
66 include_child_teams: false,
67 excluded: [],
68 notify_team: false,
69};
70
71export type TeamRef = { slug: string; name: string };
72
73export type Team = {
74 id: string;
75 workspace: string;
76 slug: string;
77 name: string;
78 description: string | null;
79 visibility: TeamVisibility;
80 parent: TeamRef | null;
81 notify: boolean;
82 review_assignment: ReviewAssignment;
83 members_count: number;
84 repos_count: number;
85 child_teams_count: number;
86 viewer_role: TeamRole | null;
87 can_manage: boolean;
88 created_at: string;
89 updated_at: string;
90};
91
92export type TeamMember = {
93 username: string;
94 name: string | null;
95 avatar: string | null;
96 role: TeamRole;
97 /** The child team they are in, when listed through one. */
98 via: string | null;
99};
100
101export type TeamRepo = {
102 /** `workspace/name`. */
103 repo: string;
104 repo_id: string;
105 role: RepoRole;
106 /** The parent team it comes from, when inherited. */
107 inherited_from: string | null;
108};
109
110/** A team with a role on a repository, as its Access settings list it. */
111export type RepoTeam = {
112 slug: string;
113 name: string;
114 role: RepoRole;
115 members_count: number;
116 visibility: TeamVisibility;
117};
118
119export type NewTeam = {
120 name: string;
121 slug?: string | null;
122 description?: string | null;
123 visibility?: TeamVisibility | null;
124 parent?: string | null;
125 notify?: boolean | null;
126 members?: string[];
127};
128
129/** What changes; the rest stays. `parent: ""` takes the team out from under its parent. */
130export type TeamChanges = {
131 name?: string;
132 slug?: string;
133 description?: string;
134 visibility?: TeamVisibility;
135 parent?: string;
136 notify?: boolean;
137 review_assignment?: ReviewAssignment;
138};
139
140/** `@acme/backend`, the way a team is mentioned. */
141export function teamHandle(team: { workspace: string; slug: string }): string {
142 return `@${team.workspace}/${team.slug}`;
143}
144
145/** A team's slug from its name, as identity makes it. Null when nothing is left. */
146export function teamSlug(name: string): string | null {
147 let slug = "";
148 for (const c of name.trim()) {
149 if (/[A-Za-z0-9]/.test(c)) slug += c.toLowerCase();
150 else if (slug && !slug.endsWith("-")) slug += "-";
151 }
152 slug = slug.replace(/-+$/, "").slice(0, 60).replace(/-+$/, "");
153 return slug ? slug : null;
154}
155
156/** One person's teams in a workspace, for the Members page. */
157export type MemberTeams = { username: string; teams: TeamRef[] };
158
159/** Identity's team methods. */
160export interface TeamsClient {
161 listTeams(viewer: User | null, workspace: string, query?: string | null): Promise<Result<Team[]>>;
162 getTeam(viewer: User | null, workspace: string, team: string): Promise<Result<Team>>;
163 createTeam(actor: User, workspace: string, team: NewTeam): Promise<Result<Team>>;
Merge branch 'worktree-agent-ad7c6d88d93adc817'164 /** Who may create the workspace's teams. Owners only. */
165 setTeamCreation(actor: User, slug: string, setting: TeamCreation): Promise<Result<TeamCreation>>;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar166 updateTeam(actor: User, workspace: string, team: string, changes: TeamChanges): Promise<Result<Team>>;
167 deleteTeam(actor: User, workspace: string, team: string): Promise<Result<boolean>>;
168 teamMembers(viewer: User | null, workspace: string, team: string, includeChildTeams?: boolean): Promise<Result<TeamMember[]>>;
169 setTeamMember(actor: User, workspace: string, team: string, username: string, role: TeamRole): Promise<Result<TeamMember>>;
170 removeTeamMember(actor: User, workspace: string, team: string, username: string): Promise<Result<boolean>>;
171 childTeams(viewer: User | null, workspace: string, team: string): Promise<Result<Team[]>>;
172 teamRepos(viewer: User | null, workspace: string, team: string): Promise<Result<TeamRepo[]>>;
173 setTeamRepo(actor: User, workspace: string, team: string, owner: string, name: string, role: RepoRole): Promise<Result<TeamRepo>>;
174 removeTeamRepo(actor: User, workspace: string, team: string, owner: string, name: string): Promise<Result<boolean>>;
175 userTeams(viewer: User | null, workspace: string, username: string): Promise<Result<Team[]>>;
176 /** Each member's teams the viewer can see. Members only. */
177 teamMemberships(viewer: User | null, workspace: string): Promise<Result<MemberTeams[]>>;
178}

This file's history is long; its oldest lines are credited to the oldest commit read.