Changeset 1524


Ignore:
Timestamp:
Feb 15, 2011, 3:05:23 PM (6 years ago)
Author:
robert@…
Message:

Refactored some of the querying stuff and built in 'check all' checkboxes

Location:
trunk
Files:
10 edited

Legend:

Unmodified
Added
Removed
  • trunk/grails-app/controllers/dbnp/query/AdvancedQueryController.groovy

    r1512 r1524  
    153153                }
    154154
    155                 // Determine the possible actions
     155                // Determine the possible actions and build correct urls
    156156                def actions = determineActions(s, selectedIds );
    157157
     
    478478
    479479                // First check whether a search with the same criteria is already present
    480                 def previousSearch = retrieveSearchByCriteria( s.getCriteria() );
     480                def previousSearch = retrieveSearchByCriteria( s.getCriteria(), s.searchMode );
    481481
    482482                def id
     
    499499         * @return                      Search that has this criteria, or null if no such search is found.
    500500         */
    501         protected Search retrieveSearchByCriteria( List criteria ) {
     501        protected Search retrieveSearchByCriteria( List criteria, SearchMode searchMode = SearchMode.and ) {
    502502                if( !session.queries )
    503503                        return null
     
    510510                        def value = query.value;
    511511
    512                         if( value.criteria && value.criteria.containsAll( criteria ) && criteria.containsAll( value.criteria ) ) {
     512                        if( value.searchMode == searchMode && value.criteria && value.criteria.containsAll( criteria ) && criteria.containsAll( value.criteria ) ) {
    513513                                return value;
    514514                        }
  • trunk/grails-app/views/advancedQuery/list.gsp

    r1512 r1524  
    4040                <thead>
    4141                        <tr>
    42                                 <th class="nonsortable"></th>
     42                                <th class="nonsortable"><input type="checkbox" id="checkAll" onClick="checkAllPaginated(this);" /></th>
    4343                                <th>#</th>
    4444                                <th>Type</th>
     
    5252                <g:each in="${searches}" var="search">
    5353                        <tr>
    54                                 <td><g:checkBox name="id" value="${search.id}" checked="${false}" /></td>
     54                                <td><g:checkBox name="id" value="${search.id}" checked="${false}" onClick="updateCheckAll(this);" /></td>
    5555                                <td>${search.id}</td>
    5656                                <td>${search.entity}</td>
  • trunk/grails-app/views/advancedQuery/results.gsp

    r1512 r1524  
    1818        <%
    1919                def resultFields = search.getShowableResultFields();
    20                 def extraFields = resultFields[ search.getResults()[ 0 ].id ]?.keySet();
     20                def extraFields = search.getShowableResultFieldNames(resultFields);
    2121        %>
    2222        <table id="searchresults" class="paginate">
    2323                <thead>
    2424                        <tr>
    25                                 <th class="nonsortable"></th>
     25                                <th class="nonsortable"><input type="checkbox" id="checkAll" onClick="checkAllPaginated(this);" /></th>
    2626                                <th>Type</th>
    2727                                <th>Id</th>
     
    3939                                                also http://datatables.net/examples/api/form.html and advancedQueryResults.js
    4040                                        */ %>
    41                                         <g:checkBox name="id" value="${result.id}" checked="${false}" />
     41                                        <g:checkBox name="id" value="${result.id}" checked="${false}" onClick="updateCheckAll(this);" />
    4242                                </td>                   
    4343                                <td>${search.entity}</td>
  • trunk/grails-app/views/advancedQuery/sampleresults.gsp

    r1512 r1524  
    1818        <%
    1919                def resultFields = search.getShowableResultFields();
    20                 def extraFields = resultFields[ search.getResults()[ 0 ].id ]?.keySet();
     20                def extraFields = search.getShowableResultFieldNames(resultFields);
    2121        %>
    2222        <table id="searchresults" class="paginate">
    2323                <thead>
    2424                <tr>
    25                         <th class="nonsortable"></th>                   
     25                        <th class="nonsortable"><input type="checkbox" id="checkAll" onClick="checkAllPaginated(this);" /></th>                 
    2626                        <th>Name</th>
    2727                        <th>Study</th>
     
    4040                                                also http://datatables.net/examples/api/form.html and advancedQueryResults.js
    4141                                        */ %>
    42                                         <g:checkBox name="id" value="${sampleInstance.id}" checked="${false}" />
     42                                        <g:checkBox name="id" value="${sampleInstance.id}" checked="${false}" onClick="updateCheckAll(this);" />
    4343                                </td>
    4444                                <td>${fieldValue(bean: sampleInstance, field: "name")}</td>
  • trunk/grails-app/views/advancedQuery/studyresults.gsp

    r1512 r1524  
    1818        <%
    1919                def resultFields = search.getShowableResultFields();
    20                 def extraFields = resultFields[ search.getResults()[ 0 ].id ]?.keySet();
     20                def extraFields = search.getShowableResultFieldNames(resultFields);
    2121        %>
    2222        <table id="searchresults" class="paginate">
    2323                <thead>
    2424                <tr>
    25                         <th class="nonsortable"></th>
     25                        <th class="nonsortable"><input type="checkbox" id="checkAll" onClick="checkAllPaginated(this);" /></th>
    2626                        <th>Title</th>
    2727                        <th>Code</th>
     
    4343                                                also http://datatables.net/examples/api/form.html and advancedQueryResults.js
    4444                                        */ %>
    45                                         <g:checkBox name="id" value="${studyInstance.id}" checked="${false}" />
     45                                        <g:checkBox name="id" value="${studyInstance.id}" checked="${false}" onClick="updateCheckAll(this);" />
    4646                                </td>
    4747                                <td>
  • trunk/src/groovy/dbnp/query/SampleSearch.groovy

    r1521 r1524  
    1515package dbnp.query
    1616
     17import groovy.lang.Closure;
     18
    1719import java.util.Map;
    1820
     
    2325class SampleSearch extends Search {
    2426        private static final log = LogFactory.getLog(this);
    25        
     27
    2628        public SampleSearch() {
    2729                super();
    28                                
     30
    2931                this.entity = "Sample";
    3032        }
     
    118120
    119121        /**
    120         * Searches for samples based on the given criteria. Only one of the criteria have to be satisfied and
    121         * criteria for the different entities are satisfied as follows:
    122         *
    123         *               Sample.title = 'abc'
    124         *                               Samples are returned from studies with title 'abc'
    125         *
    126         *               Subject.species = 'human'
    127         *                               Samples are returned from subjects with species = 'human'
    128         *
    129         *               Sample.name = 'sample 1'
    130         *                               Samples are returned with name = 'sample 1'
    131         *
    132         *               Event.startTime = '0s'
    133         *                               Samples are returned from subjects that have had an event with start time = '0s'
    134         *
    135         *               SamplingEvent.startTime = '0s'
    136         *                               Samples are returned that have originated from a sampling event with start time = '0s'
    137         *
    138         *               Assay.module = 'metagenomics'
    139         *                               Samples are returned that have been processed in an assay with module = metagenomics
    140         *
    141         * When searching for more than one criterion per entity, these are taken separately. Searching for
    142         *
    143         *               Subject.species = 'human'
    144         *               Subject.name = 'Jan'
    145         *
    146         *  will result in all samples from a human subject or a subject named 'Jan'. Samples from a mouse subject
    147         *  named 'Jan' or a human subject named 'Kees' will also satisfy the criteria.
    148         *
    149         */
    150    @Override
    151    void executeOr() {
    152            // We expect the sample criteria to be the most discriminative, and discard
    153            // the most samples. (e.g. by searching on sample title of sample type). For
    154            // that reason we first look through the list of studies. However, when the
    155            // user didn't enter any sample criteria, this will be an extra step, but doesn't
    156            // cost much time to process.
    157            def samples = []
    158            def allSamples = Sample.list().findAll { it.parent?.canRead( this.user ) }.toList();
    159 
    160            // If no criteria are found, return all samples
    161            if( !criteria || criteria.size() == 0 ) {
    162                    results = allSamples
    163                    return;
    164            }
    165            
    166            samples = ( samples + filterSamplesOnStudyCriteria( allSamples - samples ) ).unique();
    167            samples = ( samples + filterOnSubjectCriteria( allSamples - samples ) ).unique();
    168            samples = ( samples + filterOnSampleCriteria( allSamples - samples ) ).unique();
    169            samples = ( samples + filterOnEventCriteria( allSamples - samples ) ).unique();
    170            samples = ( samples + filterOnSamplingEventCriteria( allSamples - samples ) ).unique();
    171            samples = ( samples + filterOnAssayCriteria( allSamples - samples ) ).unique();
    172            
    173            samples = ( samples + filterOnModuleCriteria( allSamples - samples ) ).unique();
    174            
    175            // Save matches
    176            results = samples;
    177    }
    178        
     122         * Searches for samples based on the given criteria. Only one of the criteria have to be satisfied and
     123         * criteria for the different entities are satisfied as follows:
     124         *
     125         *              Sample.title = 'abc'
     126         *                              Samples are returned from studies with title 'abc'
     127         *
     128         *              Subject.species = 'human'
     129         *                              Samples are returned from subjects with species = 'human'
     130         *
     131         *              Sample.name = 'sample 1'
     132         *                              Samples are returned with name = 'sample 1'
     133         *
     134         *              Event.startTime = '0s'
     135         *                              Samples are returned from subjects that have had an event with start time = '0s'
     136         *
     137         *              SamplingEvent.startTime = '0s'
     138         *                              Samples are returned that have originated from a sampling event with start time = '0s'
     139         *
     140         *              Assay.module = 'metagenomics'
     141         *                              Samples are returned that have been processed in an assay with module = metagenomics
     142         *
     143         * When searching for more than one criterion per entity, these are taken separately. Searching for
     144         *
     145         *              Subject.species = 'human'
     146         *              Subject.name = 'Jan'
     147         *
     148         *  will result in all samples from a human subject or a subject named 'Jan'. Samples from a mouse subject
     149         *  named 'Jan' or a human subject named 'Kees' will also satisfy the criteria.
     150         *
     151         */
     152        @Override
     153        void executeOr() {
     154                // We expect the sample criteria to be the most discriminative, and discard
     155                // the most samples. (e.g. by searching on sample title of sample type). For
     156                // that reason we first look through the list of studies. However, when the
     157                // user didn't enter any sample criteria, this will be an extra step, but doesn't
     158                // cost much time to process.
     159                def samples = []
     160                def allSamples = Sample.list().findAll { it.parent?.canRead( this.user ) }.toList();
     161
     162                // If no criteria are found, return all samples
     163                if( !criteria || criteria.size() == 0 ) {
     164                        results = allSamples
     165                        return;
     166                }
     167
     168                samples = ( samples + filterOnStudyCriteria( allSamples - samples ) ).unique();
     169                samples = ( samples + filterOnSubjectCriteria( allSamples - samples ) ).unique();
     170                samples = ( samples + filterOnSampleCriteria( allSamples - samples ) ).unique();
     171                samples = ( samples + filterOnEventCriteria( allSamples - samples ) ).unique();
     172                samples = ( samples + filterOnSamplingEventCriteria( allSamples - samples ) ).unique();
     173                samples = ( samples + filterOnAssayCriteria( allSamples - samples ) ).unique();
     174
     175                samples = ( samples + filterOnModuleCriteria( allSamples - samples ) ).unique();
     176
     177                // Save matches
     178                results = samples;
     179        }
     180
     181        /**
     182         * Returns a closure for the given entitytype that determines the value for a criterion
     183         * on the given object. The closure receives two parameters: the object and a criterion.
     184         *
     185         * For example:
     186         *              For a study search, the object given is a study. How to determine the value for that study of
     187         *              the criterion field of type sample? This is done by returning the field values for all
     188         *              samples in the study
     189         *                      { study, criterion -> return study.samples?.collect { criterion.getFieldValue( it ); } }
     190         * @return
     191         */
     192        protected Closure valueCallback( String entity ) {
     193                switch( entity ) {
     194                        case "Study":
     195                                return { sample, criterion -> return criterion.getFieldValue( sample.parent ) }
     196                        case "Subject":
     197                                return { sample, criterion -> return criterion.getFieldValue( sample.parentSubject ); }
     198                        case "Sample":
     199                                return { sample, criterion -> return criterion.getFieldValue( sample ) }
     200                        case "Event":
     201                                return { sample, criterion ->
     202                                        if( !sample || !sample.parentEventGroup || !sample.parentEventGroup.events || sample.parentEventGroup.events.size() == 0 )
     203                                                return null
     204
     205                                        return criterion.getFieldValue( sample.parentEventGroup.events.toList() );
     206                                }
     207                        case "SamplingEvent":
     208                                return { sample, criterion -> return criterion.getFieldValue( sample.parentEvent ); }
     209                        case "Assay":
     210                                return { sample, criterion ->
     211                                        println "Find value for " + sample + " and " + criterion
     212                                        def sampleAssays = Assay.findByParent( sample.parent ).findAll { it.samples?.contains( sample ) };
     213                                        if( sampleAssays && sampleAssays.size() > 0 )
     214                                                return sampleAssays.collect { criterion.getFieldValue( it ) }
     215                                        else
     216                                                return null
     217                                }
     218                        default:
     219                                return super.valueCallback( entity );
     220                }
     221        }
     222
    179223        /**
    180224         * Filters the given list of studies on the study criteria
     
    185229                return filterOnTemplateEntityCriteria(studies, "Study", { study, criterion -> return criterion.getFieldValue( study ) })
    186230        }
    187        
    188         /**
    189         * Filters the given list of samples on the sample criteria
    190         * @param samples        Original list of samples
    191         * @return                       List with all samples that match the Study-criteria
    192         */
    193    protected List filterSamplesOnStudyCriteria( List samples ) {
    194            return filterOnTemplateEntityCriteria(samples, "Study", { study, criterion ->
    195                    return criterion.getFieldValue( study.parent )
    196            })
    197    }
    198 
    199 
    200         /**
    201          * Filters the given list of samples on the subject criteria
    202          * @param samples       Original list of samples
    203          * @return                      List with all samples that match the Subject-criteria
    204          */
    205         protected List filterOnSubjectCriteria( List samples ) {
    206                 return filterOnTemplateEntityCriteria(samples, "Subject", { sample, criterion ->
    207                         return criterion.getFieldValue( sample.parentSubject );
    208                 })
    209         }
    210 
    211         /**
    212          * Filters the given list of samples on the sample criteria
    213          * @param samples       Original list of samples
    214          * @return                      List with all samples that match the sample-criteria
    215          */
    216         protected List filterOnSampleCriteria( List samples ) {
    217                 return filterOnTemplateEntityCriteria(samples, "Sample", { sample, criterion ->
    218                         return criterion.getFieldValue( sample );
    219                 })
    220         }
    221 
    222         /**
    223          * Filters the given list of samples on the event criteria
    224          * @param samples       Original list of samples
    225          * @return                      List with all samples that match the event-criteria
    226          */
    227         protected List filterOnEventCriteria( List samples ) {
    228                 return filterOnTemplateEntityCriteria(samples, "Event", { sample, criterion ->
    229                         if( !sample || !sample.parentEventGroup || !sample.parentEventGroup.events || sample.parentEventGroup.events.size() == 0 )
    230                                 return null
    231 
    232                         return criterion.getFieldValue( sample.parentEventGroup.events.toList() );
    233                 })
    234         }
    235 
    236         /**
    237          * Filters the given list of samples on the sampling event criteria
    238          * @param samples       Original list of samples
    239          * @return                      List with all samples that match the event-criteria
    240          */
    241         protected List filterOnSamplingEventCriteria( List samples ) {
    242                 return filterOnTemplateEntityCriteria(samples, "SamplingEvent", { sample, criterion ->
    243                         return criterion.getFieldValue( sample.parentEvent );
    244                 })
    245         }
    246231
    247232        /**
     
    250235         * @return                      List with all samples that match the assay-criteria
    251236         */
     237        @Override
    252238        protected List filterOnAssayCriteria( List samples ) {
    253239                if( !samples?.size() )
     
    257243                // all assays that match the criteria
    258244                def criteria = getEntityCriteria( 'Assay' );
    259                
     245
    260246                if( getEntityCriteria( 'Assay' ).size() == 0 ) {
    261247                        if( searchMode == SearchMode.and )
     
    292278                        return false;
    293279                }
    294                
    295                 // Save sample data for later use
    296                 println samples
    297                 println "Find values for " + matchingSamples + " and " + criteria
    298                 saveResultFields( matchingSamples, criteria, { sample, criterion ->
    299                         println "Find value for " + sample + " and " + criterion
    300                         def sampleAssays = Assay.findByParent( sample.parent ).findAll { it.samples?.contains( sample ) };
    301                         if( sampleAssays && sampleAssays.size() > 0 )
    302                                 return sampleAssays.collect { criterion.getFieldValue( it ) }
    303                         else
    304                                 return null
    305                 });
    306        
     280
    307281                return matchingSamples;
    308282        }
  • trunk/src/groovy/dbnp/query/Search.groovy

    r1501 r1524  
    3131
    3232/**
    33 * Available boolean operators for searches
    34 * @author robert
    35 *
    36 */
     33 * Available boolean operators for searches
     34 * @author robert
     35 *
     36 */
    3737enum SearchMode {
    38    and, or
     38        and, or
    3939}
    4040
    4141class Search {
     42        /**
     43         * User that is performing this search. This has impact on the search results returned.
     44         */
    4245        public SecUser user;
     46
     47        /**
     48         * Date of execution of this search
     49         */
    4350        public Date executionDate;
    44         public int id;  // Is only used when this query is saved in session
    45 
     51
     52        /**
     53         * Public identifier of this search. Is only used when this query is saved in session
     54         */
     55        public int id;
     56
     57        /**
     58         * Human readable entity name of the entities that can be found using this search
     59         */
    4660        public String entity;
     61
     62        /**
     63         * Mode to search: OR or AND.
     64         * @see SearchMode
     65         */
    4766        public SearchMode searchMode = SearchMode.and
    48        
     67
    4968        protected List criteria;
    5069        protected List results;
    5170        protected Map resultFields = [:];
    5271
     72        /**
     73         * Returns a list of Criteria
     74         */
    5375        public List getCriteria() { return criteria; }
     76
     77        /**
     78         * Sets a new list of criteria
     79         * @param c     List with criteria objects
     80         */
    5481        public void setCriteria( List c ) { criteria = c; }
     82
     83        /**
     84         * Adds a criterion to this query
     85         * @param c     Criterion
     86         */
    5587        public void addCriterion( Criterion c ) {
    56                 if( criteria ) 
     88                if( criteria )
    5789                        criteria << c;
    5890                else
     
    6092        }
    6193
     94        /**
     95         * Retrieves the results found using this query. The result is empty is
     96         * the query has not been executed yet.
     97         */
    6298        public List getResults() { return results; }
    63         public void setResults( List r ) { results = r; }
     99
     100        /**
     101         * Returns the results found using this query, filtered by a list of ids.
     102         * @param selectedIds   List with ids of the entities you want to return.
     103         * @return      A list with only those results for which the id is in the selectedIds
     104         */
    64105        public List filterResults( List selectedIds ) {
    65106                if( !selectedIds || !results )
    66107                        return results
    67                        
     108
    68109                return results.findAll {
    69110                        selectedIds.contains( it.id )
    70111                }
    71112        }
    72        
    73        
     113
     114        /**
     115         * Returns a list of fields for the results of this query. The fields returned are those
     116         * fields that the query searched for.
     117         */
    74118        public Map getResultFields() { return resultFields; }
    75         public void setResultFields( Map r ) { resultFields = r; }
    76 
     119
     120        /**
     121         * Constructor of this search object. Sets the user field to the
     122         * currently logged in user
     123         * @see #user
     124         */
    77125        public Search() {
    78126                def ctx = ApplicationHolder.getApplication().getMainContext();
     
    85133                        this.user = null
    86134        }
    87        
     135
    88136        /**
    89137         * Returns the number of results found by this search
     
    113161        public void execute() {
    114162                this.executionDate = new Date();
    115                
     163
    116164                switch( searchMode ) {
    117165                        case SearchMode.and:
     
    122170                                break;
    123171                }
    124         }
    125        
     172
     173                // Save the value of this results for later use
     174                saveResultFields();
     175        }
     176
    126177        /**
    127178         * Executes an inclusive (AND) search based on the given criteria. Should be filled in by
     
    129180         */
    130181        public void executeAnd() {
    131                
    132         }
    133        
    134         /**
    135         * Executes an exclusive (OR) search based on the given criteria. Should be filled in by
    136         * subclasses searching for a specific entity
    137         */
    138    public void executeOr() {
    139            
    140    }
     182
     183        }
     184
     185        /**
     186         * Executes an exclusive (OR) search based on the given criteria. Should be filled in by
     187         * subclasses searching for a specific entity
     188         */
     189        public void executeOr() {
     190
     191        }
     192
     193        /************************************************************************
     194         *
     195         * These methods are used in querying and should be overridden by subclasses
     196         * in order to provide custom searching
     197         *
     198         */
     199
     200        /**
     201         * Returns a closure for the given entitytype that determines the value for a criterion
     202         * on the given object. The closure receives two parameters: the object and a criterion.
     203         *
     204         * For example: when searching for studies, the object given to the closure is a Study.
     205         * Also, when searching for samples, the object given is a Sample. When you have the criterion
     206         *
     207         *      sample.name equals 'sample 1'
     208         *
     209         * and searching for samples, it is easy to determine the value of the object for this criterion:
     210         *     
     211         *      object.getFieldValue( "name" )
     212         *
     213         *
     214         * However, when searching for samples with the criterion
     215         *
     216         *      study.title contains 'nbic'
     217         *
     218         * this determination is more complex:
     219         *
     220         *      object.parent.getFieldValue( "title" )
     221         *
     222         *
     223         * The other way around, when searching for studies with
     224         *
     225         *      sample.name equals 'sample 1'
     226         *
     227         * the value of the 'sample.name' property is a list:
     228         *
     229         *      object.samples*.getFieldValue( "name" )
     230         * 
     231         * The other search methods will handle the list and see whether any of the values
     232         * matches the criterion.
     233         *
     234         * NB. The Criterion object has a convenience method to retrieve the field value on a
     235         * specific (TemplateEntity) object: getFieldValue. This method also handles
     236         * non-existing fields and casts the value to the correct type.
     237         *
     238         * This method should be overridden by all searches
     239         *
     240         * @see Criterion.getFieldValue()
     241         *
     242         * @return      Closure having 2 parameters: object and criterion
     243         */
     244        protected Closure valueCallback( String entity ) {
     245                switch( entity ) {
     246                        case "Study":
     247                        case "Subject":
     248                        case "Sample":
     249                        case "Event":
     250                        case "SamplingEvent":
     251                        case "Assay":
     252                                return { object, criterion -> return criterion.getFieldValue( object ); }
     253                        default:
     254                                return null;
     255                }
     256        }
     257
     258        /*****************************************************
     259         *
     260         * The other methods are helper functions for the execution of queries in subclasses
     261         *
     262         *****************************************************/
    141263
    142264        /**
     
    148270                return criteria?.findAll { it.entity == entity }
    149271        }
    150        
     272
    151273        /**
    152274         * Filters a list with entities, based on the given criteria and a closure to check whether a criterion is matched
     
    167289                                return []
    168290                }
    169                
     291
    170292                return entities.findAll { entity ->
    171293                        if( searchMode == SearchMode.and ) {
     
    186308                }
    187309        }
    188        
     310
    189311        /**
    190312         * Prepares a value from a template entity for comparison, by giving it a correct type
     
    197319                if( value == null )
    198320                        return value
    199                        
     321
    200322                switch (type) {
    201323                        case TemplateFieldType.DATE:
     
    210332                                                return new RelTime( value );
    211333                                        } else if( value.toString().isNumber() ) {
    212                                                 return new RelTime( Long.parseLong( value.toString() ) ) 
     334                                                return new RelTime( Long.parseLong( value.toString() ) )
    213335                                        } else {
    214336                                                return new RelTime( value );
     
    250372                }
    251373
    252         }       
    253        
     374        }
     375
    254376        /**
    255377         * Filters the given list of studies on the study criteria
     
    261383        protected List filterOnTemplateEntityCriteria( List studies, String entityName, Closure valueCallback ) {
    262384                def criteria = getEntityCriteria( entityName );
    263                
     385
    264386                def checkCallback = { study, criterion ->
    265387                        def value = valueCallback( study, criterion );
    266                        
     388
    267389                        if( value == null ) {
    268390                                return false
     
    276398                }
    277399
    278                 // Save the value of this entity for later use
    279                 saveResultFields( studies, criteria, valueCallback );
    280                
    281400                return filterEntityList( studies, criteria, checkCallback);
    282401        }
    283402
    284403        /**
    285         * Filters the given list of entities on the module criteria
    286         * @param entities       Original list of entities. Entities should expose a giveUUID() method to give the token.
    287         * @return                       List with all entities that match the module criteria
    288         */
     404         * Filters the given list of studies on the study criteria
     405         * @param studies       Original list of studies
     406         * @return                      List with all studies that match the Study criteria
     407         */
     408        protected List filterOnStudyCriteria( List studies ) {
     409                def entity = "Study"
     410                return filterOnTemplateEntityCriteria(studies, entity, valueCallback( entity ) )
     411        }
     412
     413        /**
     414         * Filters the given list of studies on the subject criteria
     415         * @param studies       Original list of studies
     416         * @return                      List with all studies that match the Subject-criteria
     417         */
     418        protected List filterOnSubjectCriteria( List studies ) {
     419                def entity = "Subject"
     420                return filterOnTemplateEntityCriteria(studies, entity, valueCallback( entity ) )
     421        }
     422
     423        /**
     424         * Filters the given list of studies on the sample criteria
     425         * @param studies       Original list of studies
     426         * @return                      List with all studies that match the sample-criteria
     427         */
     428        protected List filterOnSampleCriteria( List studies ) {
     429                def entity = "Sample"
     430                return filterOnTemplateEntityCriteria(studies, entity, valueCallback( entity ) )
     431        }
     432
     433        /**
     434         * Filters the given list of studies on the event criteria
     435         * @param studies       Original list of studies
     436         * @return                      List with all studies that match the event-criteria
     437         */
     438        protected List filterOnEventCriteria( List studies ) {
     439                def entity = "Event"
     440                return filterOnTemplateEntityCriteria(studies, entity, valueCallback( entity ) )
     441        }
     442
     443        /**
     444         * Filters the given list of studies on the sampling event criteria
     445         * @param studies       Original list of studies
     446         * @return                      List with all studies that match the event-criteria
     447         */
     448        protected List filterOnSamplingEventCriteria( List studies ) {
     449                def entity = "SamplingEvent"
     450                return filterOnTemplateEntityCriteria(studies, entity, valueCallback( entity ) )
     451        }
     452
     453        /**
     454         * Filters the given list of studies on the assay criteria
     455         * @param studies       Original list of studies
     456         * @return                      List with all studies that match the assay-criteria
     457         */
     458        protected List filterOnAssayCriteria( List studies ) {
     459                def entity = "Assay"
     460                return filterOnTemplateEntityCriteria(studies, entity, valueCallback( entity ) )
     461        }
     462
     463        /**
     464         * Filters the given list of entities on the module criteria
     465         * @param entities      Original list of entities. Entities should expose a giveUUID() method to give the token.
     466         * @return                      List with all entities that match the module criteria
     467         */
    289468        protected List filterOnModuleCriteria( List entities ) {
    290469                // An empty list can't be filtered more than is has been now
    291470                if( !entities || entities.size() == 0 )
    292471                        return [];
    293                        
     472
    294473                // Determine the moduleCommunicationService. Because this object
    295474                // is mocked in the tests, it can't be converted to a ApplicationContext object
    296475                def ctx = ApplicationHolder.getApplication().getMainContext();
    297476                def moduleCommunicationService = ctx.getBean("moduleCommunicationService");
     477
     478                switch( searchMode ) {
     479                        case SearchMode.and:
     480                                // Loop through all modules and check whether criteria have been given
     481                                // for that module
     482                                AssayModule.list().each { module ->
     483                                        // Remove 'module' from module name
     484                                        def moduleName = module.name.replace( 'module', '' ).trim()
     485                                        def moduleCriteria = getEntityCriteria( moduleName );
    298486               
    299                 def allEntities = []
    300                 if( searchMode == SearchMode.or ) {
    301                         allEntities += entities;
    302                         entities = [];
    303                 }
     487                                        if( moduleCriteria && moduleCriteria.size() > 0 ) {
     488                                                def callUrl = moduleCriteriaUrl( module, entities, moduleCriteria );
     489                                               
     490                                                try {
     491                                                        def json = moduleCommunicationService.callModuleRestMethodJSON( module.url, callUrl );
     492                                                        Closure checkClosure = moduleCriterionClosure( json );
     493                                                        entities = filterEntityList( entities, moduleCriteria, checkClosure );
     494                                                } catch( Exception e ) {
     495                                                        log.error( "Error while retrieving data from " + module.name + ": " + e.getMessage() )
     496                                                }
     497                                        }
     498                                }
    304499               
    305                 // Loop through all modules and check whether criteria have been given
    306                 // for that module
    307                 AssayModule.list().each { module ->
    308                         // Remove 'module' from module name
    309                         def moduleName = module.name.replace( 'module', '' ).trim()
    310                         def moduleCriteria = getEntityCriteria( moduleName );
    311                        
    312                         if( moduleCriteria && moduleCriteria.size() > 0 ) {
    313                                 // Retrieve the data from the module
    314                                 def tokens = entities.collect { it.giveUUID() }.unique();
    315                                 def fields = moduleCriteria.collect { it.field }.unique();
     500                                return entities;
     501                        case SearchMode.or:
     502                                def resultingEntities = []
    316503                               
    317                                 def callUrl = module.url + '/rest/getQueryableFieldData?entity=' + this.entity
    318                                 tokens.sort().each { callUrl += "&tokens=" + it.encodeAsURL() }
    319                                 fields.sort().each { callUrl += "&fields=" + it.encodeAsURL() }
     504                                // Loop through all modules and check whether criteria have been given
     505                                // for that module
     506                                AssayModule.list().each { module ->
     507                                        // Remove 'module' from module name
     508                                        def moduleName = module.name.replace( 'module', '' ).trim()
     509                                        def moduleCriteria = getEntityCriteria( moduleName );
     510               
     511                                        if( moduleCriteria && moduleCriteria.size() > 0 ) {
     512                                                def callUrl = moduleCriteriaUrl( module, entities, moduleCriteria );
     513                                               
     514                                                try {
     515                                                        def json = moduleCommunicationService.callModuleRestMethodJSON( module.url, callUrl );
     516                                                        Closure checkClosure = moduleCriterionClosure( json );
     517                                                       
     518                                                        resultingEntities += filterEntityList( entities, moduleCriteria, checkClosure );
     519                                                        resultingEntities = resultingEntities.unique();
     520                                                       
     521                                                } catch( Exception e ) {
     522                                                        log.error( "Error while retrieving data from " + module.name + ": " + e.getMessage() )
     523                                                }
     524                                        }
     525                                }
     526               
     527                                println this.resultFields;
    320528                               
    321                                 try {
    322                                         def json = moduleCommunicationService.callModuleRestMethodJSON( module.url, callUrl );
    323 
    324                                         Closure checkClosure = { entity, criterion ->
    325                                                 // Find the value of the field in this sample. That value is still in the
    326                                                 // JSON object
    327                                                 def token = entity.giveUUID()
    328                                                 if( !json[ token ] || json[ token ][ criterion.field ] == null )
    329                                                         return false;
    330 
    331                                                 // Check whether a list or string is given
    332                                                 def value = json[ token ][ criterion.field ];
    333                                                
    334                                                 // Save the value of this entity for later use
    335                                                 saveResultField( entity.id, criterion.entity + " " + criterion.field, value )
    336 
    337                                                 if( !( value instanceof Collection ) ) {
    338                                                         value = [ value ];
    339                                                 }
    340                                                
    341                                                 // Convert numbers to a long or double in order to process them correctly
    342                                                 def values = value.collect { val ->
    343                                                         val = val.toString();
    344                                                         if( val.isLong() ) {
    345                                                                 val = Long.parseLong( val );
    346                                                         } else if( val.isDouble() ) {
    347                                                                 val = Double.parseDouble( val );
    348                                                         }
    349                                                         return val;
    350                                                 }
    351 
    352                                                 // Loop through all values and match any
    353                                                 for( val in values ) {
    354                                                         if( criterion.match( val ) )
    355                                                                 return true;
    356                                                 }
    357                                                
    358                                                 return false;
     529                                return resultingEntities;
     530                        default:
     531                                return [];
     532                }
     533        }
     534       
     535        /**
     536         * Returns a closure for determining the value of a module field
     537         * @param json
     538         * @return
     539         */
     540        protected Closure moduleCriterionClosure( def json ) {
     541                return { entity, criterion ->
     542                        // Find the value of the field in this sample. That value is still in the
     543                        // JSON object
     544                        def token = entity.giveUUID()
     545                        if( !json[ token ] || json[ token ][ criterion.field ] == null )
     546                                return false;
     547
     548                        // Check whether a list or string is given
     549                        def value = json[ token ][ criterion.field ];
     550
     551                        // Save the value of this entity for later use
     552                        saveResultField( entity.id, criterion.entity + " " + criterion.field, value )
     553
     554                        if( !( value instanceof Collection ) ) {
     555                                value = [ value ];
     556                        }
     557
     558                        // Convert numbers to a long or double in order to process them correctly
     559                        def values = value.collect { val ->
     560                                val = val.toString();
     561                                if( val.isLong() ) {
     562                                        val = Long.parseLong( val );
     563                                } else if( val.isDouble() ) {
     564                                        val = Double.parseDouble( val );
     565                                }
     566                                return val;
     567                        }
     568
     569                        // Loop through all values and match any
     570                        for( val in values ) {
     571                                if( criterion.match( val ) )
     572                                        return true;
     573                        }
     574
     575                        return false;
     576                }
     577        }
     578       
     579        protected String moduleCriteriaUrl( module, entities, moduleCriteria ) {
     580                // Retrieve the data from the module
     581                def tokens = entities.collect { it.giveUUID() }.unique();
     582                def fields = moduleCriteria.collect { it.field }.unique();
     583       
     584                def callUrl = module.url + '/rest/getQueryableFieldData?entity=' + this.entity
     585                tokens.sort().each { callUrl += "&tokens=" + it.encodeAsURL() }
     586                fields.sort().each { callUrl += "&fields=" + it.encodeAsURL() }
     587
     588                return callUrl;
     589        }
     590
     591        /*********************************************************************
     592         *
     593         * These methods are used for saving information about the search results and showing the information later on.
     594         *
     595         *********************************************************************/
     596
     597        /**
     598         * Saves data about template entities to use later on. This data is copied to a special
     599         * structure to make it compatible with data fetched from other modules.
     600         * @see #saveResultField()
     601         */
     602        protected void saveResultFields() {
     603                if( !results || !criteria )
     604                        return
     605
     606                criteria.each { criterion ->
     607                        if( criterion.field ) {
     608                                def valueCallback = valueCallback( criterion.entity );
     609                               
     610                                if( valueCallback != null ) {
     611                                        def name = criterion.entity + ' ' + criterion.field
     612       
     613                                        results.each { result ->
     614                                                saveResultField( result.id, name, valueCallback( result, criterion ) );
    359615                                        }
    360                                        
    361                                         // The data has been retrieved. Now walk through all criteria to filter the samples
    362                                         if( searchMode == SearchMode.and ) {
    363                                                 entities = filterEntityList( entities, moduleCriteria, checkClosure );
    364                                         } else if( searchMode == SearchMode.or ) {
    365                                                 entities += filterEntityList( allEntities - entities, moduleCriteria, checkClosure );
    366                                                 entities = entities.unique();
    367                                         }
    368                                                                                
    369                                 } catch( Exception e ) {
    370                                         log.error( "Error while retrieving data from " + module.name + ": " + e.getMessage() )
    371                                 }
    372                         }
    373                 }
    374                
    375                 return entities;
    376         }
    377        
     616                                }
     617                        }
     618                }
     619        }
     620
    378621        /**
    379622         * Saves data about template entities to use later on. This data is copied to a special
    380          * structure to make it compatible with data fetched from other modules. Ses also saveResultField() method
     623         * structure to make it compatible with data fetched from other modules.
    381624         * @param entities                      List of template entities to find data in
    382625         * @param criteria                      Criteria to search for
    383626         * @param valueCallback         Callback to retrieve a specific field from the entity
     627         * @see #saveResultField()
    384628         */
    385629        protected void saveResultFields( entities, criteria, valueCallback ) {
     
    392636        }
    393637
    394        
     638
    395639        /**
    396640         * Saves a specific field of an object to use later on. Especially useful when looking up data from other modules.
     
    402646                if( resultFields[ id ] == null )
    403647                        resultFields[ id ] = [:]
    404                
     648
    405649                // Handle special cases
    406650                if( value == null )
    407651                        value = "";
    408                
     652
    409653                if( value instanceof Collection ) {
    410654                        value = value.findAll { it != null }
    411655                }
    412                
     656
    413657                resultFields[ id ][ fieldName ] = value;
    414658        }
    415        
     659
    416660        /**
    417661         * Removes all data from the result field map
     
    420664                resultFields = [:]
    421665        }
    422        
     666
    423667        /**
    424668         * Returns the saved field data that could be shown on screen. This means, the data is filtered to show only data of the query results.
     
    435679        }
    436680       
     681        /**
     682         * Returns the field names that are found in the map with showable result fields
     683         *
     684         * @param fields        Map with showable result fields
     685         * @see getShowableResultFields
     686         * @return
     687         */
     688        public List getShowableResultFieldNames( fields ) {
     689                return fields.values()*.keySet().flatten().unique();
     690        }
     691
    437692        public String toString() {
    438693                return ( this.entity ? this.entity + " search" : "Search" ) + " " + this.id
  • trunk/src/groovy/dbnp/query/StudySearch.groovy

    r1501 r1524  
    1414 */
    1515package dbnp.query
     16
     17import groovy.lang.Closure;
    1618
    1719import java.util.List;
     
    144146
    145147        /**
    146          * Filters the given list of studies on the study criteria
    147          * @param studies       Original list of studies
    148          * @return                      List with all studies that match the Study criteria
    149          */
    150         protected List filterOnStudyCriteria( List studies ) {
    151                 return filterOnTemplateEntityCriteria(studies, "Study", { study, criterion -> return criterion.getFieldValue( study ) })
    152         }
    153 
    154         /**
    155          * Filters the given list of studies on the subject criteria
    156          * @param studies       Original list of studies
    157          * @return                      List with all studies that match the Subject-criteria
    158          */
    159         protected List filterOnSubjectCriteria( List studies ) {
    160                 return filterOnTemplateEntityCriteria(studies, "Subject", { study, criterion ->
    161                         return study.subjects?.collect { criterion.getFieldValue( it ); }
    162                 })
    163         }
    164 
    165         /**
    166          * Filters the given list of studies on the sample criteria
    167          * @param studies       Original list of studies
    168          * @return                      List with all studies that match the sample-criteria
    169          */
    170         protected List filterOnSampleCriteria( List studies ) {
    171                 return filterOnTemplateEntityCriteria(studies, "Sample", { study, criterion ->
    172                         return study.samples?.collect { criterion.getFieldValue( it ); }
    173                 })
    174         }
    175 
    176         /**
    177          * Filters the given list of studies on the event criteria
    178          * @param studies       Original list of studies
    179          * @return                      List with all studies that match the event-criteria
    180          */
    181         protected List filterOnEventCriteria( List studies ) {
    182                 return filterOnTemplateEntityCriteria(studies, "Event", { study, criterion ->
    183                         return study.events?.collect { criterion.getFieldValue( it ); }
    184                 })
    185         }
    186 
    187         /**
    188          * Filters the given list of studies on the sampling event criteria
    189          * @param studies       Original list of studies
    190          * @return                      List with all studies that match the event-criteria
    191          */
    192         protected List filterOnSamplingEventCriteria( List studies ) {
    193                 return filterOnTemplateEntityCriteria(studies, "SamplingEvent", { study, criterion ->
    194                         return study.samplingEvents?.collect { criterion.getFieldValue( it ); }
    195                 })
    196         }
    197 
    198         /**
    199          * Filters the given list of studies on the assay criteria
    200          * @param studies       Original list of studies
    201          * @return                      List with all studies that match the assay-criteria
    202          */
    203         protected List filterOnAssayCriteria( List studies ) {
    204                 return filterOnTemplateEntityCriteria(studies, "Assay", { study, criterion ->
    205                         return study.assays?.collect { criterion.getFieldValue( it ); }
    206                 })
    207         }
     148        * Returns a closure for the given entitytype that determines the value for a criterion
     149        * on the given object. The closure receives two parameters: the object and a criterion.
     150        *
     151        * This method should be implemented by all searches
     152        *
     153        * For example:
     154        *               For a study search, the object given is a study. How to determine the value for that study of
     155        *               the criterion field of type sample? This is done by returning the field values for all
     156        *               samples in the study
     157        *                       { study, criterion -> return study.samples?.collect { criterion.getFieldValue( it ); } }
     158        * @return
     159        */
     160   protected Closure valueCallback( String entity ) {
     161           switch( entity ) {
     162                   case "Study":
     163                           return { study, criterion -> return criterion.getFieldValue( study ) }
     164                   case "Subject":
     165                           return { study, criterion -> return study.subjects?.collect { criterion.getFieldValue( it ); } }
     166                   case "Sample":
     167                           return { study, criterion -> return study.samples?.collect { criterion.getFieldValue( it ); } }
     168                   case "Event":
     169                           return { study, criterion -> return study.events?.collect { criterion.getFieldValue( it ); } }
     170                   case "SamplingEvent":
     171                           return { study, criterion -> return study.samplingEvents?.collect { criterion.getFieldValue( it ); } }
     172                   case "Assay":
     173                           return { study, criterion -> return study.assays?.collect { criterion.getFieldValue( it ); } }
     174                   default:
     175                           return super.valueCallback( entity );
     176           }
     177   }
    208178
    209179        /**
  • trunk/web-app/css/advancedQuery.css

    r1512 r1524  
    3131.searchoptions ul#criteria li { margin-left: 0; padding-left: 0; display: inline; }
    3232
     33input.transparent {
     34        -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=30)";
     35        filter: alpha(opacity=30);
     36        opacity: 0.3;
     37        -moz-opacity: 0.3;
     38        -khtml-opacity: 0.3;
     39}
     40       
    3341/** Options buttons **/
    3442.options { margin-top: 8px; }
  • trunk/web-app/js/advancedQueryResults.js

    r1501 r1524  
     1function checkAllPaginated( input ) {
     2        var paginatedTable = $(input).closest( '.paginate' );
     3        var dataTable = paginatedTable.closest( '.dataTables_wrapper' );
     4        var checkAll = $( '#checkAll', paginatedTable );
     5       
     6        var oTable = paginatedTable.dataTable();
     7        var inputs = $('input', oTable.fnGetNodes())
     8       
     9        // If any of the inputs is checked, uncheck all. Otherwise, check all
     10        var check = false;
     11       
     12        for(var i = 0; i < inputs.length; i++ ) {
     13                if( !$(inputs[i]).attr( 'checked' ) ) {
     14                        check = true;
     15                        break;
     16                }
     17        }
     18       
     19        inputs.each( function( idx, el ) {
     20                $(el).attr( 'checked', check );
     21        })
     22       
     23        updateCheckAll( checkAll );
     24}
     25
     26function updateCheckAll( input ) {
     27        var paginatedTable = $(input).closest( '.paginate' );
     28        var dataTable = paginatedTable.closest( '.dataTables_wrapper' );
     29       
     30        var checkAll = $( '#checkAll', paginatedTable );
     31       
     32        var oTable = paginatedTable.dataTable();
     33        var inputs = $('input', oTable.fnGetNodes())
     34       
     35        // Is none checked, are all checked or are some checked
     36        var numChecked = 0
     37        for(var i = 0; i < inputs.length; i++ ) {
     38                if( $(inputs[i]).attr( 'checked' ) ) {
     39                        numChecked++;
     40                }
     41        }
     42       
     43        checkAll.attr( 'checked', numChecked > 0 );
     44       
     45        if( numChecked > 0 && numChecked < inputs.length - 1 ) {
     46                checkAll.addClass( 'transparent' );
     47        } else {
     48                checkAll.removeClass( 'transparent' );
     49        }
     50}
     51
    152function submitForm( form, url ) {
    253        if( form == undefined || !form )
Note: See TracChangeset for help on using the changeset viewer.