Add simple JQL implementation
This commit is contained in:
parent
f824694a05
commit
98f9204fbd
@ -166,6 +166,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .description {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .issuesList {
|
> .issuesList {
|
||||||
|
@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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),
|
||||||
|
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
@ -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 }
|
|
||||||
}
|
|
@ -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();
|
||||||
|
Loading…
Reference in New Issue
Block a user