6
6
//! to the types in `gh` and so forth but because they represent
7
7
//! a versioned API, we copy them over here to insulate them from incidental changes.
8
8
9
- use std:: path:: PathBuf ;
9
+ use std:: { path:: PathBuf , str :: FromStr } ;
10
10
11
11
use serde:: Serialize ;
12
12
13
13
use crate :: {
14
14
gh:: {
15
15
issue_id:: Repository ,
16
16
issues:: {
17
- list_issue_titles_in_milestone , ExistingGithubComment , ExistingGithubIssue ,
18
- ExistingIssueState ,
17
+ count_issues_matching_search , list_issue_titles_in_milestone , CountIssues ,
18
+ ExistingGithubComment , ExistingGithubIssue , ExistingIssueState ,
19
19
} ,
20
20
} ,
21
21
re,
@@ -32,13 +32,17 @@ pub(super) fn generate_json(
32
32
issues : issues
33
33
. into_iter ( )
34
34
. map ( |( title, issue) | {
35
- let ( total_checkboxes, checked_checkboxes) = checkboxes ( & issue) ;
35
+ let progress = match checkboxes ( & issue) {
36
+ Ok ( pair) => pair,
37
+ Err ( e) => Progress :: Error {
38
+ message : e. to_string ( ) ,
39
+ } ,
40
+ } ;
36
41
TrackingIssue {
37
42
number : issue. number ,
38
43
title,
39
44
flagship : is_flagship ( & issue) ,
40
- total_checkboxes,
41
- checked_checkboxes,
45
+ progress,
42
46
assignees : issue. assignees . into_iter ( ) . collect ( ) ,
43
47
updates : updates ( issue. comments ) ,
44
48
state : issue. state ,
@@ -80,11 +84,8 @@ struct TrackingIssue {
80
84
/// True if this is a flagship goal
81
85
flagship : bool ,
82
86
83
- /// Total checkboxes appearing in the body (i.e., `* [ ]` or `* [x]`)
84
- total_checkboxes : u32 ,
85
-
86
- /// Checked checkboxes appearing in the body (i.e., `* [x]`)
87
- checked_checkboxes : u32 ,
87
+ /// State of progress
88
+ progress : Progress ,
88
89
89
90
/// Set of assigned people
90
91
assignees : Vec < String > ,
@@ -96,6 +97,23 @@ struct TrackingIssue {
96
97
state : ExistingIssueState ,
97
98
}
98
99
100
+ #[ derive( Serialize ) ]
101
+ enum Progress {
102
+ /// We could not find any checkboxes or other deatils on the tracking issue.
103
+ /// So all we have is "open" or "closed".
104
+ Binary ,
105
+
106
+ /// We found checkboxes or issue listing.
107
+ Tracked {
108
+ completed : u32 ,
109
+ total : u32 ,
110
+ } ,
111
+
112
+ Error {
113
+ message : String ,
114
+ } ,
115
+ }
116
+
99
117
#[ derive( Serialize ) ]
100
118
struct TrackingIssueUpdate {
101
119
pub author : String ,
@@ -105,21 +123,49 @@ struct TrackingIssueUpdate {
105
123
pub url : String ,
106
124
}
107
125
108
- fn checkboxes ( issue : & ExistingGithubIssue ) -> ( u32 , u32 ) {
109
- let mut total = 0 ;
110
- let mut checked = 0 ;
126
+ /// Identify how many sub-items have been completed.
127
+ /// These can be encoded in two different ways:
128
+ ///
129
+ /// * Option A, the most common, is to have checkboxes in the issue. We just count the number that are checked.
130
+ /// * Option B is to include a metadata line called "Tracked issues" that lists a search query. We count the number of open vs closed issues in that query.
131
+ ///
132
+ /// Returns a tuple (completed, total) with the number of completed items and the total number of items.
133
+ fn checkboxes ( issue : & ExistingGithubIssue ) -> anyhow:: Result < Progress > {
134
+ let mut checkboxes = None ;
135
+ let mut issues = None ;
111
136
112
137
for line in issue. body . lines ( ) {
113
- if re:: CHECKBOX . is_match ( line) {
114
- total += 1 ;
138
+ // Does this match TRACKED_ISSUES?
139
+ if let Some ( c) = re:: TRACKED_ISSUES_QUERY . captures ( line) {
140
+ let repo = Repository :: from_str ( & c[ 1 ] ) ?;
141
+ let query = & c[ 2 ] ;
142
+
143
+ if issues. is_some ( ) {
144
+ anyhow:: bail!( "found multiple search queries for Tracked Issues" ) ;
145
+ }
146
+
147
+ let CountIssues { open, closed } = count_issues_matching_search ( & repo, query) ?;
148
+ issues = Some ( ( closed, open + closed) ) ;
115
149
}
116
150
151
+ let ( checked, total) = checkboxes. unwrap_or ( ( 0 , 0 ) ) ;
152
+
117
153
if re:: CHECKED_CHECKBOX . is_match ( line) {
118
- checked += 1 ;
154
+ checkboxes = Some ( ( checked + 1 , total + 1 ) ) ;
155
+ } else if re:: CHECKBOX . is_match ( line) {
156
+ checkboxes = Some ( ( checked, total + 1 ) ) ;
119
157
}
120
158
}
121
159
122
- ( total, checked)
160
+ eprintln ! ( "#{}: {checkboxes:?}, {issues:?}" , issue. number) ;
161
+
162
+ match ( checkboxes, issues) {
163
+ ( Some ( ( completed, total) ) , None ) | ( None , Some ( ( completed, total) ) ) => {
164
+ Ok ( Progress :: Tracked { completed, total } )
165
+ }
166
+ ( None , None ) => Ok ( Progress :: Binary ) ,
167
+ ( Some ( _) , Some ( _) ) => anyhow:: bail!( "found both Tracked Issues and checkboxes" ) ,
168
+ }
123
169
}
124
170
125
171
fn is_flagship ( issue : & ExistingGithubIssue ) -> bool {
0 commit comments