diff --git a/web/src/portico/team.js b/web/src/portico/team.ts similarity index 67% rename from web/src/portico/team.js rename to web/src/portico/team.ts index d5deb81272..3580f931a1 100644 --- a/web/src/portico/team.js +++ b/web/src/portico/team.ts @@ -1,7 +1,26 @@ import $ from "jquery"; import _ from "lodash"; -const repo_name_to_tab_name = { +// The list of repository names is duplicated here in order to provide +// a clear type for Contributor objects. +// +// TODO: We can avoid this if we introduce a `contributions` object +// referenced from Contributor, rather than having repository names be +// direct keys in the namespace that also has `email`. +const all_repository_names = [ + "zulip", + "zulip-desktop", + "zulip-mobile", + "python-zulip-api", + "zulip-js", + "zulipbot", + "zulip-terminal", + "zulip-ios-legacy", + "zulip-android", +] as const; +type RepositoryName = (typeof all_repository_names)[number]; + +const repo_name_to_tab_name: Record = { zulip: "server", "zulip-desktop": "desktop", "zulip-mobile": "mobile", @@ -13,26 +32,36 @@ const repo_name_to_tab_name = { "zulip-android": "", }; +export type Contributor = { + avatar: string; + email?: string; + github_username?: string; + name: string; +} & { + [K in RepositoryName]?: number; +}; + // Remember the loaded repositories so that HTML is not redundantly edited // if a user leaves and then revisits the same tab. -const loaded_repos = []; +const loaded_repos: string[] = []; -function calculate_total_commits(contributor) { +function calculate_total_commits(contributor: Contributor): number { let commits = 0; - for (const repo_name of Object.keys(repo_name_to_tab_name)) { + for (const repo_name of all_repository_names) { commits += contributor[repo_name] || 0; } return commits; } -function get_profile_url(contributor, tab_name) { - const commit_email_linked_to_github = "github_username" in contributor; - - if (commit_email_linked_to_github) { - return "https://github.com/" + contributor.github_username; +function get_profile_url(contributor: Contributor, tab_name?: string): string | undefined { + if (contributor.github_username) { + return `https://github.com/${contributor.github_username}`; } const email = contributor.email; + if (!email) { + return undefined; + } if (tab_name) { return `https://github.com/zulip/${tab_name}/commits?author=${email}`; @@ -47,21 +76,21 @@ function get_profile_url(contributor, tab_name) { return undefined; } -function get_display_name(contributor) { +function get_display_name(contributor: Contributor): string { if (contributor.github_username) { return "@" + contributor.github_username; } return contributor.name; } -function exclude_bot_contributors(contributor) { +function exclude_bot_contributors(contributor: Contributor): boolean { return contributor.github_username !== "dependabot[bot]"; } // TODO (for v2 of /team/ contributors): // - Make tab header responsive. // - Display full name instead of GitHub username. -export default function render_tabs(contributors) { +export default function render_tabs(contributors: Contributor[]): void { const template = _.template($("#contributors-template").html()); const count_template = _.template($("#count-template").html()); const total_count_template = _.template($("#total-count-template").html()); @@ -93,7 +122,7 @@ export default function render_tabs(contributors) { }), ); - for (const repo_name of Object.keys(repo_name_to_tab_name)) { + for (const repo_name of all_repository_names) { const tab_name = repo_name_to_tab_name[repo_name]; if (!tab_name) { continue; @@ -103,11 +132,13 @@ export default function render_tabs(contributors) { $(`#${CSS.escape(tab_name)}`).on("click", () => { if (!loaded_repos.includes(repo_name)) { - const filtered_by_repo = contributors_list.filter((c) => c[repo_name]); + const filtered_by_repo = contributors_list.filter((c) => c[repo_name] || 0); const html = filtered_by_repo - .sort((a, b) => - a[repo_name] < b[repo_name] ? 1 : a[repo_name] > b[repo_name] ? -1 : 0, - ) + .sort((a, b) => { + const a_commits = a[repo_name] || 0; + const b_commits = b[repo_name] || 0; + return a_commits < b_commits ? 1 : a_commits > b_commits ? -1 : 0; + }) .map((c) => template({ name: get_display_name(c), @@ -121,9 +152,10 @@ export default function render_tabs(contributors) { $(`#tab-${CSS.escape(tab_name)} .contributors-grid`).html(html); const contributor_count = filtered_by_repo.length; - const hundred_plus_contributor_count = filtered_by_repo.filter( - (c) => c[repo_name] >= 100, - ).length; + const hundred_plus_contributor_count = filtered_by_repo.filter((c) => { + const commits = c[repo_name] || 0; + return commits >= 100; + }).length; const repo_url = `https://github.com/zulip/${repo_name}`; $(`#tab-${CSS.escape(tab_name)}`).prepend( count_template({