Add paginated panel loading
This commit is contained in:
@@ -10,12 +10,12 @@ use crate::{
|
||||
diff,
|
||||
domain::{
|
||||
HistoryCommit, HomeData, IssueDetails, IssueDraft, IssueEditorData, MilestoneDetails,
|
||||
PullDetails, RepositoryData, Server, api_date,
|
||||
PAGE_SIZE, Page, PullDetails, RepositoryData, Server, api_date,
|
||||
},
|
||||
presentation::compact_date,
|
||||
};
|
||||
|
||||
pub async fn load_repositories(server: &Server) -> Result<Vec<RepositoryData>, String> {
|
||||
pub async fn load_repositories(server: &Server, page: i32) -> Result<Page<RepositoryData>, String> {
|
||||
let client =
|
||||
Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?;
|
||||
let configuration = client.configuration();
|
||||
@@ -24,36 +24,41 @@ pub async fn load_repositories(server: &Server) -> Result<Vec<RepositoryData>, S
|
||||
.await
|
||||
.map_err(|error| error.to_string())?;
|
||||
let login = user.login.unwrap_or_default();
|
||||
let repositories = apis::user_api::user_current_list_repos(&configuration, Some(1), Some(100))
|
||||
.await
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(repositories
|
||||
.into_iter()
|
||||
.filter(|repository| {
|
||||
repository
|
||||
.owner
|
||||
.as_ref()
|
||||
.and_then(|owner| owner.login.as_deref())
|
||||
== Some(login.as_str())
|
||||
})
|
||||
.filter_map(|repository| {
|
||||
Some(RepositoryData {
|
||||
owner: repository.owner?.login?,
|
||||
name: repository.name?,
|
||||
description: repository
|
||||
.description
|
||||
.filter(|text| !text.is_empty())
|
||||
.unwrap_or_else(|| "No description".into()),
|
||||
language: repository
|
||||
.language
|
||||
.filter(|text| !text.is_empty())
|
||||
.unwrap_or_else(|| "Unknown language".into()),
|
||||
open_issues: repository.open_issues_count.unwrap_or_default(),
|
||||
updated: compact_date(repository.updated_at.as_deref()),
|
||||
default_branch: repository.default_branch.unwrap_or_else(|| "main".into()),
|
||||
let repositories =
|
||||
apis::user_api::user_current_list_repos(&configuration, Some(page), Some(PAGE_SIZE))
|
||||
.await
|
||||
.map_err(|error| error.to_string())?;
|
||||
let has_more = repositories.len() == PAGE_SIZE as usize;
|
||||
Ok(Page {
|
||||
items: repositories
|
||||
.into_iter()
|
||||
.filter(|repository| {
|
||||
repository
|
||||
.owner
|
||||
.as_ref()
|
||||
.and_then(|owner| owner.login.as_deref())
|
||||
== Some(login.as_str())
|
||||
})
|
||||
})
|
||||
.collect())
|
||||
.filter_map(|repository| {
|
||||
Some(RepositoryData {
|
||||
owner: repository.owner?.login?,
|
||||
name: repository.name?,
|
||||
description: repository
|
||||
.description
|
||||
.filter(|text| !text.is_empty())
|
||||
.unwrap_or_else(|| "No description".into()),
|
||||
language: repository
|
||||
.language
|
||||
.filter(|text| !text.is_empty())
|
||||
.unwrap_or_else(|| "Unknown language".into()),
|
||||
open_issues: repository.open_issues_count.unwrap_or_default(),
|
||||
updated: compact_date(repository.updated_at.as_deref()),
|
||||
default_branch: repository.default_branch.unwrap_or_else(|| "main".into()),
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
has_more,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn load_issues(
|
||||
@@ -63,7 +68,8 @@ pub async fn load_issues(
|
||||
status: &str,
|
||||
labels: &[String],
|
||||
milestone: &str,
|
||||
) -> Result<Vec<models::Issue>, String> {
|
||||
page: i32,
|
||||
) -> Result<Page<models::Issue>, String> {
|
||||
let client =
|
||||
Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?;
|
||||
let labels = (!labels.is_empty()).then(|| labels.join(","));
|
||||
@@ -81,10 +87,33 @@ pub async fn load_issues(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some(1),
|
||||
Some(100),
|
||||
Some(page),
|
||||
Some(PAGE_SIZE),
|
||||
)
|
||||
.await
|
||||
.map(Page::from_items)
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub async fn load_milestones_page(
|
||||
server: &Server,
|
||||
owner: &str,
|
||||
repository: &str,
|
||||
page: i32,
|
||||
) -> Result<Page<models::Milestone>, String> {
|
||||
let client =
|
||||
Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?;
|
||||
apis::issue_api::issue_get_milestones_list(
|
||||
&client.configuration(),
|
||||
owner,
|
||||
repository,
|
||||
Some("all"),
|
||||
None,
|
||||
Some(page),
|
||||
Some(PAGE_SIZE),
|
||||
)
|
||||
.await
|
||||
.map(Page::from_items)
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
@@ -151,6 +180,7 @@ pub async fn load_milestone(
|
||||
owner: &str,
|
||||
repository: &str,
|
||||
id: i64,
|
||||
page: i32,
|
||||
) -> Result<MilestoneDetails, String> {
|
||||
let client =
|
||||
Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?;
|
||||
@@ -174,12 +204,14 @@ pub async fn load_milestone(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some(1),
|
||||
Some(100),
|
||||
Some(page),
|
||||
Some(PAGE_SIZE),
|
||||
)
|
||||
};
|
||||
let (issues, pulls) = tokio::join!(load("issues"), load("pulls"));
|
||||
let issues = issues.map_err(|error| error.to_string())?;
|
||||
let mut pulls = pulls.map_err(|error| error.to_string())?;
|
||||
let has_more = issues.len() == PAGE_SIZE as usize || pulls.len() == PAGE_SIZE as usize;
|
||||
for pull in &mut pulls {
|
||||
pull.repository.get_or_insert_with(|| {
|
||||
Box::new(models::RepositoryMeta {
|
||||
@@ -191,8 +223,9 @@ pub async fn load_milestone(
|
||||
}
|
||||
Ok(MilestoneDetails {
|
||||
milestone,
|
||||
issues: issues.map_err(|error| error.to_string())?,
|
||||
issues,
|
||||
pulls,
|
||||
has_more,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -200,7 +233,8 @@ pub async fn load_pulls(
|
||||
server: &Server,
|
||||
status: &str,
|
||||
milestone: &str,
|
||||
) -> Result<Vec<models::Issue>, String> {
|
||||
page: i32,
|
||||
) -> Result<Page<models::Issue>, String> {
|
||||
let client =
|
||||
Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?;
|
||||
let configuration = client.configuration();
|
||||
@@ -210,42 +244,35 @@ pub async fn load_pulls(
|
||||
.map_err(|error| error.to_string())?
|
||||
.login
|
||||
.ok_or("The server account has no username.")?;
|
||||
let mut pulls = Vec::new();
|
||||
for page in 1.. {
|
||||
let batch = apis::issue_api::issue_search_issues(
|
||||
&configuration,
|
||||
Some(status),
|
||||
None,
|
||||
(!milestone.is_empty()).then_some(milestone),
|
||||
None,
|
||||
None,
|
||||
Some("pulls"),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some(&owner),
|
||||
None,
|
||||
Some(page),
|
||||
Some(100),
|
||||
)
|
||||
.await
|
||||
.map_err(|error| error.to_string())?;
|
||||
if batch.is_empty() {
|
||||
break;
|
||||
}
|
||||
pulls.extend(batch);
|
||||
}
|
||||
Ok(pulls)
|
||||
apis::issue_api::issue_search_issues(
|
||||
&configuration,
|
||||
Some(status),
|
||||
None,
|
||||
(!milestone.is_empty()).then_some(milestone),
|
||||
None,
|
||||
None,
|
||||
Some("pulls"),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some(&owner),
|
||||
None,
|
||||
Some(page),
|
||||
Some(PAGE_SIZE),
|
||||
)
|
||||
.await
|
||||
.map(Page::from_items)
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub async fn load_pull_milestones(server: &Server) -> Result<Vec<String>, String> {
|
||||
let (open, closed) = tokio::try_join!(
|
||||
load_pulls(server, "open", ""),
|
||||
load_pulls(server, "closed", "")
|
||||
load_all_pulls(server, "open"),
|
||||
load_all_pulls(server, "closed")
|
||||
)?;
|
||||
Ok(open
|
||||
.into_iter()
|
||||
@@ -256,6 +283,18 @@ pub async fn load_pull_milestones(server: &Server) -> Result<Vec<String>, String
|
||||
.collect())
|
||||
}
|
||||
|
||||
async fn load_all_pulls(server: &Server, status: &str) -> Result<Vec<models::Issue>, String> {
|
||||
let mut pulls = Vec::new();
|
||||
for page in 1.. {
|
||||
let batch = load_pulls(server, status, "", page).await?;
|
||||
pulls.extend(batch.items);
|
||||
if !batch.has_more {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(pulls)
|
||||
}
|
||||
|
||||
pub async fn load_branches(
|
||||
server: &Server,
|
||||
owner: &str,
|
||||
@@ -336,11 +375,14 @@ pub async fn load_branch_commits(
|
||||
owner: &str,
|
||||
repository: &str,
|
||||
branch: &str,
|
||||
) -> Result<Vec<HistoryCommit>, String> {
|
||||
load_commits_for_ref(server, owner, repository, Some(branch))
|
||||
pages: u32,
|
||||
) -> Result<Page<HistoryCommit>, String> {
|
||||
load_commits_for_ref(server, owner, repository, Some(branch), pages)
|
||||
.await
|
||||
.map(|commits| {
|
||||
commits
|
||||
.map(|page| Page {
|
||||
has_more: page.has_more,
|
||||
items: page
|
||||
.items
|
||||
.into_iter()
|
||||
.map(|commit| HistoryCommit {
|
||||
commit,
|
||||
@@ -350,7 +392,7 @@ pub async fn load_branch_commits(
|
||||
connections: Vec::new(),
|
||||
refs: Vec::new(),
|
||||
})
|
||||
.collect()
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -359,13 +401,15 @@ pub async fn load_all_commits(
|
||||
owner: &str,
|
||||
repository: &str,
|
||||
branches: &[String],
|
||||
) -> Result<Vec<HistoryCommit>, String> {
|
||||
pages: u32,
|
||||
) -> Result<Page<HistoryCommit>, String> {
|
||||
let mut tasks = JoinSet::new();
|
||||
for (lane, branch) in branches.iter().cloned().enumerate() {
|
||||
let (server, owner, repository) =
|
||||
(server.clone(), owner.to_string(), repository.to_string());
|
||||
tasks.spawn(async move {
|
||||
let commits = load_commits_for_ref(&server, &owner, &repository, Some(&branch)).await?;
|
||||
let commits =
|
||||
load_commits_for_ref(&server, &owner, &repository, Some(&branch), pages).await?;
|
||||
Ok::<_, String>((lane, branch, commits))
|
||||
});
|
||||
}
|
||||
@@ -375,11 +419,19 @@ pub async fn load_all_commits(
|
||||
histories.push(result.map_err(|error| error.to_string())??);
|
||||
}
|
||||
histories.sort_by_key(|(lane, _, _)| *lane);
|
||||
let has_more = histories.iter().any(|(_, _, page)| page.has_more);
|
||||
let histories = histories
|
||||
.into_iter()
|
||||
.map(|(lane, branch, page)| (lane, branch, page.items))
|
||||
.collect();
|
||||
|
||||
let pull_refs = load_pull_refs(server, owner, repository)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
Ok(build_graph(histories, pull_refs))
|
||||
Ok(Page {
|
||||
items: build_graph(histories, pull_refs),
|
||||
has_more,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn load_pull_files(
|
||||
@@ -387,28 +439,21 @@ pub async fn load_pull_files(
|
||||
owner: &str,
|
||||
repository: &str,
|
||||
number: i64,
|
||||
) -> Result<Vec<models::ChangedFile>, String> {
|
||||
let mut files = Vec::new();
|
||||
for page in 1.. {
|
||||
let batch = apis::repository_api::repo_get_pull_request_files(
|
||||
configuration,
|
||||
owner,
|
||||
repository,
|
||||
number,
|
||||
None,
|
||||
None,
|
||||
Some(page),
|
||||
Some(100),
|
||||
)
|
||||
.await
|
||||
.map_err(|error| error.to_string())?;
|
||||
let done = batch.len() < 100;
|
||||
files.extend(batch);
|
||||
if done {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(files)
|
||||
page: i32,
|
||||
) -> Result<Page<models::ChangedFile>, String> {
|
||||
apis::repository_api::repo_get_pull_request_files(
|
||||
configuration,
|
||||
owner,
|
||||
repository,
|
||||
number,
|
||||
None,
|
||||
None,
|
||||
Some(page),
|
||||
Some(PAGE_SIZE),
|
||||
)
|
||||
.await
|
||||
.map(Page::from_items)
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub async fn load_issue(
|
||||
@@ -416,20 +461,37 @@ pub async fn load_issue(
|
||||
owner: &str,
|
||||
repository: &str,
|
||||
number: i64,
|
||||
page: i32,
|
||||
) -> Result<IssueDetails, String> {
|
||||
let client =
|
||||
Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?;
|
||||
let configuration = client.configuration();
|
||||
let (issue, comments) = tokio::join!(
|
||||
apis::issue_api::issue_get_issue(&configuration, owner, repository, number),
|
||||
apis::issue_api::issue_get_comments(&configuration, owner, repository, number, None, None,),
|
||||
load_issue_comments(&configuration, owner, repository, number, page),
|
||||
);
|
||||
Ok(IssueDetails {
|
||||
issue: issue.map_err(|error| error.to_string())?,
|
||||
comments: comments.map_err(|error| error.to_string())?,
|
||||
has_more: false,
|
||||
comments: comments?,
|
||||
})
|
||||
}
|
||||
|
||||
async fn load_issue_comments(
|
||||
configuration: &apis::configuration::Configuration,
|
||||
owner: &str,
|
||||
repository: &str,
|
||||
number: i64,
|
||||
page: i32,
|
||||
) -> Result<Vec<models::Comment>, String> {
|
||||
if page > 1 {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
apis::issue_api::issue_get_comments(configuration, owner, repository, number, None, None)
|
||||
.await
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub async fn load_issue_editor(
|
||||
server: &Server,
|
||||
owner: &str,
|
||||
@@ -544,19 +606,22 @@ pub async fn load_pull(
|
||||
owner: &str,
|
||||
repository: &str,
|
||||
number: i64,
|
||||
page: i32,
|
||||
) -> Result<PullDetails, String> {
|
||||
let client =
|
||||
Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?;
|
||||
let configuration = client.configuration();
|
||||
let (pull, comments, files) = tokio::join!(
|
||||
apis::repository_api::repo_get_pull_request(&configuration, owner, repository, number,),
|
||||
apis::issue_api::issue_get_comments(&configuration, owner, repository, number, None, None,),
|
||||
load_pull_files(&configuration, owner, repository, number),
|
||||
load_issue_comments(&configuration, owner, repository, number, page),
|
||||
load_pull_files(&configuration, owner, repository, number, page),
|
||||
);
|
||||
let files = files?;
|
||||
Ok(PullDetails {
|
||||
pull: pull.map_err(|error| error.to_string())?,
|
||||
comments: comments.map_err(|error| error.to_string())?,
|
||||
files: files.map_err(|error| error.to_string())?,
|
||||
has_more: files.has_more,
|
||||
comments: comments?,
|
||||
files: files.items,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -821,12 +886,14 @@ async fn load_commits_for_ref(
|
||||
owner: &str,
|
||||
repository: &str,
|
||||
branch: Option<&str>,
|
||||
) -> Result<Vec<models::Commit>, String> {
|
||||
pages: u32,
|
||||
) -> Result<Page<models::Commit>, String> {
|
||||
let client =
|
||||
Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?;
|
||||
let configuration = client.configuration();
|
||||
let mut commits = Vec::new();
|
||||
for page in 1.. {
|
||||
let mut has_more = false;
|
||||
for page in 1..=pages.max(1) {
|
||||
let batch = apis::repository_api::repo_get_all_commits(
|
||||
&configuration,
|
||||
owner,
|
||||
@@ -838,22 +905,25 @@ async fn load_commits_for_ref(
|
||||
None,
|
||||
None,
|
||||
Some(false),
|
||||
Some(page),
|
||||
Some(100),
|
||||
Some(page as i32),
|
||||
Some(PAGE_SIZE),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| error.to_string())?;
|
||||
let done = batch.len() < 100;
|
||||
has_more = batch.len() == PAGE_SIZE as usize;
|
||||
commits.extend(batch);
|
||||
if done {
|
||||
if !has_more {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(commits)
|
||||
Ok(Page {
|
||||
items: commits,
|
||||
has_more,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn load_home(server: &Server) -> Result<HomeData, String> {
|
||||
pub async fn load_home(server: &Server, page: i32) -> Result<HomeData, String> {
|
||||
let client =
|
||||
Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?;
|
||||
let configuration = client.configuration();
|
||||
@@ -863,34 +933,23 @@ pub async fn load_home(server: &Server) -> Result<HomeData, String> {
|
||||
.map_err(|error| error.to_string())?
|
||||
.login
|
||||
.ok_or("The server account has no username.")?;
|
||||
let activity_configuration = configuration.clone();
|
||||
let activity_login = login.clone();
|
||||
let activities = async move {
|
||||
let mut activities = Vec::new();
|
||||
for page in 1.. {
|
||||
let batch = apis::user_api::user_list_activity_feeds(
|
||||
&activity_configuration,
|
||||
&activity_login,
|
||||
Some(true),
|
||||
None,
|
||||
Some(page),
|
||||
Some(100),
|
||||
)
|
||||
.await
|
||||
.map_err(|error| error.to_string())?;
|
||||
if batch.is_empty() {
|
||||
break;
|
||||
}
|
||||
activities.extend(batch);
|
||||
}
|
||||
Ok::<_, String>(activities)
|
||||
};
|
||||
let activities = apis::user_api::user_list_activity_feeds(
|
||||
&configuration,
|
||||
&login,
|
||||
Some(true),
|
||||
None,
|
||||
Some(page),
|
||||
Some(PAGE_SIZE),
|
||||
);
|
||||
let (activities, heatmap) = tokio::join!(
|
||||
activities,
|
||||
apis::user_api::user_get_heatmap_data(&configuration, &login),
|
||||
);
|
||||
Ok(HomeData {
|
||||
activities: activities?,
|
||||
has_more: activities
|
||||
.as_ref()
|
||||
.is_ok_and(|items| items.len() == PAGE_SIZE as usize),
|
||||
activities: activities.map_err(|error| error.to_string())?,
|
||||
heatmap: heatmap.map_err(|error| error.to_string())?,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,6 +3,22 @@ use std::collections::{BTreeMap, BTreeSet};
|
||||
use gotcha_gitea::models;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const PAGE_SIZE: i32 = 30;
|
||||
|
||||
pub struct Page<T> {
|
||||
pub items: Vec<T>,
|
||||
pub has_more: bool,
|
||||
}
|
||||
|
||||
impl<T> Page<T> {
|
||||
pub fn from_items(items: Vec<T>) -> Self {
|
||||
Self {
|
||||
has_more: items.len() == PAGE_SIZE as usize,
|
||||
items,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum AppearanceMode {
|
||||
@@ -132,11 +148,13 @@ pub struct State {
|
||||
pub struct HomeData {
|
||||
pub activities: Vec<models::Activity>,
|
||||
pub heatmap: Vec<models::UserHeatmapData>,
|
||||
pub has_more: bool,
|
||||
}
|
||||
|
||||
pub struct IssueDetails {
|
||||
pub issue: models::Issue,
|
||||
pub comments: Vec<models::Comment>,
|
||||
pub has_more: bool,
|
||||
}
|
||||
|
||||
pub struct IssueEditorData {
|
||||
@@ -157,12 +175,14 @@ pub struct MilestoneDetails {
|
||||
pub milestone: models::Milestone,
|
||||
pub issues: Vec<models::Issue>,
|
||||
pub pulls: Vec<models::Issue>,
|
||||
pub has_more: bool,
|
||||
}
|
||||
|
||||
pub struct PullDetails {
|
||||
pub pull: models::PullRequest,
|
||||
pub comments: Vec<models::Comment>,
|
||||
pub files: Vec<models::ChangedFile>,
|
||||
pub has_more: bool,
|
||||
}
|
||||
|
||||
pub fn open_status() -> String {
|
||||
@@ -244,4 +264,10 @@ mod tests {
|
||||
assert_eq!(parse_api_date("2023-02-29T00:00:00Z"), None);
|
||||
assert_eq!(parse_api_date("not-a-date"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_api_pages_expose_more_results() {
|
||||
assert!(Page::from_items(vec![(); PAGE_SIZE as usize]).has_more);
|
||||
assert!(!Page::from_items(vec![(); PAGE_SIZE as usize - 1]).has_more);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,18 +173,36 @@ impl GotchaCore {
|
||||
save_preferences(&state.preferences).map_err(Into::into)
|
||||
}
|
||||
|
||||
pub async fn home(&self) -> Result<HomePage, GotchaError> {
|
||||
pub async fn home(&self, page: u32) -> Result<HomePage, GotchaError> {
|
||||
let server = self.server()?;
|
||||
let name = server.name.clone();
|
||||
Ok(home_page(name, load_home(&server).await?))
|
||||
Ok(home_page(
|
||||
name,
|
||||
load_home(&server, valid_page(page)?).await?,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn repositories(&self) -> Result<Vec<RepositoryRow>, GotchaError> {
|
||||
pub async fn repositories(&self, page: u32) -> Result<RepositoryListPage, GotchaError> {
|
||||
let server = self.server()?;
|
||||
let repositories = load_repositories(&server).await?;
|
||||
let page_number = valid_page(page)?;
|
||||
let repositories = load_repositories(&server, page_number).await?;
|
||||
let mut state = self.state.lock().unwrap();
|
||||
state.repositories = repositories;
|
||||
Ok(self.repository_rows(&state))
|
||||
if page_number == 1 {
|
||||
state.repositories.clear();
|
||||
}
|
||||
for repository in repositories.items {
|
||||
if let Some(existing) = state.repositories.iter_mut().find(|candidate| {
|
||||
candidate.owner == repository.owner && candidate.name == repository.name
|
||||
}) {
|
||||
*existing = repository;
|
||||
} else {
|
||||
state.repositories.push(repository);
|
||||
}
|
||||
}
|
||||
Ok(RepositoryListPage {
|
||||
rows: self.repository_rows(&state),
|
||||
has_more: repositories.has_more,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn toggle_favorite(
|
||||
@@ -209,7 +227,8 @@ impl GotchaCore {
|
||||
&self,
|
||||
owner: String,
|
||||
repository: String,
|
||||
) -> Result<Vec<IssueRow>, GotchaError> {
|
||||
page: u32,
|
||||
) -> Result<IssueListPage, GotchaError> {
|
||||
let server = self.server()?;
|
||||
let (status, filter) = {
|
||||
let state = self.state.lock().unwrap();
|
||||
@@ -224,17 +243,20 @@ impl GotchaCore {
|
||||
)
|
||||
};
|
||||
let labels: Vec<_> = filter.labels.into_iter().collect();
|
||||
Ok(issue_rows(
|
||||
&load_issues(
|
||||
&server,
|
||||
&owner,
|
||||
&repository,
|
||||
&status,
|
||||
&labels,
|
||||
&filter.milestone,
|
||||
)
|
||||
.await?,
|
||||
))
|
||||
let page = load_issues(
|
||||
&server,
|
||||
&owner,
|
||||
&repository,
|
||||
&status,
|
||||
&labels,
|
||||
&filter.milestone,
|
||||
valid_page(page)?,
|
||||
)
|
||||
.await?;
|
||||
Ok(IssueListPage {
|
||||
rows: issue_rows(&page.items),
|
||||
has_more: page.has_more,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn issue_filters(
|
||||
@@ -317,9 +339,17 @@ impl GotchaCore {
|
||||
owner: String,
|
||||
repository: String,
|
||||
number: i64,
|
||||
page: u32,
|
||||
) -> Result<IssuePage, GotchaError> {
|
||||
Ok(issue_page(
|
||||
load_issue(&self.server()?, &owner, &repository, number).await?,
|
||||
load_issue(
|
||||
&self.server()?,
|
||||
&owner,
|
||||
&repository,
|
||||
number,
|
||||
valid_page(page)?,
|
||||
)
|
||||
.await?,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -387,10 +417,14 @@ impl GotchaCore {
|
||||
&self,
|
||||
owner: String,
|
||||
repository: String,
|
||||
) -> Result<Vec<MilestoneRow>, GotchaError> {
|
||||
Ok(milestone_rows(
|
||||
&load_milestones(&self.server()?, &owner, &repository).await?,
|
||||
))
|
||||
page: u32,
|
||||
) -> Result<MilestoneListPage, GotchaError> {
|
||||
let page =
|
||||
load_milestones_page(&self.server()?, &owner, &repository, valid_page(page)?).await?;
|
||||
Ok(MilestoneListPage {
|
||||
rows: milestone_rows(&page.items),
|
||||
has_more: page.has_more,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn milestone(
|
||||
@@ -398,9 +432,10 @@ impl GotchaCore {
|
||||
owner: String,
|
||||
repository: String,
|
||||
id: i64,
|
||||
page: u32,
|
||||
) -> Result<MilestonePage, GotchaError> {
|
||||
Ok(milestone_page(
|
||||
load_milestone(&self.server()?, &owner, &repository, id).await?,
|
||||
load_milestone(&self.server()?, &owner, &repository, id, valid_page(page)?).await?,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -449,7 +484,7 @@ impl GotchaCore {
|
||||
save_preferences(&state.preferences).map_err(Into::into)
|
||||
}
|
||||
|
||||
pub async fn pulls(&self) -> Result<Vec<PullRow>, GotchaError> {
|
||||
pub async fn pulls(&self, page: u32) -> Result<PullListPage, GotchaError> {
|
||||
let server = self.server()?;
|
||||
let (status, filter) = {
|
||||
let state = self.state.lock().unwrap();
|
||||
@@ -463,9 +498,11 @@ impl GotchaCore {
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
};
|
||||
Ok(pull_rows(
|
||||
&load_pulls(&server, &status, &filter.milestone).await?,
|
||||
))
|
||||
let page = load_pulls(&server, &status, &filter.milestone, valid_page(page)?).await?;
|
||||
Ok(PullListPage {
|
||||
rows: pull_rows(&page.items),
|
||||
has_more: page.has_more,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn pull(
|
||||
@@ -473,9 +510,17 @@ impl GotchaCore {
|
||||
owner: String,
|
||||
repository: String,
|
||||
number: i64,
|
||||
page: u32,
|
||||
) -> Result<PullPage, GotchaError> {
|
||||
Ok(pull_page(
|
||||
load_pull(&self.server()?, &owner, &repository, number).await?,
|
||||
load_pull(
|
||||
&self.server()?,
|
||||
&owner,
|
||||
&repository,
|
||||
number,
|
||||
valid_page(page)?,
|
||||
)
|
||||
.await?,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -484,6 +529,7 @@ impl GotchaCore {
|
||||
owner: String,
|
||||
repository: String,
|
||||
branch: Option<String>,
|
||||
pages: u32,
|
||||
) -> Result<CommitPage, GotchaError> {
|
||||
let server = self.server()?;
|
||||
let default_branch = self
|
||||
@@ -497,10 +543,17 @@ impl GotchaCore {
|
||||
.unwrap_or_else(|| "main".into());
|
||||
let branches = load_branches(&server, &owner, &repository, &default_branch).await?;
|
||||
let commits = match branch.as_deref() {
|
||||
Some(branch) => load_branch_commits(&server, &owner, &repository, branch).await?,
|
||||
None => load_all_commits(&server, &owner, &repository, &branches).await?,
|
||||
Some(branch) => {
|
||||
load_branch_commits(&server, &owner, &repository, branch, pages.max(1)).await?
|
||||
}
|
||||
None => load_all_commits(&server, &owner, &repository, &branches, pages.max(1)).await?,
|
||||
};
|
||||
Ok(commit_page(branches, &commits, branch.is_none()))
|
||||
Ok(commit_page(
|
||||
branches,
|
||||
&commits.items,
|
||||
branch.is_none(),
|
||||
commits.has_more,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn repository_contents(
|
||||
@@ -574,6 +627,13 @@ fn validate_repository<'a>(
|
||||
Ok((owner, repository))
|
||||
}
|
||||
|
||||
fn valid_page(page: u32) -> Result<i32, GotchaError> {
|
||||
(page > 0)
|
||||
.then(|| i32::try_from(page).ok())
|
||||
.flatten()
|
||||
.ok_or_else(|| "Invalid page number.".into())
|
||||
}
|
||||
|
||||
impl GotchaCore {
|
||||
fn server(&self) -> Result<Server, GotchaError> {
|
||||
let state = self.state.lock().unwrap();
|
||||
|
||||
@@ -23,6 +23,12 @@ pub struct RepositoryRow {
|
||||
pub favorite: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, uniffi::Record)]
|
||||
pub struct RepositoryListPage {
|
||||
pub rows: Vec<RepositoryRow>,
|
||||
pub has_more: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, uniffi::Record)]
|
||||
pub struct LabelRow {
|
||||
pub name: String,
|
||||
@@ -40,6 +46,12 @@ pub struct IssueRow {
|
||||
pub labels: Vec<LabelRow>,
|
||||
}
|
||||
|
||||
#[derive(Clone, uniffi::Record)]
|
||||
pub struct IssueListPage {
|
||||
pub rows: Vec<IssueRow>,
|
||||
pub has_more: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, uniffi::Record)]
|
||||
pub struct PullRow {
|
||||
pub number: i64,
|
||||
@@ -65,6 +77,7 @@ pub struct IssuePage {
|
||||
pub milestone: String,
|
||||
pub body: String,
|
||||
pub comments: Vec<CommentRow>,
|
||||
pub has_more: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, uniffi::Record)]
|
||||
@@ -115,6 +128,13 @@ pub struct MilestonePage {
|
||||
pub milestone: MilestoneRow,
|
||||
pub issues: Vec<IssueRow>,
|
||||
pub pulls: Vec<PullRow>,
|
||||
pub has_more: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, uniffi::Record)]
|
||||
pub struct MilestoneListPage {
|
||||
pub rows: Vec<MilestoneRow>,
|
||||
pub has_more: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, uniffi::Record)]
|
||||
@@ -131,6 +151,13 @@ pub struct PullPage {
|
||||
pub files_ref: String,
|
||||
pub files: Vec<FileRow>,
|
||||
pub comments: Vec<CommentRow>,
|
||||
pub has_more: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, uniffi::Record)]
|
||||
pub struct PullListPage {
|
||||
pub rows: Vec<PullRow>,
|
||||
pub has_more: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, uniffi::Record)]
|
||||
@@ -149,6 +176,7 @@ pub struct CommitPage {
|
||||
pub branches: Vec<String>,
|
||||
pub commits: Vec<CommitRow>,
|
||||
pub lane_count: u32,
|
||||
pub has_more: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, uniffi::Record)]
|
||||
@@ -157,6 +185,12 @@ pub struct FileRow {
|
||||
pub status: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, uniffi::Record)]
|
||||
pub struct FileListPage {
|
||||
pub rows: Vec<FileRow>,
|
||||
pub has_more: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, uniffi::Record)]
|
||||
pub struct RepositoryContentRow {
|
||||
pub name: String,
|
||||
@@ -216,6 +250,7 @@ pub struct HomePage {
|
||||
pub pull_activities: Vec<ActivityRow>,
|
||||
pub heat_cells: Vec<HeatCell>,
|
||||
pub contribution_count: i64,
|
||||
pub has_more: bool,
|
||||
}
|
||||
|
||||
pub fn repository_rows(
|
||||
@@ -323,6 +358,7 @@ pub fn milestone_page(details: MilestoneDetails) -> MilestonePage {
|
||||
milestone: milestone_row(&details.milestone),
|
||||
issues: issue_rows(&details.issues),
|
||||
pulls: pull_rows(&details.pulls),
|
||||
has_more: details.has_more,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,6 +425,7 @@ pub fn commit_page(
|
||||
branches: Vec<String>,
|
||||
commits: &[HistoryCommit],
|
||||
show_graph: bool,
|
||||
has_more: bool,
|
||||
) -> CommitPage {
|
||||
let lane_count = if show_graph {
|
||||
commits
|
||||
@@ -454,6 +491,7 @@ pub fn commit_page(
|
||||
branches,
|
||||
commits,
|
||||
lane_count: lane_count as u32,
|
||||
has_more,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,6 +515,7 @@ pub fn issue_page(details: IssueDetails) -> IssuePage {
|
||||
.filter(|body| !body.is_empty())
|
||||
.unwrap_or_else(|| "No description provided.".into()),
|
||||
comments: details.comments.iter().map(comment_row).collect(),
|
||||
has_more: details.has_more,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -589,6 +628,7 @@ pub fn pull_page(details: PullDetails) -> PullPage {
|
||||
files_ref: pull_files_ref(pull),
|
||||
files: details.files.iter().filter_map(file_row).collect(),
|
||||
comments: details.comments.iter().map(comment_row).collect(),
|
||||
has_more: details.has_more,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -722,6 +762,7 @@ pub fn home_page(server_name: String, home: HomeData) -> HomePage {
|
||||
activities: home.activities.iter().map(activity_row).collect(),
|
||||
heat_cells,
|
||||
contribution_count,
|
||||
has_more: home.has_more,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1213,6 +1254,7 @@ mod tests {
|
||||
})
|
||||
.collect(),
|
||||
heatmap: Vec::new(),
|
||||
has_more: false,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1229,6 +1271,7 @@ mod tests {
|
||||
..Default::default()
|
||||
},
|
||||
comments: Vec::new(),
|
||||
has_more: false,
|
||||
});
|
||||
|
||||
assert_eq!(page.state, "closed");
|
||||
@@ -1331,6 +1374,7 @@ mod tests {
|
||||
})),
|
||||
..Default::default()
|
||||
}],
|
||||
has_more: false,
|
||||
});
|
||||
|
||||
assert!(page.issues.is_empty());
|
||||
|
||||
Reference in New Issue
Block a user