/**
 * Copyright 2017 - 2018  The Hyve B.V.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */ import { Component , OnInit , ViewChild } from '@angular/core' ; import { GbConstraintComponent } from '../gb-constraint/gb-constraint.component' ; import { Concept } from '../../../../models/constraint-models/concept' ; import { ConceptConstraint } from '../../../../models/constraint-models/concept-constraint' ; import { GbConceptOperatorState } from './gb-concept-operator-state' ; import { ValueConstraint } from '../../../../models/constraint-models/value-constraint' ; import { TrialVisit } from '../../../../models/constraint-models/trial-visit' ; import { TrialVisitConstraint } from '../../../../models/constraint-models/trial-visit-constraint' ; import { UIHelper } from '../../../../utilities/ui-helper' ; import { DateOperatorState } from '../../../../models/constraint-models/date-operator-state' ; import { CategoricalAggregate } from '../../../../models/aggregate-models/categorical-aggregate' ; import { ConceptType } from '../../../../models/constraint-models/concept-type' ; import { Aggregate } from '../../../../models/aggregate-models/aggregate' ; import { SelectItem } from 'primeng/api' ; import { ErrorHelper } from '../../../../utilities/error-helper' ; import { HttpErrorResponse } from '@angular/common/http' ; import { FormatHelper } from '../../../../utilities/format-helper' ; import { AccessLevel } from '../../../../services/authentication/access-level' ; import { ValueType } from '../../../../models/constraint-models/value-type' ; import { Operator } from '../../../../models/constraint-models/operator' ; import { StudyConstraint } from '../../../../models/constraint-models/study-constraint' ; import { Study } from '../../../../models/constraint-models/study' ; import { GbTreeNode } from '../../../../models/tree-node-models/gb-tree-node' ; import { AutoComplete } from 'primeng' ; @ Component ( { selector : 'gb-concept-constraint' , templateUrl : './gb-concept-constraint.component.html' , styleUrls : [ './gb-concept-constraint.component.css' , '../gb-constraint/gb-constraint.component.css' ] } ) export class GbConceptConstraintComponent extends GbConstraintComponent implements OnInit { static readonly valDateOperatorSequence = { [ DateOperatorState . BETWEEN ] : DateOperatorState . AFTER , [ DateOperatorState . AFTER ] : DateOperatorState . BEFORE , [ DateOperatorState . BEFORE ] : DateOperatorState . NOT_BETWEEN , [ DateOperatorState . NOT_BETWEEN ] : DateOperatorState . BETWEEN } ; static readonly obsDateOperatorSequence = { [ DateOperatorState . BETWEEN ] : DateOperatorState . AFTER , [ DateOperatorState . AFTER ] : DateOperatorState . BEFORE , [ DateOperatorState . BEFORE ] : DateOperatorState . NOT_BETWEEN , [ DateOperatorState . NOT_BETWEEN ] : DateOperatorState . BETWEEN } ; @ ViewChild ( 'autoComplete' ) autoComplete : AutoComplete ; @ ViewChild ( 'categoricalAutoComplete' ) categoricalAutoComplete : AutoComplete ; @ ViewChild ( 'trialVisitAutoComplete' ) trialVisitAutoComplete : AutoComplete ; searchResults : Concept [ ] ; operatorState : GbConceptOperatorState ; isMinEqual : boolean ; isMaxEqual : boolean ; equalVal : number ; minVal : number ; maxVal : number ; minLimit : number ; maxLimit : number ; private _valDateOperatorState : DateOperatorState = DateOperatorState . BETWEEN ; public ValDateOperatorStateEnum = DateOperatorState ; private _valDate1 : Date ; private _valDate2 : Date ; selectedCategories : string [ ] ; suggestedCategories : SelectItem [ ] ; showMoreOptions = false ; private _applyObsDateConstraint = false ; private _obsDateOperatorState : DateOperatorState = DateOperatorState . BETWEEN ; public ObsDateOperatorStateEnum = DateOperatorState ; private _obsDate1 : Date ; private _obsDate2 : Date ; private _applyTrialVisitConstraint = false ; private _allTrialVisits : TrialVisit [ ] ; private _selectedTrialVisits : TrialVisit [ ] ; private _suggestedTrialVisits : TrialVisit [ ] ; private _applyStudyConstraint = false ; ngOnInit ( ) { this . initializeConstraints ( ) . then ( ) ; } initializeConstraints ( ) { return new Promise < any > ( ( resolve , reject ) => { this . isMinEqual = true ; this . isMaxEqual = true ; this . operatorState = GbConceptOperatorState . BETWEEN ; this . selectedCategories = [ ] ; this . suggestedCategories = [ ] ; this . _obsDateOperatorState = DateOperatorState . BETWEEN ; let constraint = < ConceptConstraint > this . constraint ; if ( constraint . concept ) { let conceptOnlyConstraint = new ConceptConstraint ( ) ; conceptOnlyConstraint . concept = constraint . concept ; if ( this . isCategorical ( ) ) { this . resourceService . getCategoricalAggregate ( conceptOnlyConstraint ) . subscribe ( ( responseAggregate ) => { this . handleCategoricalAggregate ( responseAggregate ) ; resolve ( true ) ; } , ( err ) => { ErrorHelper . handleError ( err ) ; reject ( err . message ) ; } ) ; } else { this . authService . accessLevel . asObservable ( ) . subscribe ( ( level ) => { if ( level === AccessLevel . Full ) { this . resourceService . getAggregate ( conceptOnlyConstraint ) . subscribe ( ( responseAggregate ) => { if ( this . isNumeric ( ) ) { this . handleNumericAggregate ( responseAggregate ) ; } else if ( this . isDate ( ) ) { this . handleDateAggregate ( responseAggregate ) ; } resolve ( true ) ; } , ( err ) => { ErrorHelper . handleError ( err ) ; reject ( err . message ) ; } ) ; } } ) ; } this . applyObsDateConstraint = constraint . applyObsDateConstraint ; let date1 = constraint . obsDateConstraint . date1 ; this . obsDate1 = new Date ( date1 . getTime ( ) + 60000 * date1 . getTimezoneOffset ( ) ) ; let date2 = constraint . obsDateConstraint . date2 ; this . obsDate2 = new Date ( date2 . getTime ( ) + 60000 * date2 . getTimezoneOffset ( ) ) ; this . obsDateOperatorState = constraint . obsDateConstraint . dateOperator ; this . applyTrialVisitConstraint = constraint . applyTrialVisitConstraint ; this . allTrialVisits = [ ] ; this . selectedTrialVisits = [ ] ; this . suggestedTrialVisits = [ ] ; this . studyService . existsTrialVisitDimension . subscribe ( existsTrialVisitDimension => { if ( existsTrialVisitDimension ) { this . resourceService . getTrialVisits ( conceptOnlyConstraint ) . subscribe ( visits => { this . handleTrialVisits ( visits ) ; } , err => ErrorHelper . handleError ( err ) ) ; } } ) ; this . applyStudyConstraint = ( < ConceptConstraint > this . constraint ) . applyStudyConstraint ; this . showMoreOptions = this . applyObsDateConstraint || this . applyTrialVisitConstraint || this . applyStudyConstraint ; } } ) ; } handleNumericAggregate ( responseAggregate ) { let constraint = < ConceptConstraint > this . constraint ; constraint . concept . aggregate = responseAggregate ; this . minLimit = responseAggregate [ 'min' ] ; this . maxLimit = responseAggregate [ 'max' ] ; if ( constraint . valueConstraints . length > 0 ) { for ( let val of constraint . valueConstraints ) { if ( val . operator . includes ( '>' ) ) { this . minVal = val . value ; } else if ( val . operator . includes ( '<' ) ) { this . maxVal = val . value ; } else if ( val . operator === '=' ) { this . equalVal = val . value ; this . operatorState = GbConceptOperatorState . EQUAL ; } } } } handleCategoricalAggregate ( responseAggregate ) { let constraint = < ConceptConstraint > this . constraint ; constraint . concept . aggregate = responseAggregate ; let suggestedValues = ( < CategoricalAggregate > constraint . concept . aggregate ) . values ; let selectedValues = suggestedValues ; let valueCounts = ( < CategoricalAggregate > constraint . concept . aggregate ) . valueCounts ; if ( constraint . valueConstraints . length > 0 ) { selectedValues = [ ] ; for ( let val of constraint . valueConstraints ) { selectedValues . push ( val . value ) ; } } this . suggestedCategories = this . generateCategoricalValueItems ( valueCounts , suggestedValues ) ; this . selectedCategories = selectedValues ; this . onCategoricalValuePanelHide ( ) ; } handleDateAggregate ( responseAggregate ) { let constraint = < ConceptConstraint > this . constraint ; constraint . concept . aggregate = responseAggregate ; let date1 = constraint . valDateConstraint . date1 ; let date2 = constraint . valDateConstraint . date2 ; if ( Math . abs ( date1 . getTime ( ) - date2 . getTime ( ) ) < 1000 ) { this . valDate1 = new Date ( responseAggregate [ 'min' ] ) ; this . valDate2 = new Date ( responseAggregate [ 'max' ] ) ; } else { this . valDate1 = new Date ( date1 . getTime ( ) + 60000 * date1 . getTimezoneOffset ( ) ) ; this . valDate2 = new Date ( date2 . getTime ( ) + 60000 * date2 . getTimezoneOffset ( ) ) ; } this . valDateOperatorState = constraint . valDateConstraint . dateOperator ; } handleTrialVisits ( visits ) { let constraint = < ConceptConstraint > this . constraint ; this . allTrialVisits = visits ; this . selectedTrialVisits = visits . slice ( 0 ) ; constraint . trialVisitConstraint . trialVisits = visits . slice ( 0 ) ; } generateCategoricalValueItems ( valueCounts , targetValues ) { let items = [ ] ; targetValues . forEach ( ( target ) => { if ( valueCounts . has ( target ) ) { const count = FormatHelper . formatCountNumber ( valueCounts . get ( target ) ) ; items . push ( { label : target + ' (' + count + ')' , value : target } ) ; } } ) ; return items ; } get selectedConcept ( ) : Concept { return ( < ConceptConstraint > this . constraint ) . concept ; } set selectedConcept ( value ) { if ( value instanceof Concept ) { ( < ConceptConstraint > this . constraint ) . concept = value ; this . initializeConstraints ( ) ; this . update ( ) ; } } get applyObsDateConstraint ( ) : boolean { return this . _applyObsDateConstraint ; } set applyObsDateConstraint ( value ) { this . _applyObsDateConstraint = value ; let conceptConstraint = < ConceptConstraint > this . constraint ; conceptConstraint . applyObsDateConstraint = this . _applyObsDateConstraint ; if ( conceptConstraint . applyObsDateConstraint ) { this . update ( ) ; } } get obsDate1 ( ) : Date { return this . _obsDate1 ; } set obsDate1 ( value ) { if ( ! value ) { return ; } this . _obsDate1 = value ; } get obsDate2 ( ) : Date { return this . _obsDate2 ; } set obsDate2 ( value ) { if ( ! value ) { return ; } this . _obsDate2 = value ; } get obsDateOperatorState ( ) : DateOperatorState { return this . _obsDateOperatorState ; } set obsDateOperatorState ( value ) { this . _obsDateOperatorState = value ; } get applyTrialVisitConstraint ( ) : boolean { return this . _applyTrialVisitConstraint ; } set applyTrialVisitConstraint ( value ) { this . _applyTrialVisitConstraint = value ; let conceptConstraint = < ConceptConstraint > this . constraint ; conceptConstraint . applyTrialVisitConstraint = this . applyTrialVisitConstraint ; if ( conceptConstraint . applyTrialVisitConstraint ) { this . update ( ) ; } } get studyConstraint ( ) { return ( < ConceptConstraint > this . constraint ) . studyConstraint ; } get applyStudyConstraint ( ) : boolean { return this . _applyStudyConstraint ; } set applyStudyConstraint ( value ) { this . _applyStudyConstraint = value ; let conceptConstraint = < ConceptConstraint > this . constraint ; conceptConstraint . applyStudyConstraint = this . applyStudyConstraint ; if ( conceptConstraint . applyStudyConstraint ) { this . update ( ) ; } } get allTrialVisits ( ) : TrialVisit [ ] { return this . _allTrialVisits ; } set allTrialVisits ( value ) { this . _allTrialVisits = value ; } get selectedTrialVisits ( ) : TrialVisit [ ] { return this . _selectedTrialVisits ; } set selectedTrialVisits ( value ) { this . _selectedTrialVisits = value ; } get suggestedTrialVisits ( ) : TrialVisit [ ] { return this . _suggestedTrialVisits ; } set suggestedTrialVisits ( value ) { this . _suggestedTrialVisits = value ; } get valDate1 ( ) : Date { return this . _valDate1 ; } set valDate1 ( value ) { this . _valDate1 = value ; } get valDate2 ( ) : Date { return this . _valDate2 ; } set valDate2 ( value ) { this . _valDate2 = value ; } get valDateOperatorState ( ) : DateOperatorState { return this . _valDateOperatorState ; } set valDateOperatorState ( value ) { this . _valDateOperatorState = value ; } param event onSearch ( event ) { let query = event . query . toLowerCase ( ) ; let concepts = this . constraintService . concepts ; if ( query ) { this . searchResults = concepts . filter ( ( concept ) => concept . fullName && concept . fullName . toLowerCase ( ) . includes ( query ) ) ; } else { this . searchResults = concepts ; } } param event onDropdown ( event ) { this . searchResults = this . constraintService . concepts . slice ( 0 ) ; UIHelper . removePrimeNgLoaderIcon ( this . element , 200 ) ; } updateConceptValues ( ) { if ( this . isNumeric ( ) ) { this . updateNumericConceptValues ( ) ; } else if ( this . isCategorical ( ) ) { this . updateCategoricalConceptValues ( ) ; } else if ( this . isDate ( ) ) { this . updateDateConceptValues ( ) ; } this . update ( ) ; } updateNumericConceptValues ( ) { let conceptConstraint = < ConceptConstraint > this . constraint ; if ( this . operatorState === GbConceptOperatorState . EQUAL ) { if ( typeof this . equalVal === 'number' ) { let newVal = new ValueConstraint ( ) ; newVal . valueType = ValueType . numeric ; newVal . operator = < Operator > '=' ; newVal . value = this . equalVal ; conceptConstraint . valueConstraints = [ ] ; conceptConstraint . valueConstraints . push ( newVal ) ; } } else if ( this . operatorState === GbConceptOperatorState . BETWEEN ) { conceptConstraint . valueConstraints = [ ] ; if ( typeof this . minVal === 'number' ) { let newMinVal = new ValueConstraint ( ) ; newMinVal . valueType = ValueType . numeric ; newMinVal . operator = < Operator > '>' ; if ( this . isMinEqual ) { newMinVal . operator = < Operator > '>=' ; } newMinVal . value = this . minVal ; conceptConstraint . valueConstraints . push ( newMinVal ) ; } if ( typeof this . maxVal === 'number' ) { let newMaxVal = new ValueConstraint ( ) ; newMaxVal . valueType = ValueType . numeric ; newMaxVal . operator = < Operator > '<' ; if ( this . isMaxEqual ) { newMaxVal . operator = < Operator > '<=' ; } newMaxVal . value = this . maxVal ; conceptConstraint . valueConstraints . push ( newMaxVal ) ; } } } updateCategoricalConceptValues ( ) { let conceptConstraint = < ConceptConstraint > this . constraint ; conceptConstraint . valueConstraints = [ ] ; for ( let category of this . selectedCategories ) { let newVal = new ValueConstraint ( ) ; newVal . valueType = ValueType . string ; newVal . operator = < Operator > '=' ; newVal . value = category ; conceptConstraint . valueConstraints . push ( newVal ) ; } } updateDateConceptValues ( ) { let conceptConstraint = < ConceptConstraint > this . constraint ; conceptConstraint . applyValDateConstraint = true ; const val1 = this . valDate1 ; if ( val1 ) { let correctedDate1 = new Date ( val1 . getTime ( ) - 60000 * val1 . getTimezoneOffset ( ) ) ; conceptConstraint . valDateConstraint . date1 = correctedDate1 ; } const val2 = this . valDate2 ; if ( val2 ) { let correctedDate2 = new Date ( val2 . getTime ( ) - 60000 * val2 . getTimezoneOffset ( ) ) ; conceptConstraint . valDateConstraint . date2 = correctedDate2 ; } } param event onTrialVisitSearch ( event ) { let query = event . query . toLowerCase ( ) . trim ( ) ; let trialVisits = this . allTrialVisits ; if ( query ) { this . suggestedTrialVisits = trialVisits . filter ( ( category ) => category . relTimeLabel . toLowerCase ( ) . includes ( query ) ) ; } else { this . suggestedTrialVisits = trialVisits ; } } onTrialVisitDropdown ( event ) { this . suggestedTrialVisits = this . allTrialVisits . slice ( 0 ) ; UIHelper . removePrimeNgLoaderIcon ( this . element , 200 ) ; } selectAllTrialVisits ( ) { this . selectedTrialVisits = this . allTrialVisits . slice ( 0 ) ; this . updateTrialVisitValues ( ) ; } clearAllTrialVisits ( ) { this . selectedTrialVisits = [ ] ; this . updateTrialVisitValues ( ) ; } updateTrialVisitValues ( ) { let trialVisitConstraint = ( < ConceptConstraint > this . constraint ) . trialVisitConstraint ; trialVisitConstraint . trialVisits = this . selectedTrialVisits . slice ( 0 ) ; this . update ( ) ; } updateObservationDateValues ( ) { let conceptConstraint = < ConceptConstraint > this . constraint ; const val1 = this . obsDate1 ; let correctedDate1 = new Date ( val1 . getTime ( ) - 60000 * val1 . getTimezoneOffset ( ) ) ; conceptConstraint . obsDateConstraint . date1 = correctedDate1 ; const val2 = this . obsDate2 ; let correctedDate2 = new Date ( val2 . getTime ( ) - 60000 * val2 . getTimezoneOffset ( ) ) ; conceptConstraint . obsDateConstraint . date2 = correctedDate2 ; this . update ( ) ; } isNumeric ( ) { let concept = ( < ConceptConstraint > this . constraint ) . concept ; if ( ! concept ) { return false ; } return concept . type === ConceptType . NUMERICAL ; } isCategorical ( ) { let concept = ( < ConceptConstraint > this . constraint ) . concept ; if ( ! concept ) { return false ; } return concept . type === ConceptType . CATEGORICAL ; } isDate ( ) { let concept = ( < ConceptConstraint > this . constraint ) . concept ; if ( ! concept ) { return false ; } return concept . type === ConceptType . DATE ; } isBetween ( ) { return this . operatorState === GbConceptOperatorState . BETWEEN ; } /**
     * Switch the operator state of the current NUMERIC constraint
     */ switchOperatorState ( ) { if ( this . isNumeric ( ) ) { this . operatorState = ( this . operatorState === GbConceptOperatorState . EQUAL ) ? ( this . operatorState = GbConceptOperatorState . BETWEEN ) : ( this . operatorState = GbConceptOperatorState . EQUAL ) ; } this . updateConceptValues ( ) ; } getOperatorButtonName ( ) { let name = '' ; if ( this . isNumeric ( ) || this . isDate ( ) ) { name = ( this . operatorState === GbConceptOperatorState . BETWEEN ) ? 'between' : 'equal to' ; } return name ; } /**
     * Switch the operator state of the observation date constraint of the current constraint
     */ switchObsDateOperatorState ( ) { this . obsDateOperatorState = GbConceptConstraintComponent . obsDateOperatorSequence [ this . obsDateOperatorState ] ; let conceptConstraint = < ConceptConstraint > this . constraint ; conceptConstraint . obsDateConstraint . dateOperator = this . obsDateOperatorState ; this . update ( ) ; } /**
     * Switch the operator state of the current DATE constraint
     */ switchValDateOperatorState ( ) { this . valDateOperatorState = GbConceptConstraintComponent . valDateOperatorSequence [ this . valDateOperatorState ] ; let conceptConstraint = < ConceptConstraint > this . constraint ; conceptConstraint . valDateConstraint . dateOperator = this . valDateOperatorState ; this . updateConceptValues ( ) ; } /**
     * Toggle the 'more options' panel
     */ toggleMoreOptions ( ) { this . showMoreOptions = ! this . showMoreOptions ; } param studies addStudies ( studies ) { const conceptConstraint = ( < ConceptConstraint > this . constraint ) ; const currentStudyIds = conceptConstraint . studyConstraint . studies . map ( study => study . id ) ; const newStudies = studies . filter ( study => ! currentStudyIds . includes ( study . id ) ) ; newStudies . forEach ( study => conceptConstraint . studyConstraint . studies . push ( study ) ) ; if ( conceptConstraint . studyConstraint . studies . length > 0 ) { conceptConstraint . applyStudyConstraint = true ; } } onDrop ( event ) { const selectedNode = this . treeNodeService . selectedTreeNode ; if ( selectedNode && selectedNode . type === 'STUDY' ) { const droppedConstraint = this . treeNodeService . generateConstraintFromTreeNode ( selectedNode ) ; if ( droppedConstraint && droppedConstraint . className === 'StudyConstraint' ) { this . addStudies ( ( < StudyConstraint > droppedConstraint ) . studies ) ; this . initializeConstraints ( ) . then ( ( ) => { this . update ( ) ; } ) ; this . treeNodeService . selectedTreeNode = null ; event . stopPropagation ( ) ; } } } /**
     * sort the suggested categorical values so that the selected ones go on top,
     * then sort alphabetically
     */ onCategoricalValuePanelHide ( ) { this . suggestedCategories . sort ( ( a , b ) => { if ( this . selectedCategories . includes ( a . value ) && this . selectedCategories . includes ( b . value ) ) { if ( a . value > b . value ) { return 1 ; } else { return - 1 ; } } else if ( this . selectedCategories . includes ( a . value ) ) { return - 1 ; } else if ( this . selectedCategories . includes ( b . value ) ) { return 1 ; } else { if ( a . value > b . value ) { return 1 ; } else { return - 1 ; } } } ) ; } }