import moment from 'moment/min/moment-with-locales' ;
import { TAPi18n } from '/imports/i18n' ;
import {
OPERATOR _ASSIGNEE ,
OPERATOR _BOARD ,
OPERATOR _COMMENT ,
OPERATOR _CREATED _AT ,
OPERATOR _CREATOR ,
OPERATOR _DEBUG ,
OPERATOR _DUE ,
OPERATOR _HAS ,
OPERATOR _LABEL ,
OPERATOR _LIMIT ,
OPERATOR _LIST ,
OPERATOR _MEMBER ,
OPERATOR _MODIFIED _AT ,
OPERATOR _ORG ,
OPERATOR _SORT ,
OPERATOR _STATUS ,
OPERATOR _SWIMLANE ,
OPERATOR _TEAM ,
OPERATOR _UNKNOWN ,
OPERATOR _USER ,
ORDER _ASCENDING ,
ORDER _DESCENDING ,
PREDICATE _ALL ,
PREDICATE _ARCHIVED ,
PREDICATE _ASSIGNEES ,
PREDICATE _ATTACHMENT ,
PREDICATE _CHECKLIST ,
PREDICATE _CREATED _AT ,
PREDICATE _DESCRIPTION ,
PREDICATE _DUE _AT ,
PREDICATE _END _AT ,
PREDICATE _ENDED ,
PREDICATE _MEMBERS ,
PREDICATE _MODIFIED _AT ,
PREDICATE _MONTH ,
PREDICATE _OPEN ,
PREDICATE _OVERDUE ,
PREDICATE _PRIVATE ,
PREDICATE _PROJECTION ,
PREDICATE _PUBLIC ,
PREDICATE _QUARTER ,
PREDICATE _SELECTOR ,
PREDICATE _START _AT ,
PREDICATE _WEEK ,
PREDICATE _YEAR ,
} from './search-const' ;
import Boards from '../models/boards' ;
export class QueryDebug {
predicate = null ;
constructor ( predicate ) {
if ( predicate ) {
this . set ( predicate )
}
}
get ( ) {
return this . predicate ;
}
set ( predicate ) {
if ( [ PREDICATE _ALL , PREDICATE _SELECTOR , PREDICATE _PROJECTION ] . includes (
predicate
) ) {
this . predicate = predicate ;
} else {
this . predicate = null ;
}
}
show ( ) {
return ( this . predicate !== null ) ;
}
showAll ( ) {
return ( this . predicate === PREDICATE _ALL ) ;
}
showSelector ( ) {
return ( this . predicate === PREDICATE _ALL || this . predicate === PREDICATE _SELECTOR ) ;
}
showProjection ( ) {
return ( this . predicate === PREDICATE _ALL || this . predicate === PREDICATE _PROJECTION ) ;
}
}
export class QueryParams {
text = '' ;
constructor ( params = { } , text = '' ) {
this . params = params ;
this . text = text ;
}
hasOperator ( operator ) {
return (
this . params [ operator ] !== undefined &&
( this . params [ operator ] . length === undefined ||
this . params [ operator ] . length > 0 )
) ;
}
addPredicate ( operator , predicate ) {
if ( ! this . hasOperator ( operator ) ) {
this . params [ operator ] = [ ] ;
}
this . params [ operator ] . push ( predicate ) ;
}
setPredicate ( operator , predicate ) {
this . params [ operator ] = predicate ;
}
getPredicate ( operator ) {
if ( this . hasOperator ( operator ) ) {
if ( typeof this . params [ operator ] === 'object' ) {
return this . params [ operator ] [ 0 ] ;
} else {
return this . params [ operator ] ;
}
}
return null ;
}
getPredicates ( operator ) {
return this . params [ operator ] ;
}
getParams ( ) {
return this . params ;
}
}
export class QueryErrors {
operatorTagMap = [
[ OPERATOR _BOARD , 'board-title-not-found' ] ,
[ OPERATOR _SWIMLANE , 'swimlane-title-not-found' ] ,
[
OPERATOR _LABEL ,
label => {
if ( Boards . labelColors ( ) . includes ( label ) ) {
return {
tag : 'label-color-not-found' ,
value : label ,
color : true ,
} ;
} else {
return {
tag : 'label-not-found' ,
value : label ,
color : false ,
} ;
}
} ,
] ,
[ OPERATOR _LIST , 'list-title-not-found' ] ,
[ OPERATOR _COMMENT , 'comment-not-found' ] ,
[ OPERATOR _USER , 'user-username-not-found' ] ,
[ OPERATOR _ASSIGNEE , 'user-username-not-found' ] ,
[ OPERATOR _MEMBER , 'user-username-not-found' ] ,
[ OPERATOR _CREATOR , 'user-username-not-found' ] ,
[ OPERATOR _ORG , 'org-name-not-found' ] ,
[ OPERATOR _TEAM , 'team-name-not-found' ] ,
] ;
constructor ( ) {
this . _errors = { } ;
this . operatorTags = { } ;
this . operatorTagMap . forEach ( ( [ operator , tag ] ) => {
this . operatorTags [ operator ] = tag ;
} ) ;
this . colorMap = Boards . colorMap ( ) ;
}
addError ( operator , error ) {
if ( ! this . _errors [ operator ] ) {
this . _errors [ operator ] = [ ] ;
}
this . _errors [ operator ] . push ( error ) ;
}
addNotFound ( operator , value ) {
if ( typeof this . operatorTags [ operator ] === 'function' ) {
this . addError ( operator , this . operatorTags [ operator ] ( value ) ) ;
} else {
this . addError ( operator , { tag : this . operatorTags [ operator ] , value } ) ;
}
}
hasErrors ( ) {
return Object . entries ( this . _errors ) . length > 0 ;
}
errors ( ) {
const errs = [ ] ;
// eslint-disable-next-line no-unused-vars
Object . entries ( this . _errors ) . forEach ( ( [ , errors ] ) => {
errors . forEach ( err => {
errs . push ( err ) ;
} ) ;
} ) ;
return errs ;
}
errorMessages ( ) {
const messages = [ ] ;
// eslint-disable-next-line no-unused-vars
Object . entries ( this . _errors ) . forEach ( ( [ , errors ] ) => {
errors . forEach ( err => {
messages . push ( TAPi18n . _ _ ( err . tag , err . value ) ) ;
} ) ;
} ) ;
return messages ;
}
}
export class Query {
selector = { } ;
projection = { } ;
constructor ( selector , projection ) {
this . _errors = new QueryErrors ( ) ;
this . queryParams = new QueryParams ( ) ;
this . colorMap = Boards . colorMap ( ) ;
if ( selector ) {
this . selector = selector ;
}
if ( projection ) {
this . projection = projection ;
}
}
hasErrors ( ) {
return this . _errors . hasErrors ( ) ;
}
errors ( ) {
return this . _errors . errors ( ) ;
}
addError ( operator , error ) {
this . _errors . addError ( operator , error )
}
errorMessages ( ) {
return this . _errors . errorMessages ( ) ;
}
getQueryParams ( ) {
return this . queryParams ;
}
setQueryParams ( queryParams ) {
this . queryParams = queryParams ;
}
addPredicate ( operator , predicate ) {
this . queryParams . addPredicate ( operator , predicate ) ;
}
buildParams ( queryText ) {
this . queryParams = new QueryParams ( ) ;
queryText = queryText . trim ( ) ;
// eslint-disable-next-line no-console
//console.log('query:', query);
if ( ! queryText ) {
return ;
}
const reOperator1 = new RegExp (
'^((?<operator>[\\p{Letter}\\p{Mark}]+):|(?<abbrev>[#@]))(?<value>[\\p{Letter}\\p{Mark}]+)(\\s+|$)' ,
'iu' ,
) ;
const reOperator2 = new RegExp (
'^((?<operator>[\\p{Letter}\\p{Mark}]+):|(?<abbrev>[#@]))(?<quote>["\']*)(?<value>.*?)\\k<quote>(\\s+|$)' ,
'iu' ,
) ;
const reText = new RegExp ( '^(?<text>\\S+)(\\s+|$)' , 'u' ) ;
const reQuotedText = new RegExp (
'^(?<quote>["\'])(?<text>.*?)\\k<quote>(\\s+|$)' ,
'u' ,
) ;
const reNegatedOperator = new RegExp ( '^-(?<operator>.*)$' ) ;
const operators = {
'operator-board' : OPERATOR _BOARD ,
'operator-board-abbrev' : OPERATOR _BOARD ,
'operator-swimlane' : OPERATOR _SWIMLANE ,
'operator-swimlane-abbrev' : OPERATOR _SWIMLANE ,
'operator-list' : OPERATOR _LIST ,
'operator-list-abbrev' : OPERATOR _LIST ,
'operator-label' : OPERATOR _LABEL ,
'operator-label-abbrev' : OPERATOR _LABEL ,
'operator-user' : OPERATOR _USER ,
'operator-user-abbrev' : OPERATOR _USER ,
'operator-member' : OPERATOR _MEMBER ,
'operator-member-abbrev' : OPERATOR _MEMBER ,
'operator-assignee' : OPERATOR _ASSIGNEE ,
'operator-creator' : OPERATOR _CREATOR ,
'operator-assignee-abbrev' : OPERATOR _ASSIGNEE ,
'operator-status' : OPERATOR _STATUS ,
'operator-due' : OPERATOR _DUE ,
'operator-created' : OPERATOR _CREATED _AT ,
'operator-modified' : OPERATOR _MODIFIED _AT ,
'operator-comment' : OPERATOR _COMMENT ,
'operator-has' : OPERATOR _HAS ,
'operator-sort' : OPERATOR _SORT ,
'operator-limit' : OPERATOR _LIMIT ,
'operator-debug' : OPERATOR _DEBUG ,
'operator-org' : OPERATOR _ORG ,
'operator-team' : OPERATOR _TEAM ,
} ;
const predicates = {
durations : {
'predicate-week' : PREDICATE _WEEK ,
'predicate-month' : PREDICATE _MONTH ,
'predicate-quarter' : PREDICATE _QUARTER ,
'predicate-year' : PREDICATE _YEAR ,
} ,
} ;
predicates [ OPERATOR _DUE ] = {
'predicate-overdue' : PREDICATE _OVERDUE ,
} ;
predicates [ OPERATOR _STATUS ] = {
'predicate-archived' : PREDICATE _ARCHIVED ,
'predicate-all' : PREDICATE _ALL ,
'predicate-open' : PREDICATE _OPEN ,
'predicate-ended' : PREDICATE _ENDED ,
'predicate-public' : PREDICATE _PUBLIC ,
'predicate-private' : PREDICATE _PRIVATE ,
} ;
predicates [ OPERATOR _SORT ] = {
'predicate-due' : PREDICATE _DUE _AT ,
'predicate-created' : PREDICATE _CREATED _AT ,
'predicate-modified' : PREDICATE _MODIFIED _AT ,
} ;
predicates [ OPERATOR _HAS ] = {
'predicate-description' : PREDICATE _DESCRIPTION ,
'predicate-checklist' : PREDICATE _CHECKLIST ,
'predicate-attachment' : PREDICATE _ATTACHMENT ,
'predicate-start' : PREDICATE _START _AT ,
'predicate-end' : PREDICATE _END _AT ,
'predicate-due' : PREDICATE _DUE _AT ,
'predicate-assignee' : PREDICATE _ASSIGNEES ,
'predicate-member' : PREDICATE _MEMBERS ,
} ;
predicates [ OPERATOR _DEBUG ] = {
'predicate-all' : PREDICATE _ALL ,
'predicate-selector' : PREDICATE _SELECTOR ,
'predicate-projection' : PREDICATE _PROJECTION ,
} ;
const predicateTranslations = { } ;
Object . entries ( predicates ) . forEach ( ( [ category , catPreds ] ) => {
predicateTranslations [ category ] = { } ;
Object . entries ( catPreds ) . forEach ( ( [ tag , value ] ) => {
predicateTranslations [ category ] [ TAPi18n . _ _ ( tag ) ] = value ;
} ) ;
} ) ;
// eslint-disable-next-line no-console
// console.log('predicateTranslations:', predicateTranslations);
const operatorMap = { } ;
Object . entries ( operators ) . forEach ( ( [ key , value ] ) => {
operatorMap [ TAPi18n . _ _ ( key ) . toLowerCase ( ) ] = value ;
} ) ;
// eslint-disable-next-line no-console
// console.log('operatorMap:', operatorMap);
let text = '' ;
while ( queryText ) {
let m = queryText . match ( reOperator1 ) ;
if ( ! m ) {
m = queryText . match ( reOperator2 ) ;
if ( m ) {
queryText = queryText . replace ( reOperator2 , '' ) ;
}
} else {
queryText = queryText . replace ( reOperator1 , '' ) ;
}
if ( m ) {
let op ;
if ( m . groups . operator ) {
op = m . groups . operator . toLowerCase ( ) ;
} else {
op = m . groups . abbrev . toLowerCase ( ) ;
}
// eslint-disable-next-line no-prototype-builtins
if ( operatorMap . hasOwnProperty ( op ) ) {
const operator = operatorMap [ op ] ;
let value = m . groups . value ;
if ( operator === OPERATOR _LABEL ) {
if ( value in this . colorMap ) {
value = this . colorMap [ value ] ;
// console.log('found color:', value);
}
} else if (
[ OPERATOR _DUE , OPERATOR _CREATED _AT , OPERATOR _MODIFIED _AT ] . includes (
operator ,
)
) {
const days = parseInt ( value , 10 ) ;
let duration = null ;
if ( isNaN ( days ) ) {
// duration was specified as text
if ( predicateTranslations . durations [ value ] ) {
duration = predicateTranslations . durations [ value ] ;
let date = null ;
switch ( duration ) {
case PREDICATE _WEEK :
// eslint-disable-next-line no-case-declarations
const week = moment ( ) . week ( ) ;
if ( week === 52 ) {
date = moment ( 1 , 'W' ) ;
date . set ( 'year' , date . year ( ) + 1 ) ;
} else {
date = moment ( week + 1 , 'W' ) ;
}
break ;
case PREDICATE _MONTH :
// eslint-disable-next-line no-case-declarations
const month = moment ( ) . month ( ) ;
// .month() is zero indexed
if ( month === 11 ) {
date = moment ( 1 , 'M' ) ;
date . set ( 'year' , date . year ( ) + 1 ) ;
} else {
date = moment ( month + 2 , 'M' ) ;
}
break ;
case PREDICATE _QUARTER :
// eslint-disable-next-line no-case-declarations
const quarter = moment ( ) . quarter ( ) ;
if ( quarter === 4 ) {
date = moment ( 1 , 'Q' ) ;
date . set ( 'year' , date . year ( ) + 1 ) ;
} else {
date = moment ( quarter + 1 , 'Q' ) ;
}
break ;
case PREDICATE _YEAR :
date = moment ( moment ( ) . year ( ) + 1 , 'YYYY' ) ;
break ;
}
if ( date ) {
value = {
operator : '$lt' ,
value : date . format ( 'YYYY-MM-DD' ) ,
} ;
}
} else if (
operator === OPERATOR _DUE &&
value === PREDICATE _OVERDUE
) {
value = {
operator : '$lt' ,
value : moment ( ) . format ( 'YYYY-MM-DD' ) ,
} ;
} else {
this . addError ( OPERATOR _DUE , {
tag : 'operator-number-expected' ,
value : { operator : op , value } ,
} ) ;
continue ;
}
} else if ( operator === OPERATOR _DUE ) {
value = {
operator : '$lt' ,
value : moment ( moment ( ) . format ( 'YYYY-MM-DD' ) )
. add ( days + 1 , duration ? duration : 'days' )
. format ( ) ,
} ;
} else {
value = {
operator : '$gte' ,
value : moment ( moment ( ) . format ( 'YYYY-MM-DD' ) )
. subtract ( days , duration ? duration : 'days' )
. format ( ) ,
} ;
}
} else if ( operator === OPERATOR _SORT ) {
let negated = false ;
const m = value . match ( reNegatedOperator ) ;
if ( m ) {
value = m . groups . operator ;
negated = true ;
}
if ( ! predicateTranslations [ OPERATOR _SORT ] [ value ] ) {
this . addError ( OPERATOR _SORT , {
tag : 'operator-sort-invalid' ,
value ,
} ) ;
continue ;
} else {
value = {
name : predicateTranslations [ OPERATOR _SORT ] [ value ] ,
order : negated ? ORDER _DESCENDING : ORDER _ASCENDING ,
} ;
}
} else if ( operator === OPERATOR _STATUS ) {
if ( ! predicateTranslations [ OPERATOR _STATUS ] [ value ] ) {
this . addError ( OPERATOR _STATUS , {
tag : 'operator-status-invalid' ,
value ,
} ) ;
continue ;
} else {
value = predicateTranslations [ OPERATOR _STATUS ] [ value ] ;
}
} else if ( operator === OPERATOR _HAS ) {
let negated = false ;
const m = value . match ( reNegatedOperator ) ;
if ( m ) {
value = m . groups . operator ;
negated = true ;
}
if ( ! predicateTranslations [ OPERATOR _HAS ] [ value ] ) {
this . addError ( OPERATOR _HAS , {
tag : 'operator-has-invalid' ,
value ,
} ) ;
continue ;
} else {
value = {
field : predicateTranslations [ OPERATOR _HAS ] [ value ] ,
exists : ! negated ,
} ;
}
} else if ( operator === OPERATOR _LIMIT ) {
const limit = parseInt ( value , 10 ) ;
if ( isNaN ( limit ) || limit < 1 ) {
this . addError ( OPERATOR _LIMIT , {
tag : 'operator-limit-invalid' ,
value ,
} ) ;
continue ;
} else {
value = limit ;
}
} else if ( operator === OPERATOR _DEBUG ) {
if ( ! predicateTranslations [ OPERATOR _DEBUG ] [ value ] ) {
this . addError ( OPERATOR _DEBUG , {
tag : 'operator-debug-invalid' ,
value ,
} ) ;
continue ;
} else {
value = predicateTranslations [ OPERATOR _DEBUG ] [ value ] ;
}
}
this . queryParams . addPredicate ( operator , value ) ;
} else {
this . addError ( OPERATOR _UNKNOWN , {
tag : 'operator-unknown-error' ,
value : op ,
} ) ;
}
continue ;
}
m = queryText . match ( reQuotedText ) ;
if ( ! m ) {
m = queryText . match ( reText ) ;
if ( m ) {
queryText = queryText . replace ( reText , '' ) ;
}
} else {
queryText = queryText . replace ( reQuotedText , '' ) ;
}
if ( m ) {
text += ( text ? ' ' : '' ) + m . groups . text ;
}
}
this . queryParams . text = text ;
// eslint-disable-next-line no-console
if ( this . queryParams . hasOperator ( OPERATOR _DEBUG ) ) {
// eslint-disable-next-line no-console
console . log ( 'text:' , this . queryParams . text ) ;
console . log ( 'queryParams:' , this . queryParams ) ;
}
}
}