use anyhow::Result;
use crate::api::client::LinearClient;
use crate::message::{Message, Page, Request};
pub async fn execute_request(client: &LinearClient, req: Request, per_page: u32) -> Message {
match run_request(client, &req, per_page).await {
Ok(msg) => msg,
Err(e) => Message::Failed {
request: Box::new(req),
error: e.to_string(),
},
}
}
async fn run_request(client: &LinearClient, req: &Request, per_page: u32) -> Result<Message> {
let append = req.cursor().is_some();
Ok(match req {
Request::Teams => Message::Teams(client.teams().await?),
Request::Viewer => {
let viewer = client.viewer().await?;
Message::Viewer {
id: viewer.id,
organization: viewer.organization,
}
}
Request::TeamContext { team_id } => {
let (states, members) = tokio::join!(
client.workflow_states(team_id),
client.team_members(team_id)
);
Message::TeamContext {
team_id: team_id.clone(),
states: states?,
members: members?,
}
}
Request::Issues {
team_id,
after,
preset,
} => {
let (issues, info) = client
.issues(team_id, preset.state_filter(), after.as_deref(), per_page)
.await?;
Message::Issues {
team_id: team_id.clone(),
preset: *preset,
page: Page::new(issues, info, append),
}
}
Request::MyIssues { user_id, after } => {
let (issues, info) = client
.my_issues(user_id, after.as_deref(), per_page)
.await?;
Message::MyIssues(Page::new(issues, info, append))
}
Request::Search { term, team_id } => {
let (issues, _) = client
.search_issues(term, team_id.as_ref(), per_page)
.await?;
Message::SearchResults {
term: term.clone(),
team_id: team_id.clone(),
issues,
}
}
Request::PaletteSearch { term, seq } => {
let (issues, _) = client.search_issues(term, None, per_page).await?;
Message::PaletteResults { seq: *seq, issues }
}
Request::CustomViews => Message::CustomViews(client.custom_views().await?),
Request::Favorites => Message::Favorites(client.favorites().await?),
Request::ViewIssues { view_id, after } => {
let (issues, info) = client
.custom_view_issues(view_id, after.as_deref(), per_page)
.await?;
Message::ViewIssues {
view_id: view_id.clone(),
page: Page::new(issues, info, append),
}
}
Request::ViewProjects { view_id, after } => {
let (projects, info) = client
.custom_view_projects(view_id, after.as_deref())
.await?;
Message::ViewProjects {
view_id: view_id.clone(),
page: Page::new(projects, info, append),
}
}
Request::Projects { team_id, after } => {
let (projects, info) = client.projects(team_id, after.as_deref()).await?;
Message::Projects {
team_id: team_id.clone(),
page: Page::new(projects, info, append),
}
}
Request::Cycles { team_id, after } => {
let (cycles, info) = client.cycles(team_id, after.as_deref()).await?;
Message::Cycles {
team_id: team_id.clone(),
page: Page::new(cycles, info, append),
}
}
Request::IssueDetail { issue_id } => {
Message::IssueDetail(Box::new(client.issue_detail(issue_id).await?))
}
Request::ProjectIssues { project_id, after } => {
let (issues, info) = client.project_issues(project_id, after.as_deref()).await?;
Message::ProjectIssues {
project_id: project_id.clone(),
page: Page::new(issues, info, append),
}
}
Request::CycleIssues { cycle_id, after } => {
let (issues, info) = client.cycle_issues(cycle_id, after.as_deref()).await?;
Message::CycleIssues {
cycle_id: cycle_id.clone(),
page: Page::new(issues, info, append),
}
}
Request::UpdateStatus { issue_id, state_id } => {
client.update_issue_state(issue_id, state_id).await?;
Message::Mutated("Status updated")
}
Request::UpdatePriority { issue_id, priority } => {
client.update_issue_priority(issue_id, *priority).await?;
Message::Mutated("Priority updated")
}
Request::UpdateAssignee {
issue_id,
assignee_id,
} => {
client
.update_issue_assignee(issue_id, assignee_id.as_ref())
.await?;
Message::Mutated("Assignee updated")
}
Request::CreateComment { issue_id, body } => {
client.create_comment(issue_id, body).await?;
Message::Mutated("Comment posted")
}
Request::CreateIssue {
team_id,
title,
description,
priority,
} => {
let issue = client
.create_issue(team_id, title, description.as_deref(), *priority)
.await?;
Message::IssueCreated {
team_id: team_id.clone(),
issue: Box::new(issue),
}
}
Request::OpenUrl(url) => {
open::that_detached(url)?;
Message::Mutated("Opened in browser")
}
Request::Herdr(handoff) => {
crate::herdr::deliver(handoff).await?;
Message::Mutated(handoff.done())
}
})
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use serde_json::json;
use wiremock::matchers::method;
use wiremock::{Mock, MockServer, ResponseTemplate};
use super::*;
use crate::api::client::StaticCredentials;
use crate::api::ids::TeamId;
fn client(server: &MockServer) -> LinearClient {
LinearClient::with_endpoint(server.uri(), Arc::new(StaticCredentials("key".into())))
}
#[tokio::test]
async fn a_page_names_the_team_it_was_fetched_for() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.respond_with(
ResponseTemplate::new(200).set_body_json(json!({ "data": { "team": {
"cycles": { "nodes": [], "pageInfo": { "hasNextPage": false } }
} } })),
)
.mount(&server)
.await;
let request = Request::Cycles {
team_id: TeamId::new("t1"),
after: Some("c1".into()),
};
match execute_request(&client(&server), request, 50).await {
Message::Cycles { team_id, page } => {
assert_eq!(team_id, "t1");
assert!(page.append, "a page after a cursor extends the list");
}
other => panic!("unexpected {other:?}"),
}
}
#[tokio::test]
async fn a_failure_carries_the_request_back() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.respond_with(ResponseTemplate::new(500))
.mount(&server)
.await;
let request = Request::Teams;
match execute_request(&client(&server), request.clone(), 50).await {
Message::Failed {
request: failed,
error,
} => {
assert_eq!(*failed, request);
assert!(error.contains("500"), "{error}");
}
other => panic!("unexpected {other:?}"),
}
}
}