diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2024-07-05 11:06:04 +0100 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2024-07-05 11:06:04 +0100 |
| commit | ba82a894fad645757c49242c11573b6c5dd8d1e6 (patch) | |
| tree | 4026fe28e40260578a817154c9ed614a16dfcb7f /src/sub_commands/list.rs | |
| parent | d70a6ef029b90d00024da82e945316ce58b9507b (diff) | |
feat(init): group by statusv1.3-beta1
show open proposal by defult and include options to filter
by other statuses
there are not tests for this currently as the intention is
to transform 'list' further by adding a 'fetch'
Diffstat (limited to 'src/sub_commands/list.rs')
| -rw-r--r-- | src/sub_commands/list.rs | 194 |
1 files changed, 155 insertions, 39 deletions
diff --git a/src/sub_commands/list.rs b/src/sub_commands/list.rs index 24979fe..5dc868c 100644 --- a/src/sub_commands/list.rs +++ b/src/sub_commands/list.rs | |||
| @@ -46,39 +46,134 @@ pub async fn launch() -> Result<()> { | |||
| 46 | 46 | ||
| 47 | println!("finding proposals..."); | 47 | println!("finding proposals..."); |
| 48 | 48 | ||
| 49 | let proposal_events_and_revisions: Vec<nostr::Event> = | 49 | let proposal_events_and_revisions_and_statuses: Vec<nostr::Event> = |
| 50 | find_proposal_events(&client, &repo_ref, &root_commit.to_string()).await?; | 50 | find_proposal_and_status_events(&client, &repo_ref, &root_commit.to_string()).await?; |
| 51 | 51 | ||
| 52 | if proposal_events_and_revisions.is_empty() { | 52 | if proposal_events_and_revisions_and_statuses.is_empty() { |
| 53 | println!("no proposals found... create one? try `ngit send`"); | 53 | println!("no proposals found... create one? try `ngit send`"); |
| 54 | return Ok(()); | 54 | return Ok(()); |
| 55 | } | 55 | } |
| 56 | let proposal_events: Vec<&nostr::Event> = proposal_events_and_revisions | 56 | let proposal_events: Vec<&nostr::Event> = proposal_events_and_revisions_and_statuses |
| 57 | .iter() | 57 | .iter() |
| 58 | .filter(|e| !event_is_revision_root(e)) | 58 | .filter(|e| !event_is_revision_root(e) && e.kind().as_u16().eq(&PATCH_KIND)) |
| 59 | .collect::<Vec<&nostr::Event>>(); | 59 | .collect::<Vec<&nostr::Event>>(); |
| 60 | 60 | ||
| 61 | let mut open_proposals: Vec<&nostr::Event> = vec![]; | ||
| 62 | let mut draft_proposals: Vec<&nostr::Event> = vec![]; | ||
| 63 | let mut closed_proposals: Vec<&nostr::Event> = vec![]; | ||
| 64 | let mut applied_proposals: Vec<&nostr::Event> = vec![]; | ||
| 65 | |||
| 66 | for proposal in &proposal_events { | ||
| 67 | let status = if let Some(e) = proposal_events_and_revisions_and_statuses | ||
| 68 | .iter() | ||
| 69 | .filter(|e| { | ||
| 70 | status_kinds().contains(&e.kind()) | ||
| 71 | && e.iter_tags() | ||
| 72 | .any(|t| t.as_vec()[1].eq(&proposal.id.to_string())) | ||
| 73 | }) | ||
| 74 | .collect::<Vec<&nostr::Event>>() | ||
| 75 | .first() | ||
| 76 | { | ||
| 77 | e.kind().as_u16() | ||
| 78 | } else { | ||
| 79 | STATUS_KIND_OPEN | ||
| 80 | }; | ||
| 81 | if status.eq(&STATUS_KIND_OPEN) { | ||
| 82 | open_proposals.push(proposal); | ||
| 83 | } else if status.eq(&STATUS_KIND_CLOSED) { | ||
| 84 | closed_proposals.push(proposal); | ||
| 85 | } else if status.eq(&STATUS_KIND_DRAFT) { | ||
| 86 | draft_proposals.push(proposal); | ||
| 87 | } else if status.eq(&STATUS_KIND_APPLIED) { | ||
| 88 | applied_proposals.push(proposal); | ||
| 89 | } | ||
| 90 | } | ||
| 91 | |||
| 92 | if proposal_events_and_revisions_and_statuses.is_empty() { | ||
| 93 | println!("no open proposals found... create one? try `ngit send`"); | ||
| 94 | return Ok(()); | ||
| 95 | } | ||
| 96 | |||
| 97 | let mut selected_status = STATUS_KIND_OPEN; | ||
| 98 | |||
| 61 | loop { | 99 | loop { |
| 100 | let proposals_for_status = if selected_status == STATUS_KIND_OPEN { | ||
| 101 | &open_proposals | ||
| 102 | } else if selected_status == STATUS_KIND_DRAFT { | ||
| 103 | &draft_proposals | ||
| 104 | } else if selected_status == STATUS_KIND_CLOSED { | ||
| 105 | &closed_proposals | ||
| 106 | } else if selected_status == STATUS_KIND_APPLIED { | ||
| 107 | &applied_proposals | ||
| 108 | } else { | ||
| 109 | &proposal_events | ||
| 110 | }; | ||
| 111 | |||
| 112 | let prompt = if proposal_events.len().eq(&open_proposals.len()) { | ||
| 113 | "all proposals" | ||
| 114 | } else if selected_status == STATUS_KIND_OPEN { | ||
| 115 | if open_proposals.is_empty() { | ||
| 116 | "proposals menu" | ||
| 117 | } else { | ||
| 118 | "open proposals" | ||
| 119 | } | ||
| 120 | } else if selected_status == STATUS_KIND_DRAFT { | ||
| 121 | "draft proposals" | ||
| 122 | } else if selected_status == STATUS_KIND_CLOSED { | ||
| 123 | "closed proposals" | ||
| 124 | } else { | ||
| 125 | "applied proposals" | ||
| 126 | }; | ||
| 127 | |||
| 128 | let mut choices: Vec<String> = proposals_for_status | ||
| 129 | .iter() | ||
| 130 | .map(|e| { | ||
| 131 | if let Ok(cl) = event_to_cover_letter(e) { | ||
| 132 | cl.title | ||
| 133 | } else if let Ok(msg) = tag_value(e, "description") { | ||
| 134 | msg.split('\n').collect::<Vec<&str>>()[0].to_string() | ||
| 135 | } else { | ||
| 136 | e.id.to_string() | ||
| 137 | } | ||
| 138 | }) | ||
| 139 | .collect(); | ||
| 140 | |||
| 141 | if !selected_status.eq(&STATUS_KIND_OPEN) && open_proposals.len().gt(&0) { | ||
| 142 | choices.push(format!("({}) Open proposals...", open_proposals.len())); | ||
| 143 | } | ||
| 144 | if !selected_status.eq(&STATUS_KIND_DRAFT) && draft_proposals.len().gt(&0) { | ||
| 145 | choices.push(format!("({}) Draft proposals...", draft_proposals.len())); | ||
| 146 | } | ||
| 147 | if !selected_status.eq(&STATUS_KIND_CLOSED) && closed_proposals.len().gt(&0) { | ||
| 148 | choices.push(format!("({}) Closed proposals...", closed_proposals.len())); | ||
| 149 | } | ||
| 150 | if !selected_status.eq(&STATUS_KIND_APPLIED) && applied_proposals.len().gt(&0) { | ||
| 151 | choices.push(format!( | ||
| 152 | "({}) Applied proposals...", | ||
| 153 | applied_proposals.len() | ||
| 154 | )); | ||
| 155 | } | ||
| 156 | |||
| 62 | let selected_index = Interactor::default().choice( | 157 | let selected_index = Interactor::default().choice( |
| 63 | PromptChoiceParms::default() | 158 | PromptChoiceParms::default() |
| 64 | .with_prompt("all proposals") | 159 | .with_prompt(prompt) |
| 65 | .with_choices( | 160 | .with_choices(choices.clone()), |
| 66 | proposal_events | ||
| 67 | .iter() | ||
| 68 | .map(|e| { | ||
| 69 | if let Ok(cl) = event_to_cover_letter(e) { | ||
| 70 | cl.title | ||
| 71 | } else if let Ok(msg) = tag_value(e, "description") { | ||
| 72 | msg.split('\n').collect::<Vec<&str>>()[0].to_string() | ||
| 73 | } else { | ||
| 74 | e.id.to_string() | ||
| 75 | } | ||
| 76 | }) | ||
| 77 | .collect(), | ||
| 78 | ), | ||
| 79 | )?; | 161 | )?; |
| 80 | 162 | ||
| 81 | let cover_letter = event_to_cover_letter(proposal_events[selected_index]) | 163 | if (selected_index + 1).gt(&proposals_for_status.len()) { |
| 164 | if choices[selected_index].contains("Open") { | ||
| 165 | selected_status = STATUS_KIND_OPEN; | ||
| 166 | } else if choices[selected_index].contains("Draft") { | ||
| 167 | selected_status = STATUS_KIND_DRAFT; | ||
| 168 | } else if choices[selected_index].contains("Closed") { | ||
| 169 | selected_status = STATUS_KIND_CLOSED; | ||
| 170 | } else if choices[selected_index].contains("Applied") { | ||
| 171 | selected_status = STATUS_KIND_APPLIED; | ||
| 172 | } | ||
| 173 | continue; | ||
| 174 | } | ||
| 175 | |||
| 176 | let cover_letter = event_to_cover_letter(proposals_for_status[selected_index]) | ||
| 82 | .context("cannot extract proposal details from proposal root event")?; | 177 | .context("cannot extract proposal details from proposal root event")?; |
| 83 | 178 | ||
| 84 | println!("finding commits..."); | 179 | println!("finding commits..."); |
| @@ -86,14 +181,16 @@ pub async fn launch() -> Result<()> { | |||
| 86 | let commits_events: Vec<nostr::Event> = find_commits_for_proposal_root_events( | 181 | let commits_events: Vec<nostr::Event> = find_commits_for_proposal_root_events( |
| 87 | &client, | 182 | &client, |
| 88 | &[ | 183 | &[ |
| 89 | vec![proposal_events[selected_index]], | 184 | vec![proposals_for_status[selected_index]], |
| 90 | proposal_events_and_revisions | 185 | proposal_events_and_revisions_and_statuses |
| 91 | .iter() | 186 | .iter() |
| 92 | .filter(|e| { | 187 | .filter(|e| { |
| 93 | e.tags.iter().any(|t| { | 188 | e.kind.as_u16().eq(&PATCH_KIND) |
| 94 | t.as_vec().len().gt(&1) | 189 | && e.tags.iter().any(|t| { |
| 95 | && t.as_vec()[1].eq(&proposal_events[selected_index].id.to_string()) | 190 | t.as_vec().len().gt(&1) |
| 96 | }) | 191 | && t.as_vec()[1] |
| 192 | .eq(&proposals_for_status[selected_index].id.to_string()) | ||
| 193 | }) | ||
| 97 | }) | 194 | }) |
| 98 | .collect::<Vec<&nostr::Event>>(), | 195 | .collect::<Vec<&nostr::Event>>(), |
| 99 | ] | 196 | ] |
| @@ -718,45 +815,64 @@ pub fn get_most_recent_patch_with_ancestors( | |||
| 718 | Ok(res) | 815 | Ok(res) |
| 719 | } | 816 | } |
| 720 | 817 | ||
| 721 | pub async fn find_proposal_events( | 818 | pub static STATUS_KIND_OPEN: u16 = 1630; |
| 819 | pub static STATUS_KIND_APPLIED: u16 = 1631; | ||
| 820 | pub static STATUS_KIND_CLOSED: u16 = 1632; | ||
| 821 | pub static STATUS_KIND_DRAFT: u16 = 1633; | ||
| 822 | |||
| 823 | fn status_kinds() -> Vec<nostr::Kind> { | ||
| 824 | vec![ | ||
| 825 | nostr::Kind::Custom(STATUS_KIND_OPEN), | ||
| 826 | nostr::Kind::Custom(STATUS_KIND_APPLIED), | ||
| 827 | nostr::Kind::Custom(STATUS_KIND_CLOSED), | ||
| 828 | nostr::Kind::Custom(STATUS_KIND_DRAFT), | ||
| 829 | ] | ||
| 830 | } | ||
| 831 | |||
| 832 | pub async fn find_proposal_and_status_events( | ||
| 722 | #[cfg(test)] client: &crate::client::MockConnect, | 833 | #[cfg(test)] client: &crate::client::MockConnect, |
| 723 | #[cfg(not(test))] client: &Client, | 834 | #[cfg(not(test))] client: &Client, |
| 724 | repo_ref: &RepoRef, | 835 | repo_ref: &RepoRef, |
| 725 | root_commit: &str, | 836 | root_commit: &str, |
| 726 | ) -> Result<Vec<nostr::Event>> { | 837 | ) -> Result<Vec<nostr::Event>> { |
| 838 | let repo_tags_filter = nostr::Filter::default().custom_tag( | ||
| 839 | nostr::SingleLetterTag::lowercase(nostr::Alphabet::A), | ||
| 840 | repo_ref | ||
| 841 | .maintainers | ||
| 842 | .iter() | ||
| 843 | .map(|m| format!("{REPO_REF_KIND}:{m}:{}", repo_ref.identifier)), | ||
| 844 | ); | ||
| 727 | let mut proposals = client | 845 | let mut proposals = client |
| 728 | .get_events( | 846 | .get_events( |
| 729 | repo_ref.relays.clone(), | 847 | repo_ref.relays.clone(), |
| 730 | vec![ | 848 | vec![ |
| 731 | nostr::Filter::default() | 849 | repo_tags_filter |
| 850 | .clone() | ||
| 732 | .kind(nostr::Kind::Custom(PATCH_KIND)) | 851 | .kind(nostr::Kind::Custom(PATCH_KIND)) |
| 733 | .custom_tag( | 852 | .custom_tag( |
| 734 | nostr::SingleLetterTag::lowercase(nostr::Alphabet::T), | 853 | nostr::SingleLetterTag::lowercase(nostr::Alphabet::T), |
| 735 | vec!["root"], | 854 | vec!["root"], |
| 736 | ) | ||
| 737 | .custom_tag( | ||
| 738 | nostr::SingleLetterTag::lowercase(nostr::Alphabet::A), | ||
| 739 | repo_ref | ||
| 740 | .maintainers | ||
| 741 | .iter() | ||
| 742 | .map(|m| format!("{REPO_REF_KIND}:{m}:{}", repo_ref.identifier)), | ||
| 743 | ), | 855 | ), |
| 744 | // also pick up proposals from the same repo but no target at our maintainers repo | 856 | // also pick up proposals from the same repo but no target at our maintainers repo |
| 745 | // events | 857 | // events |
| 746 | nostr::Filter::default() | 858 | nostr::Filter::default() |
| 859 | .reference(root_commit) | ||
| 747 | .kind(nostr::Kind::Custom(PATCH_KIND)) | 860 | .kind(nostr::Kind::Custom(PATCH_KIND)) |
| 748 | .custom_tag( | 861 | .custom_tag( |
| 749 | nostr::SingleLetterTag::lowercase(nostr::Alphabet::T), | 862 | nostr::SingleLetterTag::lowercase(nostr::Alphabet::T), |
| 750 | vec!["root"], | 863 | vec!["root"], |
| 751 | ) | 864 | ), |
| 752 | .reference(root_commit), | 865 | repo_tags_filter.clone().kinds(status_kinds().clone()), |
| 866 | nostr::Filter::default() | ||
| 867 | .reference(root_commit) | ||
| 868 | .kinds(status_kinds().clone()), | ||
| 753 | ], | 869 | ], |
| 754 | ) | 870 | ) |
| 755 | .await | 871 | .await |
| 756 | .context("cannot get proposal events")? | 872 | .context("cannot get proposal events")? |
| 757 | .iter() | 873 | .iter() |
| 758 | .filter(|e| { | 874 | .filter(|e| { |
| 759 | event_is_patch_set_root(e) | 875 | (event_is_patch_set_root(e) || status_kinds().contains(&e.kind())) |
| 760 | && (e | 876 | && (e |
| 761 | .tags | 877 | .tags |
| 762 | .iter() | 878 | .iter() |