Add primitive support for rendering api v3 jira docs

This commit is contained in:
Daan Boerlage 2025-01-22 03:23:12 +01:00
parent 563ec7bd2f
commit fa387c5546
Signed by: daan
GPG key ID: FCE070E1E4956606
9 changed files with 224 additions and 12 deletions

View file

@ -1,13 +1,13 @@
use crate::cli::FormatMode;
use crate::jira_config::JiraConfig;
use crate::renderer::hyperlink;
use crate::renderer::{hyperlink, render_doc};
use crossterm::style::{Color, Stylize};
use libjirac::client::commands::IssueGetCommand;
use libjirac::client::JiraClient;
use libjirac::entities::issue::JiraIssue;
use libjirac::entities::issue::{Description, JiraIssue};
use std::io::Write;
fn pretty_print(issue: &JiraIssue) -> Result<(), Box<dyn std::error::Error>> {
fn pretty_print(config: &JiraConfig, issue: &JiraIssue) -> Result<(), Box<dyn std::error::Error>> {
println!("\n== Title {:=<71}", "");
println!(
"{}: {}",
@ -65,7 +65,9 @@ fn pretty_print(issue: &JiraIssue) -> Result<(), Box<dyn std::error::Error>> {
print!("{}", written);
println!("\n== Description {:=<65}", "");
match issue.fields.description.clone() {
Some(x) => println!("{}", x),
Some(x) => match x {
Description::Doc(doc) => println!("{}", render_doc(doc)?),
},
None => println!("(Issue does not have a description)"),
}
@ -76,13 +78,13 @@ fn pretty_print(issue: &JiraIssue) -> Result<(), Box<dyn std::error::Error>> {
comment.author.display_name.red(),
comment.created.with_timezone(&chrono::Local)
);
println!("{}", comment.body);
println!("{}", render_doc(comment.body)?);
}
println!("\n== Actions {:=<69}", "");
println!(
"{}",
hyperlink(
&issue.href,
&format!("{}/browse/{}", config.url, issue.key),
&"Open Issue".green().underline(Color::Green).to_string()
)
);
@ -108,7 +110,7 @@ pub async fn exec(output: FormatMode, issue_key: &str) -> Result<(), Box<dyn std
let fetched_issue = client.exec(get_cmd).await?;
match output {
FormatMode::Pretty => pretty_print(&fetched_issue)?,
FormatMode::Pretty => pretty_print(&config, &fetched_issue)?,
FormatMode::Json => json_print(&fetched_issue)?,
FormatMode::Compact => todo!(),
}

View file

@ -1,3 +1,5 @@
mod doc;
mod hyperlink;
pub use doc::render_doc;
pub use hyperlink::hyperlink;

View file

@ -0,0 +1,109 @@
use crossterm::style::Stylize;
use libjirac::entities::doc::{Doc, DocMedia, DocNode, DocTextMarker, DocTextNode};
use std::fmt::Write;
fn render_doc_node(f: &mut String, node: &DocNode) -> Result<(), Box<dyn std::error::Error>> {
match node {
DocNode::Paragraph(x) => {
for node in x {
render_text_node(f, node)?;
}
writeln!(f)?;
}
DocNode::BulletList(x) => {
for node in x {
write!(f, "* ")?;
render_doc_node(f, node)?;
}
}
DocNode::ListItem(x) => {
for node in x {
render_doc_node(f, node)?;
}
}
DocNode::OrderedList(x) => {
let mut i = 1;
for node in x {
write!(f, "{}. ", i)?;
i += 1;
render_doc_node(f, node)?;
}
}
DocNode::Rule => {
writeln!(f, "---")?;
}
DocNode::Heading(x) => {
for node in x {
render_text_node(f, node)?;
}
}
DocNode::MediaSingle(x) => {
for node in x {
render_text_markets(f, node)?;
}
}
}
Ok(())
}
fn render_text_node(f: &mut String, node: &DocTextNode) -> Result<(), Box<dyn std::error::Error>> {
match node {
DocTextNode::Text(x) => {
write!(f, "{}", x.text)?;
}
DocTextNode::HardBreak => {
writeln!(f)?;
}
DocTextNode::InlineCard(x) => {
writeln!(f, "{}", x.attrs.url)?;
}
DocTextNode::Mention(x) => {
write!(f, "{}", x.attrs.text.clone().green())?;
}
}
Ok(())
}
fn render_text_markets(
f: &mut String,
node: &DocTextMarker,
) -> Result<(), Box<dyn std::error::Error>> {
match node {
DocTextMarker::Link(x) => {
write!(f, "{}", x.href)?;
}
DocTextMarker::Strong => {
// no-op
}
DocTextMarker::Code => {
// no-op
}
DocTextMarker::Media(x) => {
render_media(f, x)?;
}
}
Ok(())
}
fn render_media(f: &mut String, node: &DocMedia) -> Result<(), Box<dyn std::error::Error>> {
match node {
DocMedia::File(x) => {
writeln!(f, "[file:{}:{}]", x.id, x.alt)?;
}
}
Ok(())
}
pub fn render_doc(doc: Doc) -> Result<String, Box<dyn std::error::Error>> {
let mut f = String::new();
for node in doc.content {
render_doc_node(&mut f, &node)?;
}
Ok(f)
}

View file

@ -18,7 +18,7 @@ impl JiraCommand for IssueCreateCommand {
const REQUEST_TYPE: JiraRequestType = JiraRequestType::Create;
fn endpoint(&self) -> String {
"/rest/api/2/issue".to_string()
"/rest/api/3/issue".to_string()
}
fn request_body(&self) -> Option<&Self::TPayload> {

View file

@ -20,7 +20,7 @@ impl JiraCommand for IssueGetCommand {
const REQUEST_TYPE: JiraRequestType = JiraRequestType::Read;
fn endpoint(&self) -> String {
format!("/rest/api/2/issue/{}", self.key)
format!("/rest/api/3/issue/{}", self.key)
}
fn request_body(&self) -> Option<&Self::TPayload> {

View file

@ -20,7 +20,7 @@ impl JiraCommand for SearchCommand {
const REQUEST_TYPE: JiraRequestType = JiraRequestType::Read;
fn endpoint(&self) -> String {
"/rest/api/2/search".to_string()
"/rest/api/3/search".to_string()
}
fn request_body(&self) -> Option<&Self::TPayload> {

View file

@ -1,3 +1,4 @@
pub mod doc;
pub mod issue;
pub mod issue_request;
pub mod search;

View file

@ -0,0 +1,90 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Doc {
pub version: i32,
pub content: Vec<DocNode>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[serde(tag = "type", content = "content")]
pub enum DocNode {
Paragraph(Vec<DocTextNode>),
BulletList(Vec<DocNode>),
ListItem(Vec<DocNode>),
OrderedList(Vec<DocNode>),
Rule,
Heading(Vec<DocTextNode>),
MediaSingle(Vec<DocTextMarker>),
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[serde(tag = "type")]
pub enum DocTextNode {
Text(DocTextNodeContent),
HardBreak,
InlineCard(DocInlineCard),
Mention(DocMention),
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct DocTextNodeContent {
pub text: String,
pub marks: Option<Vec<DocTextMarker>>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[serde(tag = "type", content = "attrs")]
pub enum DocTextMarker {
Link(DocTextMarkerLink),
Strong,
Code,
Media(DocMedia),
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct DocTextMarkerLink {
pub href: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[serde(tag = "type")]
pub enum DocMedia {
File(DocMediaFile),
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct DocMediaFile {
pub id: String,
pub alt: String,
pub collection: String,
pub height: i32,
pub width: i32,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct DocInlineCard {
pub attrs: DocInlineCardAttrs,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct DocInlineCardAttrs {
pub url: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct DocMention {
pub attrs: DocMentionAttrs,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DocMentionAttrs {
pub id: String,
pub text: String,
pub local_id: String,
}

View file

@ -1,3 +1,4 @@
use crate::entities::doc::Doc;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize)]
@ -11,7 +12,7 @@ pub struct JiraIssue {
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct JiraIssueResponseFields {
pub summary: String,
pub description: Option<String>,
pub description: Option<Description>,
pub status: Status,
pub created: chrono::DateTime<chrono::Utc>,
pub priority: Priority,
@ -24,6 +25,13 @@ pub struct JiraIssueResponseFields {
pub votes: Votes,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[serde(tag = "type")]
pub enum Description {
Doc(Doc),
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Status {
pub name: String,
@ -63,7 +71,7 @@ pub struct Comment {
pub href: String,
pub id: String,
pub author: Person,
pub body: String,
pub body: Doc,
#[serde(rename = "updateAuthor")]
pub update_author: Person,
pub created: chrono::DateTime<chrono::Utc>,