Skip to content

Commit

Permalink
feat(term): display labels for github issues and PRs
Browse files Browse the repository at this point in the history
  • Loading branch information
ymgyt committed Jun 29, 2024
1 parent e67b050 commit 1cd28d0
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 11 deletions.
6 changes: 6 additions & 0 deletions crates/synd_term/gql/github/issue_query.gql
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ query IssueQuery($repositoryOwner: String!, $repositoryName: String!, $issueNumb
state
stateReason
bodyText
labels(first: 10, orderBy: {field: NAME, direction: ASC}) {
nodes {
color
name
}
}
comments(first: 1, orderBy: {field: UPDATED_AT, direction: DESC}) {
nodes {
bodyText
Expand Down
6 changes: 6 additions & 0 deletions crates/synd_term/gql/github/pull_request_query.gql
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ query PullRequestQuery($repositoryOwner: String!, $repositoryName: String!, $pul
__typename
login
}
labels(first: 10, orderBy: {field: NAME, direction: ASC}) {
nodes {
color
name
}
}
comments(first: 1, orderBy: {field: UPDATED_AT, direction: DESC}) {
nodes {
bodyText
Expand Down
78 changes: 75 additions & 3 deletions crates/synd_term/src/types/github.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use std::ops::Deref;
use std::{ops::Deref, str::FromStr};

use either::Either;
use octocrab::models::{self, activity::Subject};
use ratatui::{style::Stylize, text::Span};
use ratatui::{
style::{Color, Stylize},
text::Span,
};
use synd_feed::types::Category;
use url::Url;

Expand Down Expand Up @@ -378,6 +381,26 @@ impl Notification {
_ => None,
}
}

pub(crate) fn labels(&self) -> Option<impl Iterator<Item = &Label>> {
match self.subject_context {
Some(SubjectContext::Issue(ref issue)) => {
if issue.labels.is_empty() {
None
} else {
Some(issue.labels.iter())
}
}
Some(SubjectContext::PullRequest(ref pr)) => {
if pr.labels.is_empty() {
None
} else {
Some(pr.labels.iter())
}
}
_ => None,
}
}
}

#[derive(Debug, Clone)]
Expand All @@ -386,6 +409,13 @@ pub(crate) struct Comment {
pub(crate) body: String,
}

#[derive(Debug, Clone)]
pub(crate) struct Label {
pub(crate) name: String,
pub(crate) color: Option<Color>,
pub(crate) luminance: Option<f64>,
}

#[derive(Debug, Clone)]
pub(crate) enum IssueState {
Open,
Expand All @@ -408,6 +438,7 @@ pub(crate) struct IssueContext {
state_reason: Option<IssueStateReason>,
body: String,
last_comment: Option<Comment>,
labels: Vec<Label>,
}

impl From<issue_query::ResponseData> for IssueContext {
Expand All @@ -422,7 +453,6 @@ impl From<issue_query::ResponseData> for IssueContext {
.into_iter()
.filter_map(|node| node.map(|node| node.topic.name))
.collect();

let issue = repo.issue.expect("ResponseData does not have issue");
let author: Option<String> = issue.author.map(|author| author.login);
let state = match issue.state {
Expand All @@ -447,6 +477,18 @@ impl From<issue_query::ResponseData> for IssueContext {
body: node.body_text,
})
});
let labels = issue
.labels
.and_then(|labels| labels.nodes)
.unwrap_or_default()
.into_iter()
.flatten()
.map(|label| Label {
name: label.name,
color: Color::from_str(&format!("#{}", label.color)).ok(),
luminance: luminance(&label.color),
})
.collect();

Self {
author,
Expand All @@ -455,6 +497,7 @@ impl From<issue_query::ResponseData> for IssueContext {
state_reason,
body,
last_comment,
labels,
}
}
}
Expand All @@ -475,6 +518,7 @@ pub(crate) struct PullRequestContext {
is_draft: bool,
body: String,
last_comment: Option<Comment>,
labels: Vec<Label>,
}

impl From<pull_request_query::ResponseData> for PullRequestContext {
Expand Down Expand Up @@ -514,6 +558,20 @@ impl From<pull_request_query::ResponseData> for PullRequestContext {
body: node.body_text,
})
});
let labels = pr
.labels
.and_then(|labels| labels.nodes)
.unwrap_or_default()
.into_iter()
.flatten()
.map(|label| Label {
name: label.name,
color: Color::from_str(&format!("#{}", label.color)).ok(),
luminance: luminance(&label.color),
})
.collect();

tracing::debug!("{labels:?}");

Self {
author,
Expand All @@ -522,6 +580,20 @@ impl From<pull_request_query::ResponseData> for PullRequestContext {
is_draft,
body,
last_comment,
labels,
}
}
}

// Assume color is "RRGGBB" in hex format
#[allow(clippy::cast_lossless)]
fn luminance(color: &str) -> Option<f64> {
if color.len() != 6 {
return None;
}
let r = u8::from_str_radix(&color[..2], 16).ok()? as f64;
let g = u8::from_str_radix(&color[2..4], 16).ok()? as f64;
let b = u8::from_str_radix(&color[4..], 16).ok()? as f64;

Some((0.2126 * r + 0.7152 * g + 0.0722 * b) / 255.)
}
58 changes: 50 additions & 8 deletions crates/synd_term/src/ui/components/gh_notifications.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use std::{borrow::Cow, collections::HashMap};

use chrono_humanize::{Accuracy, HumanTime, Tense};
use itertools::Itertools;
use ratatui::{
buffer::Buffer,
layout::{Alignment, Constraint, Layout, Rect},
style::{Modifier, Stylize},
style::{Modifier, Style, Styled, Stylize},
text::{Line, Span},
widgets::{
Block, BorderType, Borders, Cell, Padding, Paragraph, Row, StatefulWidget, Table,
Expand Down Expand Up @@ -82,6 +83,7 @@ impl GhNotifications {
.iter()
.filter_map(Notification::context)
.collect();

self.max_repository_name = self.max_repository_name.max(
notifications
.iter()
Expand All @@ -97,13 +99,14 @@ impl GhNotifications {
}

pub(crate) fn fetch_next_if_needed(&self) -> Option<Command> {
if self.notifications.len() < self.limit && self.next_page.is_some() {
Some(Command::FetchGhNotifications {
populate: Populate::Append,
page: self.next_page.unwrap(),
})
} else {
None
match self.next_page {
Some(page) if self.notifications.len() < self.limit => {
Some(Command::FetchGhNotifications {
populate: Populate::Append,
page,
})
}
_ => None,
}
}

Expand Down Expand Up @@ -394,6 +397,45 @@ impl GhNotifications {
.alignment(Alignment::Left);
let last_comment = notification.last_comment();

// Render labels if exists
let content_area = {
let labels = notification.labels().map(|labels| {
#[allow(unstable_name_collisions)]
let labels = labels
.map(|label| {
let span = Span::from(&label.name);
if let Some(color) = label.color {
span.set_style(
Style::default().bg(color).fg(
// Depending on the background color of the label
// the foreground may become difficult to read
cx.theme
.contrast_fg_from_luminance(label.luminance.unwrap_or(0.5)),
),
)
} else {
span
}
})
.intersperse(Span::from(" "));
let mut line = vec![
Span::from(concat!(icon!(label), " Labels")).bold(),
Span::from(" "),
];
line.extend(labels);
Line::from(line)
});
if labels.is_none() {
content_area
} else {
let vertical = Layout::vertical([Constraint::Length(1), Constraint::Fill(1)]);
let [labels_area, content_area] = vertical.areas(content_area);

labels.unwrap().render(labels_area, buf);
content_area
}
};

if last_comment.is_none() {
let vertical = Layout::vertical([Constraint::Length(1), Constraint::Fill(1)]);
let [body_header_area, body_area] = vertical.areas(content_area);
Expand Down
1 change: 1 addition & 0 deletions crates/synd_term/src/ui/icon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ macro_rules! icon {
(issuereopened) => { "" };
(issuenotplanned) => { "" };
(issueclosed) => { "" };
(label) => { "󱍵" };
(requirement) => { "" };
(open) => { "󰏌" };
(pullrequest) => { "" };
Expand Down
10 changes: 10 additions & 0 deletions crates/synd_term/src/ui/theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,13 @@ impl Default for Theme {
Theme::with_palette(&Palette::ferra())
}
}

impl Theme {
pub(crate) fn contrast_fg_from_luminance(&self, luminance: f64) -> Color {
if luminance > 0.5 {
self.base.bg.unwrap_or_default()
} else {
self.base.fg.unwrap_or_default()
}
}
}

0 comments on commit 1cd28d0

Please sign in to comment.