Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Zulip stream representation #1244

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/toml-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,20 @@ excluded-people = [
"rylev",
]

# Define Zulip streams owned
# It's optional, and there can be more than one
[[zulip-streams]]
# The name of the Zulip stream (required)
name = "t-overlords"
# Zulip groups that will have access to the stream if it is private (optional)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what we want the behavior of this to be when the stream isn't private. Also, given that this stream is defined on the team itself, would any of the zulip-groups automatically be included here?

groups = ["T-overlords"]
# Visibility of the stream (optional, default = "public")
# Possible values:
# "public": a web-public stream readable by anyone even without Zulip login
# "private-shared": a private stream with a shared history (joining the stream reveals you the whole history)
# "private-protected": a private stream with a protected history (joining the stream doesn't reveal its history)
visibility = "public"

# Roles to define in Discord.
[[discord-roles]]
# The name of the role.
Expand Down
20 changes: 20 additions & 0 deletions rust_team_data/src/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,26 @@ pub struct ZulipGroups {
pub groups: IndexMap<String, ZulipGroup>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ZulipStream {
pub name: String,
pub groups: Vec<String>,
pub visibility: ZulipStreamVisibility,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub enum ZulipStreamVisibility {
WebPublic,
PrivateSharedHistory,
PrivateProtectedHistory,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ZulipStreams {
pub streams: IndexMap<String, ZulipStream>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Permission {
pub github_users: Vec<String>,
Expand Down
11 changes: 11 additions & 0 deletions src/data.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::schema::{Config, List, Person, Repo, Team, ZulipGroup};
use anyhow::{bail, Context as _, Error};
use rust_team_data::v1::ZulipStream;
use serde::de::DeserializeOwned;
use std::collections::{HashMap, HashSet};
use std::ffi::OsStr;
Expand Down Expand Up @@ -112,6 +113,16 @@ impl Data {
Ok(groups)
}

pub(crate) fn zulip_streams(&self) -> Result<HashMap<String, ZulipStream>, Error> {
let mut streams = HashMap::new();
for team in self.teams() {
for stream in team.zulip_streams()? {
streams.insert(stream.name.clone(), stream);
}
}
Ok(streams)
}

pub(crate) fn team(&self, name: &str) -> Option<&Team> {
self.teams.get(name)
}
Expand Down
46 changes: 46 additions & 0 deletions src/schema.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::data::Data;
pub(crate) use crate::permissions::Permissions;
use anyhow::{bail, format_err, Error};
use rust_team_data::v1::{ZulipStream, ZulipStreamVisibility};
use serde::de::{Deserialize, Deserializer};
use serde_untagged::UntaggedEnumVisitor;
use std::collections::{HashMap, HashSet};
Expand Down Expand Up @@ -172,6 +173,8 @@ pub(crate) struct Team {
lists: Vec<TeamList>,
#[serde(default)]
zulip_groups: Vec<RawZulipGroup>,
#[serde(default)]
zulip_streams: Vec<RawZulipStream>,
discord_roles: Option<Vec<DiscordRole>>,
}

Expand Down Expand Up @@ -410,6 +413,27 @@ impl Team {
Ok(groups)
}

pub(crate) fn zulip_streams(&self) -> Result<Vec<ZulipStream>, Error> {
let streams = self
.zulip_streams
.iter()
.map(|stream| ZulipStream {
name: stream.name.clone(),
groups: stream.groups.clone(),
visibility: match stream.visibility {
RawZulipVisibility::Public => ZulipStreamVisibility::WebPublic,
RawZulipVisibility::PrivateShared => {
ZulipStreamVisibility::PrivateSharedHistory
}
RawZulipVisibility::PrivateProtected => {
ZulipStreamVisibility::PrivateProtectedHistory
}
},
})
.collect();
Ok(streams)
}

pub(crate) fn permissions(&self) -> &Permissions {
&self.permissions
}
Expand Down Expand Up @@ -680,6 +704,28 @@ pub(crate) struct RawZulipGroup {
pub(crate) excluded_people: Vec<String>,
}

#[derive(serde_derive::Deserialize, Debug)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub(crate) struct RawZulipStream {
pub(crate) name: String,
#[serde(default)]
pub(crate) groups: Vec<String>,
#[serde(default = "default_zulip_stream_visibility")]
pub(crate) visibility: RawZulipVisibility,
}

fn default_zulip_stream_visibility() -> RawZulipVisibility {
RawZulipVisibility::Public
}

#[derive(serde_derive::Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum RawZulipVisibility {
Public,
PrivateShared,
PrivateProtected,
}

#[derive(Debug)]
pub(crate) struct List {
address: String,
Expand Down
29 changes: 29 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::{ensure, Context as _, Error};
use indexmap::IndexMap;
use log::info;
use rust_team_data::v1;
use rust_team_data::v1::ZulipStream;
use std::collections::HashMap;
use std::path::Path;

Expand All @@ -27,6 +28,7 @@ impl<'a> Generator<'a> {
self.generate_repos()?;
self.generate_lists()?;
self.generate_zulip_groups()?;
self.generate_zulip_streams()?;
self.generate_permissions()?;
self.generate_rfcbot()?;
self.generate_zulip_map()?;
Expand Down Expand Up @@ -272,6 +274,33 @@ impl<'a> Generator<'a> {
Ok(())
}

fn generate_zulip_streams(&self) -> Result<(), Error> {
let mut streams = IndexMap::new();

for stream in self.data.zulip_streams()?.values() {
let ZulipStream {
name,
groups,
visibility,
} = stream;

let mut groups = groups.to_vec();
groups.sort();
streams.insert(
stream.name.clone(),
v1::ZulipStream {
name: name.clone(),
groups,
visibility: visibility.clone(),
},
);
}

streams.sort_keys();
self.add("v1/zulip-streams.json", &v1::ZulipStreams { streams })?;
Ok(())
}

fn generate_permissions(&self) -> Result<(), Error> {
for perm in &Permissions::available(self.data.config()) {
let allowed = crate::permissions::allowed_people(self.data, perm)?;
Expand Down
34 changes: 34 additions & 0 deletions src/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ static CHECKS: &[Check<fn(&Data, &mut Vec<String>)>] = checks![
validate_discord_team_members_have_discord_ids,
validate_zulip_group_ids,
validate_zulip_group_extra_people,
validate_zulip_streams,
validate_repos,
validate_branch_protections,
validate_member_roles,
Expand Down Expand Up @@ -738,6 +739,39 @@ fn validate_zulip_group_extra_people(data: &Data, errors: &mut Vec<String>) {
});
}

/// Ensure that each group with access to a Zulip stream exists and that stream names are unique.
fn validate_zulip_streams(data: &Data, errors: &mut Vec<String>) {
let mut stream_names: HashSet<String> = HashSet::default();

wrapper(data.teams(), errors, |team, errors| {
let groups: HashSet<String> = team
.zulip_groups(data)?
.into_iter()
.map(|g| g.name().to_string())
.collect();
wrapper(team.zulip_streams()?.iter(), errors, |stream, errors| {
// Check that mentioned Zulip groups exist
wrapper(stream.groups.iter(), errors, |group, _| {
if !groups.contains(group) {
bail!(
"Zulip group `{group}` in stream `{}` of team `{}` does not exist",
stream.name,
team.name()
);
}
Ok(())
});

if !stream_names.insert(stream.name.clone()) {
bail!("Zulip stream `{}` is duplicated", stream.name);
}

Ok(())
});
Ok(())
});
}

/// Ensure repos reference valid teams
fn validate_repos(data: &Data, errors: &mut Vec<String>) {
let allowed_orgs = data.config().allowed_github_orgs();
Expand Down
11 changes: 11 additions & 0 deletions tests/static-api/_expected/v1/zulip-streams.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"streams": {
"t-foo": {
"name": "t-foo",
"groups": [
"T-foo"
],
"visibility": "private-shared-history"
}
}
}
5 changes: 5 additions & 0 deletions tests/static-api/teams/foo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,8 @@ extra-teams = ["wg-test"]

[[zulip-groups]]
name = "T-foo"

[[zulip-streams]]
name = "t-foo"
groups = ["T-foo"]
visibility = "private-shared"