use crate::core::entity::{
CustomView, CustomViewId, Cycle, Favorite, IssueRef, Project, Team, TeamId,
};
use crate::core::store::Store;
#[derive(Debug, Clone, PartialEq)]
pub enum Request {
Favorites,
OpenInBrowser(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TeamPage {
Issues,
Cycles,
Projects,
}
#[derive(Debug, Clone)]
pub enum Target {
Folder,
View(CustomViewId),
MyIssues,
TeamPage(TeamId, TeamPage),
Project(Box<Project>),
Cycle(Cycle),
Issue(IssueRef),
Browser(String),
Nowhere,
}
impl PartialEq for Target {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Folder, Self::Folder)
| (Self::MyIssues, Self::MyIssues)
| (Self::Nowhere, Self::Nowhere) => true,
(Self::View(a), Self::View(b)) => a == b,
(Self::TeamPage(a, p), Self::TeamPage(b, q)) => a == b && p == q,
(Self::Project(a), Self::Project(b)) => a.id == b.id,
(Self::Cycle(a), Self::Cycle(b)) => a.id == b.id,
(Self::Issue(a), Self::Issue(b)) => a.id == b.id,
(Self::Browser(a), Self::Browser(b)) => a == b,
_ => false,
}
}
}
pub fn load() -> Request {
Request::Favorites
}
pub fn take_favorites(store: &mut Store, favorites: Vec<Favorite>) {
store.set_favorites(favorites);
}
pub fn target(favorite: &Favorite, views: &[CustomView], teams: &[Team]) -> Target {
if favorite.is_folder() {
return Target::Folder;
}
if let Some(view) = &favorite.custom_view
&& views.iter().any(|v| v.id == view.id)
{
return Target::View(view.id.clone());
}
if favorite.kind == "predefinedView" {
if favorite.predefined_view_type.as_deref() == Some("myIssues") {
return Target::MyIssues;
}
let team = favorite
.predefined_view_team
.as_ref()
.filter(|t| teams.iter().any(|x| x.id == t.id));
let page = match favorite.predefined_view_type.as_deref() {
Some("issues" | "allIssues" | "activeIssues" | "backlog") => Some(TeamPage::Issues),
Some("cycles") => Some(TeamPage::Cycles),
Some("projects") => Some(TeamPage::Projects),
_ => None,
};
if let (Some(team), Some(page)) = (team, page) {
return Target::TeamPage(team.id.clone(), page);
}
}
if let Some(project) = &favorite.project {
return Target::Project(Box::new(project.clone()));
}
if let Some(cycle) = &favorite.cycle {
return Target::Cycle(cycle.clone());
}
if let Some(issue) = &favorite.issue {
return Target::Issue(issue.clone());
}
match &favorite.url {
Some(url) => Target::Browser(url.clone()),
None => Target::Nowhere,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn fav(json: &str) -> Favorite {
serde_json::from_str(json).unwrap()
}
#[test]
fn the_favorites_are_asked_for() {
assert_eq!(load(), Request::Favorites);
}
fn views() -> Vec<CustomView> {
vec![serde_json::from_str(r#"{"id":"v1","name":"Mine"}"#).unwrap()]
}
fn teams() -> Vec<Team> {
vec![serde_json::from_str(r#"{"id":"t1","name":"Core","key":"COR"}"#).unwrap()]
}
fn target_of(json: &str) -> Target {
target(&fav(json), &views(), &teams())
}
#[test]
fn favorites_keep_linears_order() {
let mut store = Store::default();
take_favorites(
&mut store,
vec![
fav(r#"{"id":"b","type":"issue","sortOrder":2}"#),
fav(r#"{"id":"a","type":"issue","sortOrder":1}"#),
],
);
let ids: Vec<&str> = store.favorites.iter().map(|f| f.id.as_str()).collect();
assert_eq!(ids, ["a", "b"]);
}
#[test]
fn a_folder_folds() {
assert_eq!(
target_of(r#"{"id":"f","type":"folder","folderName":"Ops"}"#),
Target::Folder
);
}
#[test]
fn a_view_favorite_opens_the_view() {
assert_eq!(
target_of(r#"{"id":"f","type":"customView","customView":{"id":"v1"}}"#),
Target::View("v1".into())
);
assert_eq!(
target_of(
r#"{"id":"f","type":"customView","customView":{"id":"v9"},"url":"https://x"}"#
),
Target::Browser("https://x".into())
);
}
#[test]
fn predefined_pages_open_as_the_page() {
assert_eq!(
target_of(r#"{"id":"f","type":"predefinedView","predefinedViewType":"myIssues"}"#),
Target::MyIssues
);
assert_eq!(
target_of(
r#"{"id":"f","type":"predefinedView","predefinedViewType":"backlog",
"predefinedViewTeam":{"id":"t1"}}"#
),
Target::TeamPage("t1".into(), TeamPage::Issues)
);
assert_eq!(
target_of(
r#"{"id":"f","type":"predefinedView","predefinedViewType":"cycles",
"predefinedViewTeam":{"id":"t9"}}"#
),
Target::Nowhere
);
}
#[test]
fn a_project_cycle_or_issue_opens_in_place() {
assert!(matches!(
target_of(r#"{"id":"f","type":"project","project":{"id":"p","name":"P"}}"#),
Target::Project(p) if p.name == "P"
));
assert!(matches!(
target_of(r#"{"id":"f","type":"cycle","cycle":{"id":"c"}}"#),
Target::Cycle(_)
));
assert!(matches!(
target_of(
r#"{"id":"f","type":"issue","issue":{"id":"i","identifier":"COR-1","title":"t"}}"#
),
Target::Issue(i) if i.identifier == "COR-1"
));
}
#[test]
fn anything_else_opens_in_the_browser() {
assert_eq!(
target_of(r#"{"id":"f","type":"document","url":"https://linear.app/doc"}"#),
Target::Browser("https://linear.app/doc".into())
);
assert_eq!(
target_of(r#"{"id":"f","type":"document"}"#),
Target::Nowhere
);
}
#[test]
fn targets_to_one_project_cycle_or_issue_are_the_same_place() {
let project = |name: &str| {
target_of(&format!(
r#"{{"id":"f","type":"project","project":{{"id":"p","name":"{name}"}}}}"#
))
};
assert_eq!(project("Old name"), project("New name"));
let cycle = |n: u32| {
target_of(&format!(
r#"{{"id":"f","type":"cycle","cycle":{{"id":"c","number":{n}}}}}"#
))
};
assert_eq!(cycle(1), cycle(2));
let issue = |title: &str| {
target_of(&format!(
r#"{{"id":"f","type":"issue","issue":{{"id":"i","identifier":"COR-1","title":"{title}"}}}}"#
))
};
assert_eq!(issue("Before"), issue("After"));
assert_ne!(project("P"), Target::Folder);
}
#[test]
fn a_teams_projects_open_as_the_page_and_other_pages_in_the_browser() {
assert_eq!(
target_of(
r#"{"id":"f","type":"predefinedView","predefinedViewType":"projects",
"predefinedViewTeam":{"id":"t1"}}"#
),
Target::TeamPage("t1".into(), TeamPage::Projects)
);
assert_eq!(
target_of(
r#"{"id":"f","type":"predefinedView","predefinedViewType":"roadmap",
"predefinedViewTeam":{"id":"t1"},"url":"https://linear.app/r"}"#
),
Target::Browser("https://linear.app/r".into())
);
}
}