Skip to content

Commit 5262b6b

Browse files
Merge pull request #1813 from Kobzol/lookup-zulip-id
Allow automatically fetching Zulip ID for newly added users
2 parents 007fde6 + 0f68ffc commit 5262b6b

File tree

4 files changed

+59
-9
lines changed

4 files changed

+59
-9
lines changed

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,12 @@ cargo run add-person <github-username>
7474

7575
You can also add additional information, such as someone's Discord or Zulip ID by adding additional fields to their `.toml` file.
7676

77-
To determine someone's Zulip ID, find them in the list of people on the
77+
You can use the `--fetch-zulip-id` flag to automatically try to find the Zulip ID for the added user based on their GitHub login. This will only work if the user has linked their Zulip and GitHub accounts. You will need to get a [Zulip API key](https://zulip.com/api/api-keys#get-your-api-key) for this to work, and set the following environment variables:
78+
79+
- `ZULIP_USER`: should be set to the e-mail address of your Zulip account
80+
- `ZULIP_TOKEN`: should be set to your API key
81+
82+
If you need to determine someone's Zulip ID manually, find them in the list of people on the
7883
right-hand side in Zulip, click the "three dots" menu and then "View profile", and copy the 'User ID'
7984
into the toml file:
8085

src/api/zulip.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,14 @@ impl ZulipApi {
4242
}
4343

4444
/// Get all users of the Rust Zulip instance
45-
pub(crate) fn get_users(&self) -> Result<Vec<ZulipUser>, Error> {
45+
pub(crate) fn get_users(&self, include_profile_fields: bool) -> Result<Vec<ZulipUser>, Error> {
46+
let url = if include_profile_fields {
47+
"/users?include_custom_profile_fields=true"
48+
} else {
49+
"/users"
50+
};
4651
let response = self
47-
.req(Method::GET, "/users", None)?
52+
.req(Method::GET, url, None)?
4853
.error_for_status()?
4954
.json::<ZulipUsers>()?
5055
.members;
@@ -97,10 +102,24 @@ struct ZulipOneUser {
97102
user: ZulipUser,
98103
}
99104

105+
#[derive(Clone, Deserialize, Debug, PartialEq, Eq)]
106+
pub(crate) struct ProfileValue {
107+
value: String,
108+
}
109+
100110
/// A single Zulip user
101-
#[derive(Clone, Deserialize, PartialEq, Eq, Hash)]
111+
#[derive(Clone, Deserialize, Debug, PartialEq, Eq)]
102112
pub(crate) struct ZulipUser {
103113
pub(crate) user_id: u64,
104114
#[serde(rename = "full_name")]
105115
pub(crate) name: String,
116+
#[serde(default)]
117+
pub(crate) profile_data: HashMap<String, ProfileValue>,
118+
}
119+
120+
impl ZulipUser {
121+
// The GitHub profile data key is 3873
122+
pub(crate) fn get_github_username(&self) -> Option<&str> {
123+
self.profile_data.get("3873").map(|v| v.value.as_str())
124+
}
106125
}

src/main.rs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use schema::{Email, Team, TeamKind};
1919

2020
use crate::ci::{check_codeowners, generate_codeowners_file};
2121
use crate::schema::RepoPermission;
22-
use anyhow::{bail, format_err, Error};
22+
use anyhow::{bail, format_err, Context, Error};
2323
use api::github;
2424
use clap::Parser;
2525
use log::{error, info, warn};
@@ -48,7 +48,12 @@ enum Cli {
4848
skip: Vec<String>,
4949
},
5050
/// Add a new person from their GitHub profile
51-
AddPerson { github_name: String },
51+
AddPerson {
52+
github_name: String,
53+
/// Try to fetch the Zulip ID of the user, based on their GitHub handle or e-mail.
54+
#[arg(long)]
55+
fetch_zulip_id: bool,
56+
},
5257
/// Generate the static API
5358
StaticApi { dest: String },
5459
/// Print information about a person
@@ -212,22 +217,42 @@ fn run() -> Result<(), Error> {
212217
&skip.iter().map(|s| s.as_ref()).collect::<Vec<_>>(),
213218
)?;
214219
}
215-
Cli::AddPerson { ref github_name } => {
216-
#[derive(serde::Serialize)]
220+
Cli::AddPerson {
221+
ref github_name,
222+
fetch_zulip_id,
223+
} => {
224+
#[derive(serde::Serialize, Debug)]
217225
#[serde(rename_all = "kebab-case")]
218226
struct PersonToAdd<'a> {
219227
name: &'a str,
220228
github: &'a str,
221229
github_id: u64,
222230
#[serde(skip_serializing_if = "Option::is_none")]
223231
email: Option<&'a str>,
232+
#[serde(skip_serializing_if = "Option::is_none")]
233+
zulip_id: Option<u64>,
224234
}
225235

226236
let github = github::GitHubApi::new();
227237
let user = github.user(github_name)?;
228238
let github_name = user.login;
229239
let github_id = user.id;
230240

241+
let mut zulip_id: Option<u64> = None;
242+
if fetch_zulip_id {
243+
let zulip = ZulipApi::new();
244+
let users = zulip.get_users(true).context("Cannot get user data from Zulip. Configure ZULIP_USER and ZULIP_TOKEN environment variables")?;
245+
246+
// Try to find user by GitHub handle
247+
if let Some(zulip_user) = users.iter().find(|u| {
248+
u.get_github_username().map(|login| login.to_lowercase())
249+
== Some(github_name.to_lowercase())
250+
}) {
251+
info!("Found Zulip ID {}", zulip_user.user_id);
252+
zulip_id = Some(zulip_user.user_id);
253+
}
254+
}
255+
231256
if data.person(&github_name).is_some() {
232257
bail!("person already in the repo: {}", github_name);
233258
}
@@ -248,6 +273,7 @@ fn run() -> Result<(), Error> {
248273
warn!("the person is missing the email on GitHub, leaving the field empty");
249274
None
250275
}),
276+
zulip_id,
251277
})?
252278
.as_bytes(),
253279
)?;

src/validate.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,7 @@ fn validate_discord_team_members_have_discord_ids(data: &Data, errors: &mut Vec<
676676

677677
/// Ensure every member of a team that has a Zulip group has a Zulip id
678678
fn validate_zulip_users(data: &Data, zulip: &ZulipApi, errors: &mut Vec<String>) {
679-
let by_id = match zulip.get_users() {
679+
let by_id = match zulip.get_users(false) {
680680
Ok(u) => u.iter().map(|u| u.user_id).collect::<HashSet<_>>(),
681681
Err(err) => {
682682
errors.push(format!("couldn't verify Zulip users: {}", err));

0 commit comments

Comments
 (0)