Add simple JQL implementation

This commit is contained in:
Adrian Woźniak 2021-04-29 14:47:38 +02:00
parent f824694a05
commit 98f9204fbd
7 changed files with 452 additions and 87 deletions

View File

@ -166,6 +166,10 @@
} }
} }
} }
> .description {
padding: 5px;
}
} }
> .issuesList { > .issuesList {

View File

@ -3,7 +3,7 @@ use std::str::FromStr;
use seed::prelude::*; use seed::prelude::*;
use seed::*; use seed::*;
use crate::Msg; use crate::{resolve_page, Msg};
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct StyledLink<'l> { pub struct StyledLink<'l> {
@ -24,6 +24,9 @@ impl<'l> StyledLink<'l> {
ev.stop_propagation(); ev.stop_propagation();
if let Ok(url) = seed::Url::from_str(href.as_str()) { if let Ok(url) = seed::Url::from_str(href.as_str()) {
url.go_and_push(); url.go_and_push();
if let Some(page) = resolve_page(url) {
return Some(Msg::ChangePage(page));
}
} }
} }

View File

@ -15,6 +15,7 @@ use crate::components::styled_tooltip;
use crate::components::styled_tooltip::{TooltipVariant as StyledTooltip, TooltipVariant}; use crate::components::styled_tooltip::{TooltipVariant as StyledTooltip, TooltipVariant};
use crate::modals::DebugMsg; use crate::modals::DebugMsg;
use crate::model::{ModalType, Model, Page}; use crate::model::{ModalType, Model, Page};
use crate::pages::issues_and_filters::IssuesAndFiltersMsg;
use crate::shared::{go_to_board, go_to_login}; use crate::shared::{go_to_board, go_to_login};
use crate::ws::{flush_queue, open_socket, read_incoming, send_ws_msg}; use crate::ws::{flush_queue, open_socket, read_incoming, send_ws_msg};
@ -102,6 +103,9 @@ pub enum Msg {
ProjectToggleRecentlyUpdated, ProjectToggleRecentlyUpdated,
ProjectClearFilters, ProjectClearFilters,
// issues and filters
IssuesAndFilters(IssuesAndFiltersMsg),
// inputs // inputs
StrInputChanged(FieldId, String), StrInputChanged(FieldId, String),

View File

@ -4,17 +4,23 @@ use seed::app::Orders;
use crate::components::styled_select::StyledSelectState; use crate::components::styled_select::StyledSelectState;
use crate::{model, FieldId, IssuesAndFiltersId, Msg}; use crate::{model, FieldId, IssuesAndFiltersId, Msg};
#[derive(Debug)]
pub enum IssuesAndFiltersMsg {
RemoveFilter(usize),
}
#[derive(Debug)] #[derive(Debug)]
pub struct IssuesAndFiltersPage { pub struct IssuesAndFiltersPage {
pub visible_issues: Vec<IssueId>, pub visible_issues: Vec<IssueId>,
pub active_id: Option<IssueId>, pub active_id: Option<IssueId>,
pub current_jql_part: StyledSelectState, pub current_jql_part: StyledSelectState,
pub jql_parts: Vec<JqlPart>, pub jql: Jql,
} }
impl IssuesAndFiltersPage { impl IssuesAndFiltersPage {
pub fn new(model: &model::Model) -> Self { pub fn new(model: &model::Model) -> Self {
let visible_issues = Self::visible_issues(model.issues()); let jql = Jql::default();
let visible_issues = Self::visible_issues(model.issues(), &jql);
let active_id = model.issues().first().as_ref().map(|issue| issue.id); let active_id = model.issues().first().as_ref().map(|issue| issue.id);
Self { Self {
@ -24,12 +30,16 @@ impl IssuesAndFiltersPage {
FieldId::IssuesAndFilters(IssuesAndFiltersId::Jql), FieldId::IssuesAndFilters(IssuesAndFiltersId::Jql),
vec![], vec![],
), ),
jql_parts: vec![], jql,
} }
} }
pub fn visible_issues(issues: &[Issue]) -> Vec<IssueId> { pub fn visible_issues(issues: &[Issue], jql: &Jql) -> Vec<IssueId> {
issues.iter().map(|issue| issue.id).collect() issues
.iter()
.filter(|issue| jql.is_visible(issue))
.map(|issue| issue.id)
.collect()
} }
pub fn update(&mut self, msg: &Msg, orders: &mut impl Orders<Msg>) { pub fn update(&mut self, msg: &Msg, orders: &mut impl Orders<Msg>) {
@ -37,11 +47,181 @@ impl IssuesAndFiltersPage {
} }
} }
#[derive(Debug, Default)]
pub struct Jql {
pub parts: Vec<JqlPart>,
}
impl Jql {
pub fn current_token(&self) -> Option<JqlPartType> {
if self.parts.is_empty() {
return None;
}
// [field, op, field, keyword]
match self.len() % 4 {
0 => Some(JqlPartType::Keyword),
3 => Some(JqlPartType::Value),
2 => Some(JqlPartType::Op),
1 => Some(JqlPartType::Field),
_ => None,
}
}
pub fn op(&self) -> Option<&JqlPart> {
if self.parts.is_empty() {
return None;
}
match self.len() % 4 {
// [field, op, value, keyword]
0 => self.parts.get(self.len() - 3),
// [field, op, value]
3 => self.parts.get(self.len() - 2),
// [field, op]
2 => self.parts.last(),
// [field]
_ => None,
}
}
pub fn value(&self) -> Option<&JqlPart> {
if self.parts.is_empty() {
return None;
}
match self.len() % 4 {
// [field, op, value, keyword]
0 => self.parts.get(self.len() - 2),
// [field, op, value]
3 => self.parts.last(),
_ => None,
}
}
pub fn field(&self) -> Option<&JqlPart> {
if self.parts.is_empty() {
return None;
}
match self.len() % 4 {
// [field, op, value, keyword]
0 => self.parts.get(self.len() - 3),
// [field]
1 => self.parts.last(),
// [field, op]
2 => self.parts.get(self.len() - 2),
_ => None,
}
}
pub fn keyword(&self) -> Option<&JqlPart> {
if self.parts.is_empty() {
return None;
}
match self.len() % 4 {
// [field]
0 => self.parts.last(),
_ => None,
}
}
pub fn len(&self) -> usize {
self.parts.len()
}
pub fn push(&mut self, part: JqlPart) {
self.parts.push(part);
}
pub fn remove_from(&mut self, idx: usize) {
if self.parts.is_empty() {
return;
}
if self.len() <= 4 {
self.parts.clear();
} else {
self.parts.drain((idx - 1 - (idx % 4))..);
}
}
pub fn is_visible(&self, issue: &jirs_data::Issue) -> bool {
if self.len() < 3 {
return true;
}
let mut q = (&self.parts).iter();
while let Some(field) = q.next() {
let op = match q.next() {
None => break,
Some(op) => op,
};
let value = match q.next() {
None => break,
Some(value) => value,
};
let _keyword = q.next(); // skip keyword
match (field, op, value) {
//
(
JqlPart::Field(FieldOption::Assignee),
JqlPart::Op(OpOption::Is | OpOption::Eq),
JqlPart::Value(JqlValueOption::User(id, _)),
) if !issue.user_ids.contains(id) => return false,
(
JqlPart::Field(FieldOption::Assignee),
JqlPart::Op(OpOption::IsNot | OpOption::NotEq),
JqlPart::Value(JqlValueOption::User(id, _)),
) if issue.user_ids.contains(id) => return false,
//
(
JqlPart::Field(FieldOption::Type),
JqlPart::Op(OpOption::Is | OpOption::Eq),
JqlPart::Value(JqlValueOption::Type(t)),
) if issue.issue_type != *t => {
return false;
}
(
JqlPart::Field(FieldOption::Type),
JqlPart::Op(OpOption::IsNot | OpOption::NotEq),
JqlPart::Value(JqlValueOption::Type(t)),
) if issue.issue_type == *t => {
return false;
}
//
(
JqlPart::Field(FieldOption::Priority),
JqlPart::Op(OpOption::Is | OpOption::Eq),
JqlPart::Value(JqlValueOption::Priority(p)),
) if issue.priority != *p => {
return false;
}
(
JqlPart::Field(FieldOption::Priority),
JqlPart::Op(OpOption::IsNot | OpOption::NotEq),
JqlPart::Value(JqlValueOption::Priority(p)),
) if issue.priority == *p => {
return false;
}
_ => {}
};
}
true
}
}
#[derive(Debug)]
pub enum JqlPartType {
Field,
Op,
Value,
Keyword,
}
#[derive(Debug)] #[derive(Debug)]
pub enum JqlPart { pub enum JqlPart {
Field(FieldOption), Field(FieldOption),
Op(OpOption), Op(OpOption),
Value(JqlValueOption), Value(JqlValueOption),
Keyword(KeywordOption),
} }
impl JqlPart { impl JqlPart {
@ -50,6 +230,7 @@ impl JqlPart {
JqlPart::Field(f) => f.to_label(), JqlPart::Field(f) => f.to_label(),
JqlPart::Op(op) => op.to_label(), JqlPart::Op(op) => op.to_label(),
JqlPart::Value(v) => v.to_label(), JqlPart::Value(v) => v.to_label(),
JqlPart::Keyword(k) => k.to_label(),
} }
} }
} }
@ -58,6 +239,8 @@ impl JqlPart {
pub enum FieldOption { pub enum FieldOption {
None, None,
Assignee, Assignee,
Type,
Priority,
} }
impl FieldOption { impl FieldOption {
@ -65,6 +248,8 @@ impl FieldOption {
match self { match self {
FieldOption::None => " ", FieldOption::None => " ",
FieldOption::Assignee => "Assignee", FieldOption::Assignee => "Assignee",
FieldOption::Type => "Type",
FieldOption::Priority => "Priority",
} }
} }
@ -72,6 +257,8 @@ impl FieldOption {
match self { match self {
FieldOption::None => "none", FieldOption::None => "none",
FieldOption::Assignee => "assignee", FieldOption::Assignee => "assignee",
FieldOption::Type => "ticketType",
FieldOption::Priority => "ticketPriority",
} }
} }
@ -79,6 +266,8 @@ impl FieldOption {
match self { match self {
FieldOption::None => 0, FieldOption::None => 0,
FieldOption::Assignee => 1, FieldOption::Assignee => 1,
FieldOption::Type => 2,
FieldOption::Priority => 3,
} }
} }
} }
@ -87,6 +276,8 @@ impl From<u32> for FieldOption {
fn from(n: u32) -> Self { fn from(n: u32) -> Self {
match n { match n {
1 => FieldOption::Assignee, 1 => FieldOption::Assignee,
2 => FieldOption::Type,
3 => FieldOption::Priority,
_ => FieldOption::None, _ => FieldOption::None,
} }
} }
@ -97,6 +288,8 @@ pub enum OpOption {
None, None,
Eq, Eq,
NotEq, NotEq,
Is,
IsNot,
} }
impl OpOption { impl OpOption {
@ -105,6 +298,8 @@ impl OpOption {
OpOption::None => " ", OpOption::None => " ",
OpOption::Eq => "=", OpOption::Eq => "=",
OpOption::NotEq => "!=", OpOption::NotEq => "!=",
OpOption::Is => "IS",
OpOption::IsNot => "IS NOT",
} }
} }
@ -113,6 +308,8 @@ impl OpOption {
OpOption::None => "none", OpOption::None => "none",
OpOption::Eq => "equal", OpOption::Eq => "equal",
OpOption::NotEq => "notEqual", OpOption::NotEq => "notEqual",
OpOption::Is => "is",
OpOption::IsNot => "isNot",
} }
} }
@ -121,6 +318,8 @@ impl OpOption {
OpOption::None => 0, OpOption::None => 0,
OpOption::Eq => 1, OpOption::Eq => 1,
OpOption::NotEq => 2, OpOption::NotEq => 2,
OpOption::Is => 3,
OpOption::IsNot => 4,
} }
} }
} }
@ -130,6 +329,8 @@ impl From<u32> for OpOption {
match n { match n {
1 => OpOption::Eq, 1 => OpOption::Eq,
2 => OpOption::NotEq, 2 => OpOption::NotEq,
3 => OpOption::Is,
4 => OpOption::IsNot,
_ => OpOption::None, _ => OpOption::None,
} }
} }
@ -138,24 +339,70 @@ impl From<u32> for OpOption {
#[derive(Debug)] #[derive(Debug)]
pub enum JqlValueOption { pub enum JqlValueOption {
User(UserId, UsernameString), User(UserId, UsernameString),
Priority(jirs_data::IssuePriority),
Type(jirs_data::IssueType),
} }
impl JqlValueOption { impl JqlValueOption {
pub fn to_label(&self) -> &str { pub fn to_label(&self) -> &str {
match self { match self {
JqlValueOption::User(_id, name) => name.as_str(), JqlValueOption::User(_id, name) => name.as_str(),
JqlValueOption::Priority(p) => p.to_label(),
JqlValueOption::Type(t) => t.to_label(),
} }
} }
pub fn to_name(&self) -> &'static str { pub fn to_name(&self) -> &'static str {
match self { match self {
JqlValueOption::User(_, _) => "user", JqlValueOption::User(_, _) => "user",
JqlValueOption::Priority(_) => "priority",
JqlValueOption::Type(_) => "type",
} }
} }
pub fn to_value(&self) -> u32 { pub fn to_value(&self) -> u32 {
match self { match self {
JqlValueOption::User(id, _) => (*id) as u32, JqlValueOption::User(id, _) => (*id) as u32,
JqlValueOption::Priority(p) => (*p).into(),
JqlValueOption::Type(t) => (*t).into(),
}
}
}
#[derive(Debug)]
pub enum KeywordOption {
None,
And,
}
impl From<u32> for KeywordOption {
fn from(n: u32) -> Self {
match n {
1 => KeywordOption::And,
_ => KeywordOption::None,
}
}
}
impl KeywordOption {
pub fn to_label(&self) -> &'static str {
match self {
KeywordOption::None => " ",
KeywordOption::And => "AND",
}
}
pub fn to_name(&self) -> &'static str {
match self {
KeywordOption::None => "none",
KeywordOption::And => "and",
}
}
pub fn to_value(&self) -> u32 {
match self {
KeywordOption::None => 0,
KeywordOption::And => 1,
} }
} }
} }

View File

@ -1,12 +1,13 @@
use seed::prelude::*; use seed::prelude::*;
use super::IssuesAndFiltersMsg;
use crate::components::styled_select::StyledSelectChanged; use crate::components::styled_select::StyledSelectChanged;
use crate::model::{Model, PageContent}; use crate::model::{Model, PageContent};
use crate::pages::issues_and_filters::{FieldOption, JqlPart, JqlValueOption, OpOption}; use crate::pages::issues_and_filters::{
FieldOption, JqlPart, JqlPartType, JqlValueOption, KeywordOption, OpOption,
};
use crate::ws::board_load; use crate::ws::board_load;
use crate::{FieldId, IssuesAndFiltersId, Msg, OperationKind, Page, ResourceKind}; use crate::{match_page, FieldId, IssuesAndFiltersId, Msg, OperationKind, Page, ResourceKind};
mod jql;
pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Orders<Msg>) { pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Orders<Msg>) {
if model.user.is_none() { if model.user.is_none() {
@ -31,8 +32,14 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
crate::match_page_mut!(model, IssuesAndFilters).update(&msg, orders); crate::match_page_mut!(model, IssuesAndFilters).update(&msg, orders);
match msg { match msg {
Msg::IssuesAndFilters(IssuesAndFiltersMsg::RemoveFilter(idx)) => {
crate::match_page_mut!(model, IssuesAndFilters)
.jql
.remove_from(idx);
}
Msg::ResourceChanged(ResourceKind::Issue, OperationKind::ListLoaded, _) => { Msg::ResourceChanged(ResourceKind::Issue, OperationKind::ListLoaded, _) => {
let issues = super::IssuesAndFiltersPage::visible_issues(model.issues()); let jql = &match_page!(model, IssuesAndFilters).jql;
let issues = super::IssuesAndFiltersPage::visible_issues(model.issues(), jql);
let first_id = model.issues().first().as_ref().map(|issue| issue.id); let first_id = model.issues().first().as_ref().map(|issue| issue.id);
let page = crate::match_page_mut!(model, IssuesAndFilters); let page = crate::match_page_mut!(model, IssuesAndFilters);
if page.active_id.is_none() { if page.active_id.is_none() {
@ -52,26 +59,56 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
StyledSelectChanged::Changed(Some(n)), StyledSelectChanged::Changed(Some(n)),
) if n != 0 => { ) if n != 0 => {
let page = crate::match_page_mut!(model, IssuesAndFilters); let page = crate::match_page_mut!(model, IssuesAndFilters);
match page.jql_parts.len() % 3 { match page.jql.current_token() {
0 => { None | Some(JqlPartType::Keyword) => {
let field = FieldOption::from(n); let field = FieldOption::from(n);
page.jql_parts.push(JqlPart::Field(field)); page.jql.push(JqlPart::Field(field));
} }
1 => { Some(JqlPartType::Field) => {
let field = OpOption::from(n); let field = OpOption::from(n);
page.jql_parts.push(JqlPart::Op(field)); page.jql.push(JqlPart::Op(field));
} }
2 => { Some(JqlPartType::Op)
if matches!(
page.jql.field(),
Some(JqlPart::Field(FieldOption::Assignee))
) =>
{
let u = match model.users.get(n as usize) { let u = match model.users.get(n as usize) {
Some(u) => u, Some(u) => u,
_ => return, _ => return,
}; };
let field = JqlValueOption::User(u.id, u.name.clone()); let field = JqlValueOption::User(u.id, u.name.clone());
page.jql_parts.push(JqlPart::Value(field)); page.jql.push(JqlPart::Value(field));
}
Some(JqlPartType::Op)
if matches!(page.jql.field(), Some(JqlPart::Field(FieldOption::Type))) =>
{
page.jql
.push(JqlPart::Value(JqlValueOption::Type(n.into())));
}
Some(JqlPartType::Op)
if matches!(
page.jql.field(),
Some(JqlPart::Field(FieldOption::Priority))
) =>
{
page.jql
.push(JqlPart::Value(JqlValueOption::Priority(n.into())));
}
Some(JqlPartType::Value) => {
let field = KeywordOption::from(n);
page.jql.push(JqlPart::Keyword(field));
} }
_ => {} _ => {}
} }
page.current_jql_part.reset(); page.current_jql_part.reset();
page.current_jql_part.opened = true;
let issues = super::IssuesAndFiltersPage::visible_issues(
model.issues(),
&crate::match_page!(model, IssuesAndFilters).jql,
);
crate::match_page_mut!(model, IssuesAndFilters).visible_issues = issues;
} }
_ => {} _ => {}
} }

View File

@ -1,31 +0,0 @@
pub enum OpType {
Eq,
}
impl OpType {
pub fn to_str(&self) -> &str {
match self {
OpType::Eq => "=",
}
}
}
pub enum FieldType {
Assignee,
}
impl FieldType {
pub fn to_str(&self) -> &'static str {
match self {
FieldType::Assignee => "Assignee",
}
}
}
pub struct JqlNode {
op: OpType,
}
pub fn parse(_query: &[&str]) -> JqlNode {
JqlNode { op: OpType::Eq }
}

View File

@ -1,12 +1,15 @@
use seed::prelude::*; use seed::prelude::*;
use seed::*; use seed::*;
use super::super::IssuesAndFiltersMsg;
use crate::components::styled_button::ButtonVariant; use crate::components::styled_button::ButtonVariant;
use crate::components::styled_icon::{Icon, StyledIcon}; use crate::components::styled_icon::{Icon, StyledIcon};
use crate::components::styled_select::{SelectVariant, StyledSelect}; use crate::components::styled_select::{SelectVariant, StyledSelect};
use crate::components::styled_select_child::StyledSelectOption; use crate::components::styled_select_child::StyledSelectOption;
use crate::model::Model; use crate::model::Model;
use crate::pages::issues_and_filters::{FieldOption, IssuesAndFiltersPage, JqlPart}; use crate::pages::issues_and_filters::{
FieldOption, IssuesAndFiltersPage, Jql, JqlPart, JqlPartType, KeywordOption, OpOption,
};
use crate::styled_button::StyledButton; use crate::styled_button::StyledButton;
use crate::Msg; use crate::Msg;
@ -28,10 +31,9 @@ fn pseudo_input(model: &Model, page: &IssuesAndFiltersPage) -> Node<Msg> {
.current_jql_part .current_jql_part
.values .values
.iter() .iter()
.enumerate() .map(|n| field_select_option(model, *n, &page.jql))
.map(|(idx, n)| field_select_option(model, idx, *n, &page.jql_parts))
.collect(), .collect(),
options: Some(options(model, &page.jql_parts).into_iter()), options: Some(options(model, &page.jql).into_iter()),
is_multi: false, is_multi: false,
opened: page.current_jql_part.opened, opened: page.current_jql_part.opened,
} }
@ -39,24 +41,33 @@ fn pseudo_input(model: &Model, page: &IssuesAndFiltersPage) -> Node<Msg> {
div![ div![
C!["pseudoInput"], C!["pseudoInput"],
StyledIcon::from(Icon::Search).render(), StyledIcon::from(Icon::Search).render(),
page.jql_parts page.jql
.parts
.iter() .iter()
.map(|s| s.to_label()) .map(|s| s.to_label())
.map(removable_part) .enumerate()
.map(|(idx, m)| removable_part(idx, m))
.collect::<Vec<Node<Msg>>>(), .collect::<Vec<Node<Msg>>>(),
input input
] ]
} }
fn options<'l, 'm: 'l>(model: &'m Model, parts: &[JqlPart]) -> Vec<StyledSelectOption<'l>> { fn options<'l, 'm: 'l>(model: &'m Model, jql: &Jql) -> Vec<StyledSelectOption<'l>> {
log::info!("{} {:?}", parts.len(), parts); let ty = jql.current_token();
if parts.is_empty() { match ty {
vec![field_select_option(model, 0, 1, &parts)] Some(JqlPartType::Value) => {
} else if parts.len() % 3 == 2 vec![build_keyword_select_option(1)]
&& matches!( }
parts.get(parts.len() - 2), Some(JqlPartType::Field) => {
Some(JqlPart::Field(FieldOption::Assignee)) vec![
) field_select_option(model, 1, jql),
field_select_option(model, 2, jql),
field_select_option(model, 3, jql),
field_select_option(model, 4, jql),
]
}
Some(JqlPartType::Op)
if matches!(jql.field(), Some(JqlPart::Field(FieldOption::Assignee))) =>
{ {
model model
.users .users
@ -70,29 +81,68 @@ fn options<'l, 'm: 'l>(model: &'m Model, parts: &[JqlPart]) -> Vec<StyledSelectO
variant: SelectVariant::Empty, variant: SelectVariant::Empty,
}) })
.collect() .collect()
} else { }
vec![field_select_option(model, 0, 1, &parts)] Some(JqlPartType::Op)
if matches!(jql.field(), Some(JqlPart::Field(FieldOption::Priority))) =>
{
vec![
jirs_data::IssuePriority::Lowest,
jirs_data::IssuePriority::Low,
jirs_data::IssuePriority::Medium,
jirs_data::IssuePriority::High,
jirs_data::IssuePriority::Highest,
]
.into_iter()
.map(|u| StyledSelectOption {
name: Some("priority"),
icon: None,
text: Some(u.to_label()),
value: u.into(),
class_list: "",
variant: SelectVariant::Empty,
})
.collect()
}
Some(JqlPartType::Op) if matches!(jql.field(), Some(JqlPart::Field(FieldOption::Type))) => {
vec![
jirs_data::IssueType::Task,
jirs_data::IssueType::Bug,
jirs_data::IssueType::Story,
]
.into_iter()
.map(|u| StyledSelectOption {
name: Some("type"),
icon: None,
text: Some(u.to_label()),
value: u.into(),
class_list: "",
variant: SelectVariant::Empty,
})
.collect()
}
Some(JqlPartType::Op) => {
vec![field_select_option(model, 1, jql)]
}
None | Some(JqlPartType::Keyword) => vec![
field_select_option(model, 1, jql),
field_select_option(model, 2, jql),
field_select_option(model, 3, jql),
],
} }
} }
fn field_select_option<'l, 'm: 'l>( fn field_select_option<'l, 'm: 'l>(
model: &'m Model, model: &'m Model,
idx: usize,
option_value: u32, option_value: u32,
parts: &[JqlPart], jql: &Jql,
) -> StyledSelectOption<'l> { ) -> StyledSelectOption<'l> {
if parts.is_empty() { let ty = jql.current_token();
if ty.is_none() {
return build_field_select_option(option_value); return build_field_select_option(option_value);
} }
match (parts.len(), parts.len() % 3) { match jql.current_token() {
(_, 1) => { Some(JqlPartType::Op)
return build_field_select_option(option_value); if matches!(jql.field(), Some(JqlPart::Field(FieldOption::Assignee))) =>
}
(_, 2)
if matches!(
parts.get(idx - 1),
Some(JqlPart::Field(FieldOption::Assignee))
) =>
{ {
let user = model let user = model
.users_by_id .users_by_id
@ -108,10 +158,59 @@ fn field_select_option<'l, 'm: 'l>(
variant: SelectVariant::Empty, variant: SelectVariant::Empty,
} }
} }
Some(JqlPartType::Op)
if matches!(jql.field(), Some(JqlPart::Field(FieldOption::Priority))) =>
{
let p: jirs_data::IssuePriority = option_value.into();
StyledSelectOption {
name: Some("priority"),
icon: None,
text: Some(p.to_label()),
value: p.into(),
class_list: "",
variant: SelectVariant::Empty,
}
}
Some(JqlPartType::Op) if matches!(jql.field(), Some(JqlPart::Field(FieldOption::Type))) => {
let p: jirs_data::IssueType = option_value.into();
StyledSelectOption {
name: Some("type"),
icon: None,
text: Some(p.to_label()),
value: p.into(),
class_list: "",
variant: SelectVariant::Empty,
}
}
Some(JqlPartType::Field) => build_op_select_option(option_value),
_ => build_field_select_option(option_value), _ => build_field_select_option(option_value),
} }
} }
fn build_op_select_option<'l>(option_value: u32) -> StyledSelectOption<'l> {
let v = OpOption::from(option_value);
StyledSelectOption {
name: Some(v.to_name()),
icon: None,
text: Some(v.to_label()),
value: v.to_value(),
class_list: "",
variant: SelectVariant::Empty,
}
}
fn build_keyword_select_option<'l>(option_value: u32) -> StyledSelectOption<'l> {
let v = KeywordOption::from(option_value);
StyledSelectOption {
name: Some(v.to_name()),
icon: None,
text: Some(v.to_label()),
value: v.to_value(),
class_list: "",
variant: SelectVariant::Empty,
}
}
fn build_field_select_option<'l>(option_value: u32) -> StyledSelectOption<'l> { fn build_field_select_option<'l>(option_value: u32) -> StyledSelectOption<'l> {
let v = FieldOption::from(option_value); let v = FieldOption::from(option_value);
StyledSelectOption { StyledSelectOption {
@ -124,11 +223,13 @@ fn build_field_select_option<'l>(option_value: u32) -> StyledSelectOption<'l> {
} }
} }
fn removable_part(part: &str) -> Node<Msg> { fn removable_part(idx: usize, part: &str) -> Node<Msg> {
let remove = StyledButton { let remove = StyledButton {
variant: ButtonVariant::Empty, variant: ButtonVariant::Empty,
icon: Some(StyledIcon::from(Icon::Trash).render()), icon: Some(StyledIcon::from(Icon::Trash).render()),
on_click: None, on_click: Some(mouse_ev("click", move |_| {
Msg::IssuesAndFilters(IssuesAndFiltersMsg::RemoveFilter(idx))
})),
..Default::default() ..Default::default()
} }
.render(); .render();