Skip to content

Commit

Permalink
Add support for custom roles in website, beyond "Team leader" (#1154)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
dtolnay authored Jan 8, 2024
1 parent 7e3f7f5 commit a9ba0cb
Show file tree
Hide file tree
Showing 9 changed files with 191 additions and 19 deletions.
42 changes: 31 additions & 11 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ rust_team_data = { path = "rust_team_data", features = ["email-encryption"] }
serde = "1"
serde_derive = "1"
serde_json = "1"
serde-untagged = "0.1"
structopt = "0.3.26"
toml = "0.8"

Expand Down
15 changes: 15 additions & 0 deletions docs/toml-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ members = [
"rfcbot",
"craterbot",
"rust-timer",
# Any subset of members may hold custom roles, beyond "Team leader" which is
# controlled by the `leads` array above. Members with roles are written
# using an inline table as follows. A simple string member like "bors" is
# equivalent to {github = "bors", roles = []}. The strings in `roles` must
# be present as the `id` of some role in [[roles]] section below.
{ github = "Crab01", roles = ["cohost"] },
{ github = "Crab02", roles = ["cohost"] },
]
# Past members of the team. They will not be considered as part of the team,
# but they will be recognized on the website.
Expand Down Expand Up @@ -133,6 +140,14 @@ zulip-stream = "t-lang"
# Default is 0.
weight = -100

# Customized roles held by a subset of the team's members, beyond "Team leader"
# which is rendered automatically for members of the `leads` array.
[[roles]]
# Kebab-case id for the role. This serves as a key for translations.
id = "cohost"
# Text to appear on the website beneath the team member's name and GitHub handle.
description = "Co-host"

# Define the mailing lists used by the team
# It's optional, and there can be more than one
[[lists]]
Expand Down
2 changes: 2 additions & 0 deletions rust_team_data/src/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ pub struct TeamMember {
pub github: String,
pub github_id: usize,
pub is_lead: bool,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub roles: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
Expand Down
10 changes: 9 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ use schema::{Email, Team, TeamKind};

use anyhow::{bail, format_err, Error};
use log::{error, info, warn};
use std::{collections::HashMap, path::PathBuf};
use std::collections::{BTreeMap, HashMap};
use std::path::PathBuf;
use structopt::StructOpt;

#[derive(structopt::StructOpt)]
Expand Down Expand Up @@ -382,6 +383,7 @@ fn run() -> Result<(), Error> {
);
let mut teams: Vec<_> = data.teams().collect();
teams.sort_by_key(|team| team.name());
let mut roles = BTreeMap::new();
for team in teams {
if let Some(website) = team.website_data() {
let name = team.name();
Expand All @@ -392,6 +394,12 @@ fn run() -> Result<(), Error> {
website.description()
);
}
for role in team.roles() {
roles.insert(&role.id, &role.description);
}
}
for (role_id, description) in roles {
println!("governance-role-{role_id} = {description}");
}
}
Cli::DumpPermission { ref name } => {
Expand Down
53 changes: 50 additions & 3 deletions src/schema.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use crate::data::Data;
pub(crate) use crate::permissions::Permissions;
use anyhow::{bail, format_err, Error};
use serde::de::{Deserialize, Deserializer};
use serde_untagged::UntaggedEnumVisitor;
use std::collections::{HashMap, HashSet};

#[derive(serde_derive::Deserialize, Debug)]
Expand Down Expand Up @@ -165,6 +167,8 @@ pub(crate) struct Team {
rfcbot: Option<RfcbotData>,
website: Option<WebsiteData>,
#[serde(default)]
roles: Vec<MemberRole>,
#[serde(default)]
lists: Vec<TeamList>,
#[serde(default)]
zulip_groups: Vec<RawZulipGroup>,
Expand Down Expand Up @@ -226,6 +230,10 @@ impl Team {
self.website.as_ref()
}

pub(crate) fn roles(&self) -> &[MemberRole] {
&self.roles
}

pub(crate) fn discord_roles(&self) -> Option<&Vec<DiscordRole>> {
self.discord_roles.as_ref()
}
Expand All @@ -236,7 +244,12 @@ impl Team {
}

pub(crate) fn members<'a>(&'a self, data: &'a Data) -> Result<HashSet<&'a str>, Error> {
let mut members: HashSet<_> = self.people.members.iter().map(|s| s.as_str()).collect();
let mut members: HashSet<_> = self
.people
.members
.iter()
.map(|s| s.github.as_str())
.collect();

for team in &self.people.included_teams {
let team = data.team(team).ok_or_else(|| {
Expand Down Expand Up @@ -450,7 +463,7 @@ impl Team {
}

// People explicitly set as members
pub(crate) fn explicit_members(&self) -> &Vec<String> {
pub(crate) fn explicit_members(&self) -> &[TeamMember] {
&self.people.members
}

Expand Down Expand Up @@ -505,7 +518,7 @@ impl std::cmp::Ord for GitHubTeam<'_> {
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub(crate) struct TeamPeople {
pub leads: Vec<String>,
pub members: Vec<String>,
pub members: Vec<TeamMember>,
pub alumni: Option<Vec<String>>,
#[serde(default)]
pub included_teams: Vec<String>,
Expand All @@ -521,6 +534,33 @@ pub(crate) struct TeamPeople {
pub include_all_alumni: bool,
}

#[derive(serde::Deserialize, Clone, Debug)]
#[serde(remote = "Self", deny_unknown_fields)]
pub(crate) struct TeamMember {
pub github: String,
pub roles: Vec<String>,
}

impl<'de> Deserialize<'de> for TeamMember {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
UntaggedEnumVisitor::new()
.string(|github| {
Ok(TeamMember {
github: github.to_owned(),
roles: Vec::new(),
})
})
.map(|map| {
let deserializer = serde::de::value::MapAccessDeserializer::new(map);
TeamMember::deserialize(deserializer)
})
.deserialize(deserializer)
}
}

#[derive(serde::Deserialize, Debug)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
struct GitHubData {
Expand Down Expand Up @@ -601,6 +641,13 @@ impl WebsiteData {
}
}

#[derive(serde_derive::Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub(crate) struct MemberRole {
pub id: String,
pub description: String,
}

#[derive(serde_derive::Deserialize, Debug)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub(crate) struct TeamList {
Expand Down
8 changes: 8 additions & 0 deletions src/static_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use anyhow::Error;
use indexmap::IndexMap;
use log::info;
use rust_team_data::v1;
use std::collections::HashMap;
use std::path::Path;

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

for team in self.data.teams() {
let mut website_roles = HashMap::new();
for member in team.explicit_members().iter().cloned() {
website_roles.insert(member.github, member.roles);
}

let leads = team.leads();
let mut members = Vec::new();
for github_name in &team.members(self.data)? {
Expand All @@ -122,6 +128,7 @@ impl<'a> Generator<'a> {
github: (*github_name).into(),
github_id: person.github_id(),
is_lead: leads.contains(github_name),
roles: website_roles.get(*github_name).cloned().unwrap_or_default(),
});
}
}
Expand All @@ -136,6 +143,7 @@ impl<'a> Generator<'a> {
github: github_name.to_string(),
github_id: person.github_id(),
is_lead: false,
roles: Vec::new(),
});
}
}
Expand Down
Loading

0 comments on commit a9ba0cb

Please sign in to comment.