Skip to content

Commit a9ba0cb

Browse files
authored
Add support for custom roles in website, beyond "Team leader" (#1154)
* Add support for custom roles in website, beyond "Team leader" * Convert elements of members array from String to struct In preparation for adding optional `roles` for members. * Specify roles inline in the members declaration * Address nits from PR 1154 review * Update toml schema for website roles * Improve TOML schema of custom roles
1 parent 7e3f7f5 commit a9ba0cb

File tree

9 files changed

+191
-19
lines changed

9 files changed

+191
-19
lines changed

Cargo.lock

Lines changed: 31 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ rust_team_data = { path = "rust_team_data", features = ["email-encryption"] }
1818
serde = "1"
1919
serde_derive = "1"
2020
serde_json = "1"
21+
serde-untagged = "0.1"
2122
structopt = "0.3.26"
2223
toml = "0.8"
2324

docs/toml-schema.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ members = [
4949
"rfcbot",
5050
"craterbot",
5151
"rust-timer",
52+
# Any subset of members may hold custom roles, beyond "Team leader" which is
53+
# controlled by the `leads` array above. Members with roles are written
54+
# using an inline table as follows. A simple string member like "bors" is
55+
# equivalent to {github = "bors", roles = []}. The strings in `roles` must
56+
# be present as the `id` of some role in [[roles]] section below.
57+
{ github = "Crab01", roles = ["cohost"] },
58+
{ github = "Crab02", roles = ["cohost"] },
5259
]
5360
# Past members of the team. They will not be considered as part of the team,
5461
# but they will be recognized on the website.
@@ -133,6 +140,14 @@ zulip-stream = "t-lang"
133140
# Default is 0.
134141
weight = -100
135142

143+
# Customized roles held by a subset of the team's members, beyond "Team leader"
144+
# which is rendered automatically for members of the `leads` array.
145+
[[roles]]
146+
# Kebab-case id for the role. This serves as a key for translations.
147+
id = "cohost"
148+
# Text to appear on the website beneath the team member's name and GitHub handle.
149+
description = "Co-host"
150+
136151
# Define the mailing lists used by the team
137152
# It's optional, and there can be more than one
138153
[[lists]]

rust_team_data/src/v1.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ pub struct TeamMember {
3232
pub github: String,
3333
pub github_id: usize,
3434
pub is_lead: bool,
35+
#[serde(skip_serializing_if = "Vec::is_empty")]
36+
pub roles: Vec<String>,
3537
}
3638

3739
#[derive(Debug, Clone, Serialize, Deserialize)]

src/main.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ use schema::{Email, Team, TeamKind};
1717

1818
use anyhow::{bail, format_err, Error};
1919
use log::{error, info, warn};
20-
use std::{collections::HashMap, path::PathBuf};
20+
use std::collections::{BTreeMap, HashMap};
21+
use std::path::PathBuf;
2122
use structopt::StructOpt;
2223

2324
#[derive(structopt::StructOpt)]
@@ -382,6 +383,7 @@ fn run() -> Result<(), Error> {
382383
);
383384
let mut teams: Vec<_> = data.teams().collect();
384385
teams.sort_by_key(|team| team.name());
386+
let mut roles = BTreeMap::new();
385387
for team in teams {
386388
if let Some(website) = team.website_data() {
387389
let name = team.name();
@@ -392,6 +394,12 @@ fn run() -> Result<(), Error> {
392394
website.description()
393395
);
394396
}
397+
for role in team.roles() {
398+
roles.insert(&role.id, &role.description);
399+
}
400+
}
401+
for (role_id, description) in roles {
402+
println!("governance-role-{role_id} = {description}");
395403
}
396404
}
397405
Cli::DumpPermission { ref name } => {

src/schema.rs

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use crate::data::Data;
22
pub(crate) use crate::permissions::Permissions;
33
use anyhow::{bail, format_err, Error};
4+
use serde::de::{Deserialize, Deserializer};
5+
use serde_untagged::UntaggedEnumVisitor;
46
use std::collections::{HashMap, HashSet};
57

68
#[derive(serde_derive::Deserialize, Debug)]
@@ -165,6 +167,8 @@ pub(crate) struct Team {
165167
rfcbot: Option<RfcbotData>,
166168
website: Option<WebsiteData>,
167169
#[serde(default)]
170+
roles: Vec<MemberRole>,
171+
#[serde(default)]
168172
lists: Vec<TeamList>,
169173
#[serde(default)]
170174
zulip_groups: Vec<RawZulipGroup>,
@@ -226,6 +230,10 @@ impl Team {
226230
self.website.as_ref()
227231
}
228232

233+
pub(crate) fn roles(&self) -> &[MemberRole] {
234+
&self.roles
235+
}
236+
229237
pub(crate) fn discord_roles(&self) -> Option<&Vec<DiscordRole>> {
230238
self.discord_roles.as_ref()
231239
}
@@ -236,7 +244,12 @@ impl Team {
236244
}
237245

238246
pub(crate) fn members<'a>(&'a self, data: &'a Data) -> Result<HashSet<&'a str>, Error> {
239-
let mut members: HashSet<_> = self.people.members.iter().map(|s| s.as_str()).collect();
247+
let mut members: HashSet<_> = self
248+
.people
249+
.members
250+
.iter()
251+
.map(|s| s.github.as_str())
252+
.collect();
240253

241254
for team in &self.people.included_teams {
242255
let team = data.team(team).ok_or_else(|| {
@@ -450,7 +463,7 @@ impl Team {
450463
}
451464

452465
// People explicitly set as members
453-
pub(crate) fn explicit_members(&self) -> &Vec<String> {
466+
pub(crate) fn explicit_members(&self) -> &[TeamMember] {
454467
&self.people.members
455468
}
456469

@@ -505,7 +518,7 @@ impl std::cmp::Ord for GitHubTeam<'_> {
505518
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
506519
pub(crate) struct TeamPeople {
507520
pub leads: Vec<String>,
508-
pub members: Vec<String>,
521+
pub members: Vec<TeamMember>,
509522
pub alumni: Option<Vec<String>>,
510523
#[serde(default)]
511524
pub included_teams: Vec<String>,
@@ -521,6 +534,33 @@ pub(crate) struct TeamPeople {
521534
pub include_all_alumni: bool,
522535
}
523536

537+
#[derive(serde::Deserialize, Clone, Debug)]
538+
#[serde(remote = "Self", deny_unknown_fields)]
539+
pub(crate) struct TeamMember {
540+
pub github: String,
541+
pub roles: Vec<String>,
542+
}
543+
544+
impl<'de> Deserialize<'de> for TeamMember {
545+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
546+
where
547+
D: Deserializer<'de>,
548+
{
549+
UntaggedEnumVisitor::new()
550+
.string(|github| {
551+
Ok(TeamMember {
552+
github: github.to_owned(),
553+
roles: Vec::new(),
554+
})
555+
})
556+
.map(|map| {
557+
let deserializer = serde::de::value::MapAccessDeserializer::new(map);
558+
TeamMember::deserialize(deserializer)
559+
})
560+
.deserialize(deserializer)
561+
}
562+
}
563+
524564
#[derive(serde::Deserialize, Debug)]
525565
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
526566
struct GitHubData {
@@ -601,6 +641,13 @@ impl WebsiteData {
601641
}
602642
}
603643

644+
#[derive(serde_derive::Deserialize, Debug)]
645+
#[serde(deny_unknown_fields)]
646+
pub(crate) struct MemberRole {
647+
pub id: String,
648+
pub description: String,
649+
}
650+
604651
#[derive(serde_derive::Deserialize, Debug)]
605652
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
606653
pub(crate) struct TeamList {

src/static_api.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use anyhow::Error;
44
use indexmap::IndexMap;
55
use log::info;
66
use rust_team_data::v1;
7+
use std::collections::HashMap;
78
use std::path::Path;
89

910
pub(crate) struct Generator<'a> {
@@ -113,6 +114,11 @@ impl<'a> Generator<'a> {
113114
let mut teams = IndexMap::new();
114115

115116
for team in self.data.teams() {
117+
let mut website_roles = HashMap::new();
118+
for member in team.explicit_members().iter().cloned() {
119+
website_roles.insert(member.github, member.roles);
120+
}
121+
116122
let leads = team.leads();
117123
let mut members = Vec::new();
118124
for github_name in &team.members(self.data)? {
@@ -122,6 +128,7 @@ impl<'a> Generator<'a> {
122128
github: (*github_name).into(),
123129
github_id: person.github_id(),
124130
is_lead: leads.contains(github_name),
131+
roles: website_roles.get(*github_name).cloned().unwrap_or_default(),
125132
});
126133
}
127134
}
@@ -136,6 +143,7 @@ impl<'a> Generator<'a> {
136143
github: github_name.to_string(),
137144
github_id: person.github_id(),
138145
is_lead: false,
146+
roles: Vec::new(),
139147
});
140148
}
141149
}

0 commit comments

Comments
 (0)