Skip to content

Commit f55110e

Browse files
authored
feat: issues analysis (#15)
* feat: add graphql * feat: issues analysis * test: issues analysis * fix lint * docs: update * chore: update version
1 parent 0f34354 commit f55110e

15 files changed

+1722
-9
lines changed

Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "gh-lens"
33
authors = ["hirokisan <[email protected]>"]
44
description = "CLI to analyze your activity on GitHub"
5-
version = "0.0.11"
5+
version = "0.0.12"
66
edition = "2021"
77
license = "MIT"
88
keywords = ["github", "cli", "analysis", "gh-lens"]

README.md

+81
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,84 @@ $ gh-lens prs --repo hirokisan/gh-lens --start-date 2024-11-12 --end-date 2024-1
6060
]
6161
}
6262
```
63+
64+
```console
65+
gh-lens issues --repo hirokisan/bybit --start-date 2024-01-01 --end-date 2024-10-31 | jq .
66+
{
67+
"start_date": "2024-01-01",
68+
"end_date": "2024-10-31",
69+
"issues_count": 5,
70+
"assigns_count": 1,
71+
"comments_count": {
72+
"sum": 12,
73+
"average": 2.4
74+
},
75+
"time_to_closed": {
76+
"average": 339269.2
77+
},
78+
"issues_summaries": [
79+
{
80+
"url": "https://github.com/hirokisan/bybit/issues/190",
81+
"author": "id-petrov",
82+
"assignees": null,
83+
"participants": [
84+
"id-petrov",
85+
"hirokisan"
86+
],
87+
"comments_count": 1,
88+
"created_at": "2024-10-28T20:10:43Z",
89+
"closed_at": "2024-10-29T05:39:09Z"
90+
},
91+
{
92+
"url": "https://github.com/hirokisan/bybit/issues/181",
93+
"author": "austymenko",
94+
"assignees": null,
95+
"participants": [
96+
"austymenko",
97+
"hirokisan"
98+
],
99+
"comments_count": 5,
100+
"created_at": "2024-07-06T03:31:11Z",
101+
"closed_at": "2024-07-13T13:29:15Z"
102+
},
103+
{
104+
"url": "https://github.com/hirokisan/bybit/issues/175",
105+
"author": "apeman76",
106+
"assignees": null,
107+
"participants": [
108+
"apeman76",
109+
"hirokisan"
110+
],
111+
"comments_count": 2,
112+
"created_at": "2024-06-12T14:13:08Z",
113+
"closed_at": "2024-06-23T07:57:42Z"
114+
},
115+
{
116+
"url": "https://github.com/hirokisan/bybit/issues/171",
117+
"author": "s-prosvirnin",
118+
"assignees": [
119+
"hirokisan"
120+
],
121+
"participants": [
122+
"s-prosvirnin",
123+
"hirokisan"
124+
],
125+
"comments_count": 2,
126+
"created_at": "2024-04-21T10:35:14Z",
127+
"closed_at": "2024-04-22T11:54:24Z"
128+
},
129+
{
130+
"url": "https://github.com/hirokisan/bybit/issues/160",
131+
"author": "biancheng347",
132+
"assignees": null,
133+
"participants": [
134+
"hirokisan",
135+
"biancheng347"
136+
],
137+
"comments_count": 2,
138+
"created_at": "2024-01-25T09:18:51Z",
139+
"closed_at": "2024-01-25T10:01:03Z"
140+
}
141+
]
142+
}
143+
```

src/github.rs

+5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
mod client;
22
mod gql;
3+
mod issue;
4+
mod issues;
5+
mod issues_summary;
36
mod pull_request;
47
mod pull_requests;
58
mod pull_requests_summary;
69

710
pub(crate) use client::*;
11+
pub(crate) use issues::*;
12+
pub(crate) use issues_summary::*;
813
pub(crate) use pull_requests::*;
914
pub(crate) use pull_requests_summary::*;

src/github/client.rs

+95-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
use graphql_client::GraphQLQuery;
22

3-
use super::gql::query::{pull_requests_query, PullRequestsQuery};
3+
use super::gql::issues_query::{issues_query, IssuesQuery};
4+
use super::gql::pull_requests_query::{pull_requests_query, PullRequestsQuery};
5+
use super::issue::Issue;
6+
use super::issues::Issues;
7+
use super::issues_summary::IssuesSummary;
48
use super::pull_request::PullRequest;
59
use super::pull_requests::PullRequests;
610
use super::pull_requests_summary::PullRequestsSummary;
@@ -72,6 +76,56 @@ impl Client {
7276
Ok(result)
7377
}
7478

79+
async fn get_issues(
80+
&self,
81+
repo: &str,
82+
start_date: &str,
83+
end_date: &str,
84+
) -> Result<Issues, anyhow::Error> {
85+
let mut result = Issues::new();
86+
87+
let offset = 10;
88+
let query = format!("repo:{repo} is:issue created:{start_date}..{end_date}");
89+
let mut variables = issues_query::Variables {
90+
first: offset,
91+
query: query.to_string(),
92+
threshold: 50,
93+
after: None,
94+
};
95+
96+
loop {
97+
let response: octocrab::Result<graphql_client::Response<issues_query::ResponseData>> =
98+
self.inner
99+
.graphql(&IssuesQuery::build_query(variables.clone()))
100+
.await;
101+
102+
match response {
103+
Ok(res) => {
104+
let issues = &res.data.as_ref().unwrap().search;
105+
let has_next_page = issues.page_info.has_next_page;
106+
let end_cursor = issues.page_info.end_cursor.clone();
107+
108+
for item in issues.nodes.as_ref().unwrap().iter().flatten() {
109+
match item {
110+
issues_query::IssuesQuerySearchNodes::Issue(issue) => {
111+
result.add(Issue::new(issue.clone()))
112+
}
113+
_ => continue,
114+
};
115+
}
116+
117+
if !has_next_page {
118+
break;
119+
}
120+
variables.after.clone_from(&end_cursor);
121+
}
122+
Err(err) => return Err(anyhow::anyhow!(err)),
123+
}
124+
}
125+
126+
Ok(result)
127+
}
128+
75129
pub async fn get_pull_requests_summary(
76130
&self,
77131
repo: String,
@@ -115,4 +169,44 @@ impl Client {
115169

116170
Ok(summaries)
117171
}
172+
173+
pub async fn get_issues_summary(
174+
&self,
175+
repo: String,
176+
start_date: String,
177+
end_date: String,
178+
) -> Result<IssuesSummary, anyhow::Error> {
179+
let issues = self.get_issues(&repo, &start_date, &end_date).await?;
180+
181+
Ok(IssuesSummary::new(
182+
start_date.clone(),
183+
end_date.clone(),
184+
&issues,
185+
))
186+
}
187+
188+
pub async fn get_issues_summary_on_individuals(
189+
&self,
190+
repo: String,
191+
start_date: String,
192+
end_date: String,
193+
individuals: Vec<String>,
194+
) -> Result<HashMap<String, IssuesSummary>, anyhow::Error> {
195+
let issues = self.get_issues(&repo, &start_date, &end_date).await?;
196+
197+
let mut summaries: HashMap<String, IssuesSummary> = HashMap::new();
198+
199+
for individual in individuals.iter() {
200+
summaries
201+
.entry(individual.clone())
202+
.or_insert(IssuesSummary::new_with_by(
203+
start_date.clone(),
204+
end_date.clone(),
205+
&issues,
206+
individual,
207+
));
208+
}
209+
210+
Ok(summaries)
211+
}
118212
}

src/github/gql.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
pub(super) mod query;
1+
pub(super) mod issues_query;
2+
pub(super) mod pull_requests_query;
23
pub(super) mod scaler;

src/github/gql/issues_query.graphql

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
query IssuesQuery($first: Int!, $after: String, $query: String!, $threshold: Int!) {
2+
search( type: ISSUE first: $first after: $after query: $query) {
3+
issueCount
4+
pageInfo {
5+
hasNextPage
6+
endCursor
7+
}
8+
nodes {
9+
__typename
10+
... on Issue {
11+
url
12+
createdAt
13+
closedAt
14+
author {
15+
__typename
16+
login
17+
}
18+
comments(first: $threshold) {
19+
nodes {
20+
author {
21+
__typename
22+
login
23+
}
24+
}
25+
}
26+
timelineItems(first: 1, itemTypes: CLOSED_EVENT) {
27+
nodes {
28+
__typename
29+
... on ClosedEvent {
30+
actor {
31+
__typename
32+
login
33+
}
34+
createdAt
35+
}
36+
}
37+
}
38+
assignees(first: $threshold) {
39+
nodes {
40+
login
41+
}
42+
}
43+
participants(first: $threshold) {
44+
nodes {
45+
login
46+
}
47+
}
48+
}
49+
}
50+
}
51+
}

0 commit comments

Comments
 (0)