Skip to content

Commit f33ff92

Browse files
committed
Add alias for compound labels
Configure relabel command aliases from the `triagebot.toml`. Does not handle the case of multiple labels and command aliases intermixed, example: @rustbot label cmd-alias label1 cmd-label1 In case a valid alias is parsed, the rest will be discarded, example: @rustbot cmd-alias label1 is the same as: @rustbot cmd-alias skip-checks: true
1 parent 7ab36ca commit f33ff92

File tree

3 files changed

+135
-10
lines changed

3 files changed

+135
-10
lines changed

parser/src/command/relabel.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ pub enum LabelDelta {
1919
}
2020

2121
#[derive(Debug, PartialEq, Eq, Clone)]
22-
pub struct Label(String);
22+
pub struct Label(pub String);
2323

2424
#[derive(PartialEq, Eq, Debug)]
2525
pub enum ParseError {
@@ -103,6 +103,7 @@ fn delta_empty() {
103103
}
104104

105105
impl RelabelCommand {
106+
/// Parse and validate command tokens
106107
pub fn parse<'a>(input: &mut Tokenizer<'a>) -> Result<Option<Self>, Error<'a>> {
107108
let mut toks = input.clone();
108109

@@ -261,3 +262,39 @@ fn parse_quote() {
261262
]))
262263
);
263264
}
265+
266+
#[test]
267+
fn parse_command_1() {
268+
assert_eq!(
269+
parse("labels to-stable"),
270+
Ok(Some(vec![
271+
LabelDelta::Add(Label("regression-from-stable-to-stable".into())),
272+
LabelDelta::Remove(Label("regression-from-stable-to-beta".into())),
273+
LabelDelta::Remove(Label("regression-from-stable-to-nightly".into()))
274+
]))
275+
);
276+
}
277+
278+
#[test]
279+
fn parse_command_2() {
280+
assert_eq!(
281+
parse("labels to-beta"),
282+
Ok(Some(vec![
283+
LabelDelta::Add(Label("regression-from-stable-to-beta".into())),
284+
LabelDelta::Remove(Label("regression-from-stable-to-stable".into())),
285+
LabelDelta::Remove(Label("regression-from-stable-to-nightly".into()))
286+
]))
287+
);
288+
}
289+
290+
#[test]
291+
fn parse_command_variants() {
292+
assert_eq!(
293+
parse("labels: to-beta"),
294+
Ok(Some(vec![
295+
LabelDelta::Add(Label("regression-from-stable-to-beta".into())),
296+
LabelDelta::Remove(Label("regression-from-stable-to-stable".into())),
297+
LabelDelta::Remove(Label("regression-from-stable-to-nightly".into()))
298+
]))
299+
);
300+
}

src/config.rs

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::changelogs::ChangelogFormat;
22
use crate::github::{GithubClient, Repository};
3+
use parser::command::relabel::{Label, LabelDelta, RelabelCommand};
34
use std::collections::{HashMap, HashSet};
45
use std::fmt;
56
use std::sync::{Arc, LazyLock, RwLock};
@@ -238,10 +239,36 @@ pub(crate) struct MentionsPathConfig {
238239

239240
#[derive(PartialEq, Eq, Debug, serde::Deserialize)]
240241
#[serde(rename_all = "kebab-case")]
241-
#[serde(deny_unknown_fields)]
242242
pub(crate) struct RelabelConfig {
243243
#[serde(default)]
244244
pub(crate) allow_unauthenticated: Vec<String>,
245+
// alias identifier -> labels
246+
#[serde(flatten)]
247+
pub(crate) configs: Option<HashMap<String, RelabelRuleConfig>>,
248+
}
249+
250+
#[derive(Default, PartialEq, Eq, Debug, serde::Deserialize)]
251+
#[serde(rename_all = "kebab-case")]
252+
#[serde(deny_unknown_fields)]
253+
pub(crate) struct RelabelRuleConfig {
254+
/// Labels to be added
255+
pub(crate) add_labels: Vec<String>,
256+
/// Labels to be removed
257+
pub(crate) rem_labels: Vec<String>,
258+
}
259+
260+
impl RelabelRuleConfig {
261+
/// Translate a RelabelRuleConfig into a RelabelCommand for GitHub consumption
262+
pub fn to_command(&self) -> RelabelCommand {
263+
let mut deltas = Vec::new();
264+
for l in self.add_labels.iter() {
265+
deltas.push(LabelDelta::Add(Label(l.into())));
266+
}
267+
for l in self.rem_labels.iter() {
268+
deltas.push(LabelDelta::Remove(Label(l.into())));
269+
}
270+
RelabelCommand(deltas)
271+
}
245272
}
246273

247274
#[derive(PartialEq, Eq, Debug, serde::Deserialize)]
@@ -756,6 +783,7 @@ mod tests {
756783
Config {
757784
relabel: Some(RelabelConfig {
758785
allow_unauthenticated: vec!["C-*".into()],
786+
configs: Some(HashMap::new())
759787
}),
760788
assign: Some(AssignConfig {
761789
warn_non_default_branch: WarnNonDefaultBranchConfig::Simple(false),
@@ -926,4 +954,36 @@ mod tests {
926954
})
927955
);
928956
}
957+
958+
#[test]
959+
fn relabel_new_config() {
960+
let config = r#"
961+
[relabel]
962+
allow-unauthenticated = ["ABCD-*"]
963+
964+
[relabel.to-stable]
965+
add-labels = ["regression-from-stable-to-stable"]
966+
rem-labels = ["regression-from-stable-to-beta", "regression-from-stable-to-nightly"]
967+
"#;
968+
let config = toml::from_str::<Config>(&config).unwrap();
969+
970+
let mut relabel_configs = HashMap::new();
971+
relabel_configs.insert(
972+
"to-stable".into(),
973+
RelabelRuleConfig {
974+
add_labels: vec!["regression-from-stable-to-stable".to_string()],
975+
rem_labels: vec![
976+
"regression-from-stable-to-beta".to_string(),
977+
"regression-from-stable-to-nightly".to_string(),
978+
],
979+
},
980+
);
981+
982+
let expected_cfg = RelabelConfig {
983+
allow_unauthenticated: vec!["ABCD-*".to_string()],
984+
configs: Some(relabel_configs),
985+
};
986+
987+
assert_eq!(config.relabel, Some(expected_cfg));
988+
}
929989
}

src/handlers/relabel.rs

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,30 @@ pub(super) async fn handle_command(
2424
event: &Event,
2525
input: RelabelCommand,
2626
) -> anyhow::Result<()> {
27-
let mut results = vec![];
27+
let mut to_rem = vec![];
2828
let mut to_add = vec![];
29-
for delta in &input.0 {
29+
30+
// Parse alias configurations, look for a match in the input
31+
// If it does, extract the appropriate config (RelabelRuleConfig) and build a new RelabelCommand.
32+
// TODO: handle the case of multiple labels and cmd aliases mixed together
33+
// TODO: (example: "@rustbot label cmd-alias label cmd-label-1 label1")
34+
// TODO: one option is to interpret only the first token and discard the rest
35+
let new_input = match &config.configs {
36+
Some(configs) => {
37+
// extract the "alias" from the RelabelCommand
38+
let maybe_cmd_alias = input.0.get(0).unwrap();
39+
let maybe_cmd_alias = maybe_cmd_alias.label();
40+
41+
// check if this alias matches any RelabelRuleConfig key in our config
42+
// extract the labels and build a new command
43+
let (_alias, cfg) = configs.get_key_value(maybe_cmd_alias.as_str()).unwrap();
44+
cfg.to_command()
45+
}
46+
None => input,
47+
};
48+
49+
// Parse input label command, checks permissions, built GitHub commands
50+
for delta in &new_input.0 {
3051
let name = delta.label().as_str();
3152
let err = match check_filter(name, config, is_member(&event.user(), &ctx.team).await) {
3253
Ok(CheckFilterResult::Allow) => None,
@@ -53,14 +74,12 @@ pub(super) async fn handle_command(
5374
});
5475
}
5576
LabelDelta::Remove(label) => {
56-
results.push((
57-
label,
58-
event.issue().unwrap().remove_label(&ctx.github, &label),
59-
));
77+
to_rem.push(label);
6078
}
6179
}
6280
}
6381

82+
// Add new labels (if needed)
6483
if let Err(e) = event
6584
.issue()
6685
.unwrap()
@@ -84,8 +103,14 @@ pub(super) async fn handle_command(
84103
return Err(e);
85104
}
86105

87-
for (label, res) in results {
88-
if let Err(e) = res.await {
106+
// Remove labels (if needed)
107+
for label in to_rem {
108+
if let Err(e) = event
109+
.issue()
110+
.unwrap()
111+
.remove_label(&ctx.github, &label)
112+
.await
113+
{
89114
tracing::error!(
90115
"failed to remove {:?} from issue {}: {:?}",
91116
label,
@@ -124,6 +149,8 @@ enum CheckFilterResult {
124149
DenyUnknown,
125150
}
126151

152+
/// Check if the team member is allowed to apply labels
153+
/// configured in `allow_unauthenticated`
127154
fn check_filter(
128155
label: &str,
129156
config: &RelabelConfig,
@@ -220,6 +247,7 @@ mod tests {
220247
($($member:ident { $($label:expr => $res:ident,)* })*) => {
221248
let config = RelabelConfig {
222249
allow_unauthenticated: vec!["T-*".into(), "I-*".into(), "!I-*nominated".into()],
250+
configs: None
223251
};
224252
$($(assert_eq!(
225253
check_filter($label, &config, TeamMembership::$member),

0 commit comments

Comments
 (0)