Changeset 1482


Ignore:
Timestamp:
Feb 2, 2011, 4:40:22 PM (12 years ago)
Author:
robert@…
Message:

Implemented saving of queries

Location:
trunk
Files:
4 added
16 edited

Legend:

Unmodified
Added
Removed
  • trunk/grails-app/controllers/RestController.groovy

    r1440 r1482  
    525525
    526526                        def user = AuthenticationService.getRemotelyLoggedInUser( params.consumer, params.token );
    527                         render( ['isOwner': study.isOwner(user), 'canRead': study.canRead(user), 'canWrite': study.canWrite(user)] as JSON )
     527                        def auth = ['isOwner': study.isOwner(user), 'canRead': study.canRead(user), 'canWrite': study.canWrite(user)];
     528                        log.trace "Authorization for study " + study.title + " and user " + user.username + ": " + auth
     529                        render auth as JSON;
    528530                } else {
    529531                        response.sendError(400)
  • trunk/grails-app/controllers/dbnp/authentication/LogoutController.groovy

    r1430 r1482  
    1616                }
    1717                // TODO  put any pre-logout code here
     18               
     19                // Remove all queries from session
     20                session.queries = [];
    1821        }
    1922
     
    3033                        redirect uri: SpringSecurityUtils.securityConfig.logout.filterProcessesUrl // '/j_spring_security_logout'
    3134                }
     35
     36                // Remove all queries from session
     37                session.queries = [];
    3238        }
    3339}
  • trunk/grails-app/controllers/dbnp/query/AdvancedQueryController.groovy

    r1458 r1482  
    1212class AdvancedQueryController {
    1313        def moduleCommunicationService;
     14        def authenticationService
     15
     16        def entitiesToSearchFor = [ 'Study': 'Studies', 'Sample': 'Samples']
    1417       
    15         def entitiesToSearchFor = [ 'Study': 'Studies', 'Sample': 'Samples']
    16     def index = {
    17                 [entitiesToSearchFor: entitiesToSearchFor, searchableFields: getSearchableFields()]
    18     }
     18        /**
     19         * Shows search screen
     20         */
     21        def index = {
     22                // Check whether criteria have been given before
     23                def criteria = [];
     24                if( params.criteria ) {
     25                        criteria = parseCriteria( params.criteria, false )
     26                }
     27                [entitiesToSearchFor: entitiesToSearchFor, searchableFields: getSearchableFields(), criteria: criteria]
     28        }
    1929
    2030        /**
     
    3848                // Create a search object and let it do the searching
    3949                Search search;
    40                 String view;
     50                String view = determineView( params.entity );
    4151                switch( params.entity ) {
    42                         case "Study":   search = new StudySearch();             view = "studyresults";  break;
    43                         case "Sample":  search = new SampleSearch();    view = "sampleresults"; break;
    44                        
    45                         // This exception will only be thrown if the entitiesToSearchFor contains more entities than 
     52                        case "Study":   search = new StudySearch();     break;
     53                        case "Sample":  search = new SampleSearch(); break;
     54
     55                        // This exception will only be thrown if the entitiesToSearchFor contains more entities than
    4656                        // mentioned in this switch structure.
    47                         default:                throw new Exception( "Can't search for entities of type " + params.entity );   
    48                 }
    49                
     57                        default:                throw new Exception( "Can't search for entities of type " + params.entity );
     58                }
    5059                search.execute( parseCriteria( params.criteria ) );
    51                
    52                 render( view: view, model: [search: search] );
     60
     61                // Save search in session
     62                def queryId = saveSearch( search );
     63                render( view: view, model: [search: search, queryId: queryId] );
     64        }
     65
     66        /**
     67         * Removes a specified search from session
     68         * @param       id      queryId of the search to discard
     69         */
     70        def discard = {
     71                Integer queryId
     72                try {
     73                        queryId = params.id as Integer
     74                } catch( Exception e ) {
     75                        flash.error = "Incorrect search ID given to discard"
     76                        redirect( action: "index" );
     77                        return
     78                }
     79
     80                discardSearch( queryId );
     81                flash.message = "Search has been discarded"
     82                redirect( action: "list" );
     83        }
     84
     85        /**
     86         * Shows a specified search from session
     87         * @param       id      queryId of the search to show
     88         */
     89        def show = {
     90                Integer queryId
     91                try {
     92                        queryId = params.id as Integer
     93                } catch( Exception e ) {
     94                        flash.error = "Incorrect search ID given to show"
     95                        redirect( action: "index" );
     96                        return
     97                }
     98
     99                // Retrieve the search from session
     100                Search s = retrieveSearch( queryId );
     101                if( !s ) {
     102                        flash.message = "Specified search could not be found"
     103                        redirect( action: "index" );
     104                        return;
     105                }
     106
     107                // Determine which view to show
     108                def view = determineView( s.entity );
     109                render( view: view, model: [search: s, queryId: queryId] );
     110        }
     111
     112        /**
     113         * Shows a list of searches that have been saved in session
     114         * @param       id      queryId of the search to show
     115         */
     116        def list = {
     117                def searches = listSearches();
     118
     119                if( !searches || searches.size() == 0 ) {
     120                        flash.message = "No previous searches found";
     121                        redirect( action: "index" );
     122                        return;
     123                }
     124                [searches: searches]
    53125        }
    54126       
     127        /**
     128         * Shows a search screen where the user can search within the results of another search
     129         * @param       id      queryId of the search to search in
     130         */
     131        def searchIn = {
     132                Integer queryId
     133                try {
     134                        queryId = params.id as Integer
     135                } catch( Exception e ) {
     136                        flash.error = "Incorrect search ID given to show"
     137                        redirect( action: "index" );
     138                        return
     139                }
     140
     141                // Retrieve the search from session
     142                Search s = retrieveSearch( queryId );
     143                if( !s ) {
     144                        flash.message = "Specified search could not be found"
     145                        redirect( action: "index" );
     146                        return;
     147                }
     148
     149                redirect( action: "index", params: [ "criteria.0.entityfield": s.entity, "criteria.0.operator": "in", "criteria.0.value": queryId ])
     150        }
     151
     152        protected String determineView( String entity ) {
     153                switch( entity ) {
     154                        case "Study":   return "studyresults";  break;
     155                        case "Sample":  return "sampleresults"; break;
     156                        default:                return "results"; break;
     157                }
     158        }
     159
    55160        /**
    56161         * Returns a map of entities with the names of the fields the user can search on
     
    59164        protected def getSearchableFields() {
    60165                def fields = [:];
    61                
     166
    62167                // Retrieve all local search fields
    63168                getEntities().each {
    64169                        def entity = getEntity( 'dbnp.studycapturing.' + it );
    65                        
     170
    66171                        if( entity ) {
    67172                                def domainFields = entity.giveDomainFields();
    68173                                def templateFields = TemplateField.findAllByEntity( entity )
    69                                
     174
    70175                                def fieldNames = ( domainFields + templateFields ).collect { it.name }.unique() + 'Template'
    71                                
     176
    72177                                fields[ it ] = fieldNames.sort { a, b -> a[0].toUpperCase() + a[1..-1] <=> b[0].toUpperCase() + b[1..-1] };
    73178                        }
    74179                }
    75                
     180
    76181                // Loop through all modules and check which fields are searchable
    77182                // Right now, we just combine the results for different entities
     
    81186                                def json = moduleCommunicationService.callModuleRestMethodJSON( module.url, callUrl );
    82187                                def moduleFields = [];
    83                                 entitiesToSearchFor.each { entity ->                                   
     188                                entitiesToSearchFor.each { entity ->
    84189                                        if( json[ entity.key ] ) {
    85190                                                json[ entity.key ].each { field ->
     
    88193                                        }
    89194                                }
    90                                
     195
    91196                                // Remove 'module' from module name
    92197                                def moduleName = module.name.replace( 'module', '' ).trim()
    93                                
     198
    94199                                fields[ moduleName ] = moduleFields.unique();
    95200                        } catch( Exception e ) {
     
    97202                        }
    98203                }
    99                
     204
    100205                return fields;
    101206        }
    102        
     207
    103208        /**
    104209         * Parses the criteria from the query form given by the user
     
    115220         *              1.field: d
    116221         *      ]
    117          *
    118          * @return      List with Criterion objects
    119          */
    120         protected List parseCriteria( def c ) {
     222         * @param parseSearchIds        Determines whether searches are returned instead of their ids
     223         * @return                                      List with Criterion objects
     224         */
     225        protected List parseCriteria( def formCriteria, def parseSearchIds = true ) {
    121226                ArrayList list = [];
    122                
     227                flash.error = "";
    123228                // Loop through all keys of c and remove the non-numeric ones
    124                 c.each {
    125                         if( it.key ==~ /[0-9]+/ ) {
    126                                 def formCriterion = it.value;
     229                for( c in formCriteria ) {
     230                        if( c.key ==~ /[0-9]+/ ) {
     231                                def formCriterion = c.value;
     232                               
    127233                                Criterion criterion = new Criterion();
    128                                
     234
    129235                                // Split entity and field
    130236                                def field = formCriterion.entityfield?.split( /\./ );
    131                                
    132237                                if( field.size() > 1 ) {
    133238                                        criterion.entity = field[0].toString();
    134239                                        criterion.field = field[1].toString();
    135240                                } else {
    136                                         criterion.entity = null;
    137                                         criterion.field = field;
     241                                        criterion.entity = field[0];
     242                                        criterion.field = null;
     243                                }
     244
     245                                // Convert operator string to Operator-enum field
     246                                try {
     247                                        criterion.operator = Criterion.parseOperator( formCriterion.operator );
     248                                } catch( Exception e) {
     249                                        println "Operator " + formCriterion.operator + " could not be parsed: " + e.getMessage();
     250                                        flash.error += "Criterion could not be used: operator " + formCriterion.operator + " is not valid.<br />\n";
     251                                        continue;
    138252                                }
    139253                               
    140                                 // Convert operator string to Operator-enum field
    141                                 switch( formCriterion.operator ) {
    142                                         case ">=":                      criterion.operator = Operator.gte; break;
    143                                         case ">":                       criterion.operator = Operator.gt;  break;
    144                                         case "<":                       criterion.operator = Operator.lte; break;
    145                                         case "<=":                      criterion.operator = Operator.lt;  break;
    146                                         case "contains":        criterion.operator = Operator.contains; break;
    147                                         case "equals":          criterion.operator = Operator.equals; break;
     254                                // Special case of the 'in' operator
     255                                if( criterion.operator == Operator.insearch ) {
     256                                        Search s
     257                                        try {
     258                                                s = retrieveSearch( Integer.parseInt( formCriterion.value ) );
     259                                        } catch( Exception e ) {}
     260                                       
     261                                        if( !s ) {
     262                                                flash.error += "Can't search within previous query: query not found";
     263                                                continue;
     264                                        }
     265                                       
     266                                        if( parseSearchIds ) {
     267                                                criterion.value = s
     268                                        } else {
     269                                                criterion.value = s.id
     270                                        }
     271                                } else {
     272                                        // Copy value
     273                                        criterion.value = formCriterion.value;
    148274                                }
    149275                               
    150                                 // Copy value
    151                                 criterion.value = formCriterion.value;
    152                                  
    153276                                list << criterion;
    154277                        }
    155278                }
    156                
     279
    157280                return list;
    158281        }
    159        
     282
    160283        /**
    161284         * Returns all entities for which criteria can be entered
     
    165288                return [ 'Study', 'Subject', 'Sample', 'Event', 'SamplingEvent', 'Assay' ]
    166289        }
    167        
    168         /**
    169         * Creates an object of the given entity.
    170         *
    171         * @return False if the entity is not a subclass of TemplateEntity
    172         */
    173    protected def getEntity( entityName ) {
    174            // Find the templates
    175            def entity
    176            try {
    177                    entity = Class.forName(entityName, true, this.getClass().getClassLoader())
    178 
    179                    // succes, is entity an instance of TemplateEntity?
    180                    if (entity.superclass =~ /TemplateEntity$/ || entity.superclass.superclass =~ /TemplateEntity$/) {
    181                            return entity;
    182                    } else {
    183                            return false;
    184                    }
    185            } catch( ClassNotFoundException e ) {
    186                         log.error "Class " + entityName + " not found: " + e.getMessage()
    187                         return null;
    188            }
    189 
    190    }
    191 
     290
     291        /**
     292         * Creates an object of the given entity.
     293         *
     294         * @return False if the entity is not a subclass of TemplateEntity
     295         */
     296        protected def getEntity( entityName ) {
     297                // Find the templates
     298                def entity
     299                try {
     300                        entity = Class.forName(entityName, true, this.getClass().getClassLoader())
     301
     302                        // succes, is entity an instance of TemplateEntity?
     303                        if (entity.superclass =~ /TemplateEntity$/ || entity.superclass.superclass =~ /TemplateEntity$/) {
     304                                return entity;
     305                        } else {
     306                                return false;
     307                        }
     308                } catch( ClassNotFoundException e ) {
     309                        log.error "Class " + entityName + " not found: " + e.getMessage()
     310                        return null;
     311                }
     312
     313        }
     314
     315
     316        /***************************************************************************
     317         *
     318         * Methods for saving results in session
     319         *
     320         ***************************************************************************/
     321
     322        /**
     323         * Saves the given search in session. Any search with the same criteria will be overwritten
     324         * 
     325         * @param s             Search to save
     326         * @return              Id of the search for later reference
     327         */
     328        protected int saveSearch( Search s ) {
     329                if( !session.queries )
     330                        session.queries = [:]
     331
     332                // First check whether a search with the same criteria is already present
     333                def previousSearch = retrieveSearchByCriteria( s.getCriteria() );
     334               
     335                def id
     336                if( previousSearch ) {
     337                        id = previousSearch.id;
     338                } else {
     339                        // Determine unique id
     340                        id = ( session.queries*.key.max() ?: 0 ) + 1;
     341                }
     342               
     343                s.id = id;
     344                session.queries[ id ] = s;
     345
     346                println "On saveSearch: " + session.queries;
     347                return id;
     348        }
     349
     350        /**
     351         * Retrieves a search from session with the same criteria as given
     352         * @param criteria      List of criteria to search for
     353         * @return                      Search that has this criteria, or null if no such search is found.
     354         */
     355        protected Search retrieveSearchByCriteria( List criteria ) {
     356                if( !session.queries )
     357                        return null
     358               
     359                if( !criteria )
     360                        return null
     361                       
     362                for( query in session.queries ) {
     363                        def key = query.key;
     364                        def value = query.value;
     365
     366                        if( value.criteria && value.criteria.containsAll( criteria ) && criteria.containsAll( value.criteria ) ) {
     367                                return value;
     368                        }
     369                }
     370
     371                return null;
     372        }
     373
     374
     375        /**
     376         * Retrieves a search from session
     377         * @param id    Id of the search
     378         * @return              Search that belongs to this ID or null if no search is found
     379         */
     380        protected Search retrieveSearch( int id ) {
     381                if( !session.queries || !session.queries[ id ] )
     382                        return null
     383
     384                if( !( session.queries[ id ] instanceof Search ) )
     385                        return null;
     386
     387                println "On retrieveSearch: " + session.queries;
     388                return (Search) session.queries[ id ]
     389        }
     390
     391        /**
     392         * Removes a search from session
     393         * @param id    Id of the search
     394         * @return      Search that belonged to this ID or null if no search is found
     395         */
     396        protected Search discardSearch( int id ) {
     397                if( !session.queries || !session.queries[ id ] )
     398                        return null
     399
     400                def sessionSearch = session.queries[ id ];
     401
     402                session.queries.remove( id );
     403
     404                println "On discardSearch: " + session.queries;
     405                if( !( sessionSearch instanceof Search ) )
     406                        return null;
     407
     408                return (Search) sessionSearch
     409        }
     410
     411        /**
     412         * Retrieves a list of searches from session
     413         * @return      List of searches from session
     414         */
     415        protected List listSearches() {
     416                if( !session.queries )
     417                        return []
     418
     419                return session.queries*.value.toList()
     420        }
    192421}
  • trunk/grails-app/domain/dbnp/studycapturing/Sample.groovy

    r1457 r1482  
    129129                return name
    130130        }
     131
     132        /**
     133        * Basic equals method to check whether objects are equals, by comparing the ids
     134        * @param o              Object to compare with
     135        * @return               True iff the id of the given Sample is equal to the id of this Sample
     136        */
     137   public boolean equals( Object o ) {
     138           if( o == null )
     139                   return false;
     140                   
     141           if( !( o instanceof Sample ) )
     142                   return false
     143           
     144           Sample s = (Sample) o;
     145           
     146           return this.id == s.id
     147   }
    131148       
    132149        /**
  • trunk/grails-app/domain/dbnp/studycapturing/Study.groovy

    r1457 r1482  
    522522                return this.studyUUID;
    523523        }
     524       
     525        /**
     526         * Basic equals method to check whether objects are equals, by comparing the ids
     527         * @param o             Object to compare with
     528         * @return              True iff the id of the given Study is equal to the id of this Study
     529         */
     530        public boolean equals( Object o ) {
     531                if( o == null )
     532                        return false;
     533                       
     534                if( !( o instanceof Study ) )
     535                        return false
     536               
     537                Study s = (Study) o;
     538               
     539                return this.id == s.id
     540        }
    524541
    525542        // Send messages to modules about changes in this study
  • trunk/grails-app/services/dbnp/modules/ModuleCommunicationService.groovy

    r1458 r1482  
    4848         * @return
    4949         */
    50         def invalidateStudy( Study study ) {
     50        def invalidateStudy( def study ) {
    5151                moduleNotificationService.invalidateStudy( study );
    5252        }
     
    113113                try {
    114114                        def textResponse = url.toURL().getText()
    115                         println "GSCF call to " + consumer + " URL: " + url
    116                         println "GSCF response: " + textResponse
     115                        log.trace "GSCF call to " + consumer + " URL: " + url
     116                        log.trace "GSCF response: " + textResponse
    117117                        restResponse = JSON.parse( textResponse )
    118118                } catch (Exception e) {
  • trunk/grails-app/views/advancedQuery/index.gsp

    r1430 r1482  
    66        <link rel="stylesheet" href="<g:resource dir="css" file="advancedQuery.css" />" type="text/css"/>
    77        <g:javascript src="advancedQuery.js" />
     8        <script type="text/javascript">
     9                // Make a list of fields to search in
     10                var queryableFields = [
     11                        <g:set var="j" value="${0}" />
     12                        <g:each in="${searchableFields}" var="entity">
     13                                <g:each in="${entity.value}" var="field">
     14                                        <g:if test="${j > 0}">,</g:if>
     15                                        {
     16                                                value: "${entity.key.toString().encodeAsJavaScript()}.${field.toString().encodeAsJavaScript()}",
     17                                                show: "${(field[0].toUpperCase() + field[1..-1]).encodeAsJavaScript()}",
     18                                                label: "${entity.key.toString().encodeAsJavaScript()}.${field.toString().encodeAsJavaScript()}",
     19                                                entity: "${entity.key.toString().encodeAsJavaScript()}"
     20                                        }
     21                                        <g:set var="j" value="1" />
     22                                </g:each>
     23                        </g:each>
     24                ];
     25       
     26                <g:if test="${criteria && criteria.size() > 0}">
     27                        // Show given criteria
     28                        $(function() {
     29                                <g:each in="${criteria}" var="criterion">
     30                                        showCriterium("${criterion.entityField().encodeAsJavaScript()}", "${criterion.value.toString().encodeAsJavaScript()}", "${criterion.operator.toString().encodeAsJavaScript()}");
     31                                </g:each>
     32                                showHideNoCriteriaItem();
     33                        });
     34                </g:if>
     35        </script>
    836</head>
    937<body>
     
    1139<h1>Query database</h1>
    1240
     41<g:if test="${flash.error}">
     42        <div class="error">
     43                ${flash.error.toString().encodeAsHTML()}
     44        </div>
     45</g:if>
     46<g:if test="${flash.message}">
     47        <div class="message">
     48                ${flash.message.toString().encodeAsHTML()}
     49        </div>
     50</g:if>
     51
     52<a href="<g:createLink action="list" />">View previous queries</a>
     53
    1354<form id="input_criteria">
    1455        <h2>Add criterium</h2>
    15         <p>
     56        <p class="explanation">
    1657                N.B. Comparing numerical values is done without taking into
    1758                account the units. E.g. a weight of 1 kg equals 1 grams.
    1859        </p>
    1960        <label for="field">Field</label>
    20                 <select name="field">
     61                <select name="field" id="queryFieldSelect">
    2162                        <option value=""></option>
    2263                        <g:each in="${searchableFields}" var="entity">
     
    2970                                </optgroup>
    3071                        </g:each>
    31                 </select> 
     72                </select>
    3273               
    3374        <label for="value">Comparison</label>
  • trunk/grails-app/views/advancedQuery/results.gsp

    r1430 r1482  
    55        <title>Query results</title>
    66        <link rel="stylesheet" href="<g:resource dir="css" file="advancedQuery.css" />" type="text/css"/>
    7         <g:javascript src="advancedQuery.js" />
    87</head>
    98<body>
     
    1716        <g:each in="${search.getCriteria()}" var="criterion">
    1817                <li>
    19                         <span class="entityfield">${criterion.entity}.${criterion.field}</span>
     18                        <span class="entityfield">${criterion.entityField()}</span>
    2019                        <span class="operator">${criterion.operator}</span>
    2120                        <span class="value">${criterion.value}</span>
     
    2827
    2928<g:if test="${search.getNumResults() > 0}">
    30 
     29        <%
     30                def resultFields = search.getShowableResultFields();
     31                def extraFields = resultFields[ search.getResults()[ 0 ].id ]?.keySet();
     32        %>
    3133        <table id="searchresults">
    3234                <thead>
     
    3436                                <th>Type</th>
    3537                                <th>Id</th>
     38                                <g:each in="${extraFields}" var="fieldName">
     39                                        <th>${fieldName}</th>
     40                                </g:each>
    3641                        </tr>
    3742                </thead>
     
    4045                                <td>${search.entity}</td>
    4146                                <td>${result.id}</td>
     47                                <g:each in="${extraFields}" var="fieldName">
     48                                        <td>
     49                                                <%
     50                                                        def fieldValue = resultFields[ result.id ]?.get( fieldName );
     51                                                        if( fieldValue ) {
     52                                                                if( fieldValue instanceof Collection )
     53                                                                        fieldValue = fieldValue.collect { it.toString() }.findAll { it }.join( ', ' );
     54                                                                else
     55                                                                        fieldValue = fieldValue.toString();
     56                                                        }
     57                                                %>
     58                                                ${fieldValue}
     59                                        </td>
     60                                </g:each>
     61
    4262                        </tr>
    4363                </g:each>
    4464        </table>
    4565</g:if>
    46 <p>
    47         <g:link action="index">Search again</g:link>
    48 </p>
     66<g:render template="resultbuttons" model="[queryId: queryId]" />
     67
    4968</body>
    5069</html>
  • trunk/grails-app/views/advancedQuery/sampleresults.gsp

    r1458 r1482  
    55        <title>Query results</title>
    66        <link rel="stylesheet" href="<g:resource dir="css" file="advancedQuery.css" />" type="text/css"/>
    7         <g:javascript src="advancedQuery.js" />
    87</head>
    98<body>
     
    1211
    1312<p>
    14         Your search for studies with:
     13        Your search for samples with:
    1514</p>
    16 <ul id="criteria">
    17         <g:each in="${search.getCriteria()}" var="criterion">
    18                 <li>
    19                         <span class="entityfield">${criterion.entity}.${criterion.field}</span>
    20                         <span class="operator">${criterion.operator}</span>
    21                         <span class="value">${criterion.value}</span>
    22                 </li>
    23         </g:each>
    24 </ul>
     15<g:render template="criteria" model="[criteria: search.getCriteria()]" />
    2516<p>
    2617        resulted in ${search.getNumResults()} <g:if test="${search.getNumResults() == 1}">sample</g:if><g:else>samples</g:else>.
    2718</p>
    2819
    29 
    3020<g:if test="${search.getNumResults() > 0}">
    31 
     21        <%
     22                def resultFields = search.getShowableResultFields();
     23                def extraFields = resultFields[ search.getResults()[ 0 ].id ]?.keySet();
     24        %>
    3225        <table id="searchresults" class="paginate">
    3326                <thead>
     
    3528                        <th>Study</th>
    3629                        <th>Name</th>
     30                        <g:each in="${extraFields}" var="fieldName">
     31                                <th>${fieldName}</th>
     32                        </g:each>
    3733                </tr>
    3834                </thead>
     
    4036                <g:each in="${search.getResults()}" var="sampleInstance" status="i">
    4137                        <tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
    42 
    4338                                <td><g:link controller="study" action="show" id="${sampleInstance?.parent?.id}">${sampleInstance?.parent?.title}</g:link></td>
    4439                                <td>${fieldValue(bean: sampleInstance, field: "name")}</td>
     40                                <g:each in="${extraFields}" var="fieldName">
     41                                        <td>
     42                                                <%
     43                                                        def fieldValue = resultFields[ sampleInstance.id ]?.get( fieldName );
     44                                                        if( fieldValue ) {
     45                                                                if( fieldValue instanceof Collection )
     46                                                                        fieldValue = fieldValue.collect { it.toString() }.findAll { it }.join( ', ' );
     47                                                                else
     48                                                                        fieldValue = fieldValue.toString();
     49                                                        }
     50                                                %>
     51                                                ${fieldValue}
     52                                        </td>
     53                                </g:each>
    4554                        </tr>
    4655                </g:each>
     
    4958
    5059</g:if>
    51 <p>
    52         <g:link action="index">Search again</g:link>
    53 </p>
     60<g:render template="resultbuttons" model="[queryId: queryId]" />
    5461</body>
    5562</html>
  • trunk/grails-app/views/advancedQuery/studyresults.gsp

    r1458 r1482  
    55        <title>Query results</title>
    66        <link rel="stylesheet" href="<g:resource dir="css" file="advancedQuery.css" />" type="text/css"/>
    7         <g:javascript src="advancedQuery.js" />
    87</head>
    98<body>
     
    1413        Your search for studies with:
    1514</p>
    16 <ul id="criteria">
    17         <g:each in="${search.getCriteria()}" var="criterion">
    18                 <li>
    19                         <span class="entityfield">${criterion.entity}.${criterion.field}</span>
    20                         <span class="operator">${criterion.operator}</span>
    21                         <span class="value">${criterion.value}</span>
    22                 </li>
    23         </g:each>
    24 </ul>
     15<g:render template="criteria" model="[criteria: search.getCriteria()]" />
    2516<p>
    2617        resulted in ${search.getNumResults()} <g:if test="${search.getNumResults() == 1}">study</g:if><g:else>studies</g:else>.
    2718</p>
    2819<g:if test="${search.getNumResults() > 0}">
     20        <%
     21                def resultFields = search.getShowableResultFields();
     22                def extraFields = resultFields[ search.getResults()[ 0 ].id ]?.keySet();
     23        %>
    2924
    3025        <table id="searchresults" class="paginate">
     
    3732                        <th>Events</th>
    3833                        <th>Assays</th>
     34                        <g:each in="${extraFields}" var="fieldName">
     35                                <th>${fieldName}</th>
     36                        </g:each>                       
    3937                </tr>
    4038                </thead>
     
    7977                                        </g:else>
    8078                                </td>
    81 
     79                                <g:each in="${extraFields}" var="fieldName">
     80                                        <td>
     81                                                <%
     82                                                        def fieldValue = resultFields[ studyInstance.id ]?.get( fieldName );
     83                                                        if( fieldValue ) {
     84                                                                if( fieldValue instanceof Collection )
     85                                                                        fieldValue = fieldValue.collect { it.toString() }.findAll { it }.join( ', ' );
     86                                                                else
     87                                                                        fieldValue = fieldValue.toString();
     88                                                        }
     89                                                %>
     90                                                ${fieldValue}
     91                                        </td>
     92                                </g:each>
    8293                        </tr>
    8394                </g:each>
     
    8697
    8798</g:if>
    88 <p>
    89         <g:link action="index">Search again</g:link>
    90 </p>
     99<g:render template="resultbuttons" model="[queryId: queryId]" />
    91100</body>
    92101</html>
  • trunk/src/groovy/dbnp/query/Criterion.groovy

    r1458 r1482  
    33import java.text.SimpleDateFormat
    44import org.dbnp.gdt.*
     5import org.apache.commons.logging.LogFactory;
    56
    67/**
     
    1011 */
    1112enum Operator {
    12         equals, contains, gte, gt, lte, lt
     13        equals( "=" ), contains( "contains" ), gte( ">="), gt( ">" ), lte( "<=" ), lt( "<" ), insearch( "in" )
     14        Operator(String name) { this.name = name }
     15        private final String name;
     16        public String toString() { return name }
    1317}
    1418
     
    1923 */
    2024class Criterion {
     25        private static final log = LogFactory.getLog(this);
    2126        public String entity
    2227        public String field
     
    2530
    2631        /**
     32         * Retrieves a combination of the entity and field
     33         * @return
     34         */
     35        public String entityField() {
     36                return entity.toString() + ( field ? "." + field.toString() : "" );
     37        }
     38       
     39        /**
    2740         * Retrieves the correct value for this criterion in the given object (with template)
    2841         *
    2942         * @param entity                Entity to check for value. Should be a child of template entity
    30          * @param criterion     Criterion to match on
    31          * @return                      Value of the given field or null if the field doesn't exist
     43         * @param criterion             Criterion to match on
     44         * @return                              Value of the given field or null if the field doesn't exist
    3245         */
    3346        public def getFieldValue( TemplateEntity entity ) {
     
    3750                try {
    3851                        def fieldValue
    39                         if( field == "Template" ) {
     52                        if( !field ) {
     53                                fieldValue = entity
     54                        } else if( field == "Template" ) {
    4055                                fieldValue = entity.template?.name
    4156                        } else {
     
    138153                if( fieldValue == null )
    139154                        return false;
    140 
     155               
     156                // in-search criteria have to be handled separately
     157                if( this.operator == Operator.insearch ) {
     158                        return this.value?.getResults()?.contains( fieldValue );
     159                }       
     160               
     161                // Other criteria are handled based on the class of the value given.
    141162                def classname = fieldValue.class.getName();
    142163                classname = classname[classname.lastIndexOf( '.' ) + 1..-1].toLowerCase();
    143 
    144                 println "Match " + fieldValue + " of class " + classname + " with " + this
    145164
    146165                try {
     
    185204                                return fieldValue <= criterionValue;
    186205                        case Operator.contains:
    187                                 return fieldValue.contains( criterionValue );
     206                                // Contains operator can only be used on string values
     207                                return fieldValue.toString().contains( criterionValue.toString() );
    188208                        case Operator.equals:
    189209                        default:
     
    295315                }
    296316        }
     317       
     318        public static Operator parseOperator( String name ) throws Exception {
     319                switch( name.trim() ) {
     320                        case "=": 
     321                        case "equals":          return Operator.equals;
     322                        case "contains":        return Operator.contains;
     323                        case ">=":
     324                        case "gte":                     return Operator.gte;
     325                        case ">":
     326                        case "gt":                      return Operator.gt;
     327                        case "<=":
     328                        case "lte":                     return Operator.lte;
     329                        case "<":
     330                        case "lt":                      return Operator.lt;
     331                        case "in":                      return Operator.insearch;
     332                        default:
     333                                throw new Exception( "Operator not found" );
     334                }
     335        }
    297336
    298337        public String toString() {
    299                 return "[Criterion " + entity + "." + field + " " + operator + " " + value + "]";
     338                return "[Criterion " + entityField() + " " + operator + " " + value + "]";
     339        }
     340       
     341        public boolean equals( Object o ) {
     342                if( o == null )
     343                        return false;
     344               
     345                if( !( o instanceof Criterion ) )
     346                        return false;
     347                       
     348                Criterion otherCriterion = (Criterion) o;
     349                return  this.entity == otherCriterion.entity &&
     350                                this.field == otherCriterion.field &&
     351                                this.operator == otherCriterion.operator &&
     352                                this.value == otherCriterion.value;
    300353        }
    301354}
  • trunk/src/groovy/dbnp/query/SampleSearch.groovy

    r1458 r1482  
    1515package dbnp.query
    1616
     17import java.util.Map;
     18
    1719import dbnp.studycapturing.*
    1820import org.dbnp.gdt.*
     21import org.apache.commons.logging.LogFactory;
    1922
    2023class SampleSearch extends Search {
     24        private static final log = LogFactory.getLog(this);
    2125       
    2226        public SampleSearch() {
     27                super();
     28                               
    2329                this.entity = "Sample";
    2430        }
     
    5763        @Override
    5864        void execute() {
    59                 // TODO: check for authorization for these studies?
     65                super.execute();
    6066
    6167                // If no criteria are found, return all samples
    6268                if( !criteria || criteria.size() == 0 ) {
    63                         results = Sample.list();
     69                        results = Sample.list().findAll { it.parent?.canRead( this.user ) };
    6470                        return;
    6571                }
     
    7278                def samples = []
    7379                if( getEntityCriteria( 'Study' ).size() > 0 ) {
    74                         def studies = Study.findAll();
    75                        
     80                        def studies = Study.findAll().findAll { it.canRead( this.user ) };
     81
    7682                        studies = filterOnStudyCriteria( studies );
    77                        
     83
    7884                        if( studies.size() == 0 ) {
    7985                                results = [];
    8086                                return;
    8187                        }
    82                        
     88
    8389                        def c = Sample.createCriteria()
    8490                        samples = c.list {
    8591                                'in'( 'parent', studies )
    8692                        }
     93
     94                        // Save data about the resulting studies in the
     95                        // result fields array. The data that is now in the array
     96                        // is saved based on the study id, not based on the sample id
     97                        clearResultFields();
     98                        saveResultFields( samples, getEntityCriteria( "Study" ), { sample, criterion ->
     99                                return criterion.getFieldValue( sample.parent );
     100                        });
    87101                } else {
    88                         samples = Sample.findAll()
     102                        samples = Sample.findAll().findAll { it.parent?.canRead( this.user ) }
    89103                }
    90104
     
    94108                samples = filterOnSamplingEventCriteria( samples );
    95109                samples = filterOnAssayCriteria( samples );
    96                
     110
    97111                samples = filterOnModuleCriteria( samples );
    98                
     112
    99113                // Save matches
    100114                results = samples;
     
    168182                if( getEntityCriteria( 'Assay' ).size() == 0 )
    169183                        return samples
    170                        
     184
    171185                // There is no sample.assays property, so we have to look for assays another way: just find
    172186                // all assays that match the criteria
     
    178192                        return criterion.matchOne( assay );
    179193                });
    180                
     194
    181195                // If no assays match these criteria, then no samples will match either
    182196                if( assays.size() == 0 )
    183197                        return [];
    184                
     198
    185199                // Save sample data for later use
    186                 saveResultFields( samples, criteria, { sample, criterion -> 
    187                          def sampleAssays = Assay.findByStudy( sample.parent ).findAll { it.samples?.contains( sample ) };
    188                          if( sampleAssays && sampleAssays.size() > 0 )
    189                                 return sampleAssays.collect( criterion.getFieldValue( it ) )
    190                         else 
     200                saveResultFields( samples, criteria, { sample, criterion ->
     201                        def sampleAssays = Assay.findByStudy( sample.parent ).findAll { it.samples?.contains( sample ) };
     202                        if( sampleAssays && sampleAssays.size() > 0 )
     203                                return sampleAssays.collect( criterion.getFieldValue( it ) )
     204                        else
    191205                                return null
    192206                });
    193                        
     207
    194208                // Now filter the samples on whether they are attached to the filtered assays
    195209                return samples.findAll { sample ->
    196210                        if( !sample.parent )
    197211                                return false;
    198                        
     212
    199213                        def studyAssays = assays.findAll { it.parent.equals( sample.parent ); }
    200                        
    201                         // See if this sample is present in any of the matching assays. If so, 
     214
     215                        // See if this sample is present in any of the matching assays. If so,
    202216                        // this sample matches the criteria
    203217                        for( def assay in studyAssays ) {
    204                                 if( assay.samples?.contains( sample ) ) 
     218                                if( assay.samples?.contains( sample ) )
    205219                                        return true;
    206220                        }
    207                        
     221
    208222                        return false;
    209223                }
    210224        }
     225
     226        /**
     227         * Returns the saved field data that could be shown on screen. This means, the data
     228         * is filtered to show only data of the query results. Also, the study title and sample
     229         * name are filtered out, in order to be able to show all data on the screen without
     230         * checking further
     231         *
     232         * @return      Map with the entity id as a key, and a field-value map as value
     233         */
     234        public Map getShowableResultFields() {
     235                Map showableFields = super.getShowableResultFields()
     236                showableFields.each { sampleElement ->
     237                        sampleElement.value = sampleElement.value.findAll { fieldElement ->
     238                                fieldElement.key != "Study title" && fieldElement.key != "Sample name"
     239                        }
     240                }
     241        }
    211242}
  • trunk/src/groovy/dbnp/query/Search.groovy

    r1478 r1482  
    1616package dbnp.query
    1717
     18import dbnp.authentication.SecUser
    1819import groovy.lang.Closure;
    1920
     
    2223
    2324import org.springframework.context.ApplicationContext
     25import org.springframework.web.context.request.RequestContextHolder;
    2426import org.codehaus.groovy.grails.commons.ApplicationHolder;
    2527
     
    2830class Search {
    2931        public String entity;
     32        public SecUser user;
     33        public Date executionDate;
     34        public int id;  // Is only used when this query is saved in session
    3035
    3136        protected List criteria;
     
    4247        public void setResultFields( Map r ) { resultFields = r; }
    4348
     49<<<<<<< .mine
     50        public Search() {
     51                ApplicationContext ctx = (ApplicationContext)ApplicationHolder.getApplication().getMainContext();
     52                def authenticationService = ctx.getBean("authenticationService");
     53                def sessionUser = authenticationService.getLoggedInUser();
     54               
     55                if( sessionUser )
     56                        this.user = sessionUser;
     57                else
     58                        this.user = null
     59        }
     60       
     61=======
     62>>>>>>> .r1481
    4463        /**
    4564         * Returns the number of results found by this search
     
    6887         * subclasses searching for a specific entity
    6988         */
    70         public void execute() {}
     89        public void execute() {
     90                this.executionDate = new Date();
     91        }
    7192
    7293        /**
     
    146167                                        return Boolean.valueOf( value )
    147168                                } catch( Exception e ) {
    148                                         println e.getMessage();
    149169                                        return value.toString();
    150170                                }
     
    178198        protected List filterOnTemplateEntityCriteria( List studies, String entityName, Closure valueCallback ) {
    179199                def criteria = getEntityCriteria( entityName );
     200               
    180201                def checkCallback = { study, criterion ->
    181202                        def value = valueCallback( study, criterion );
     
    192213                // Save the value of this entity for later use
    193214                saveResultFields( studies, criteria, valueCallback );
    194 
     215               
    195216                return filterEntityList( studies, criteria, checkCallback);
    196217        }
     
    207228                       
    208229                // Determine the moduleCommunicationService
    209                 def ctx = ApplicationHolder.getApplication().getMainContext();
     230                def ctx = (ApplicationContext)ApplicationHolder.getApplication().getMainContext();
    210231                def moduleCommunicationService = ctx.getBean("moduleCommunicationService");
    211232                       
     
    218239                       
    219240                        if( moduleCriteria && moduleCriteria.size() > 0 ) {
    220                                 println "Filter " + entities.size() + " entities on " + module.name + " criteria: " + moduleCriteria.size();
    221 
    222241                                // Retrieve the data from the module
    223242                                def tokens = entities.collect { it.giveUUID() }.unique();
     
    243262                                               
    244263                                                // Save the value of this entity for later use
    245                                                 saveResultField( entity.id, criterion.field, value )
     264                                                saveResultField( entity.id, criterion.entity + " " + criterion.field, value )
    246265
    247266                                                if( !( value instanceof Collection ) ) {
     
    267286                                                                               
    268287                                } catch( Exception e ) {
    269                                         println( "Error while retrieving data from " + module.name + ": " + e.getMessage() )
     288                                        log.error( "Error while retrieving data from " + module.name + ": " + e.getMessage() )
    270289                                }
    271290                        }
     
    285304                for( criterion in criteria ) {
    286305                        for( entity in entities ) {
    287                                 saveResultField( entity.id, criterion.field, valueCallback( entity, criterion ) )
     306                                if( criterion.field )
     307                                        saveResultField( entity.id, criterion.entity + ' ' + criterion.field, valueCallback( entity, criterion ) )
    288308                        }
    289309                }
     
    303323                resultFields[ id ][ fieldName ] = value;
    304324        }
     325       
     326        /**
     327         * Removes all data from the result field map
     328         */
     329        protected void clearResultFields() {
     330                resultFields = [:]
     331        }
     332       
     333        /**
     334         * 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.
     335         *
     336         * Subclasses could filter out the fields they don't want to show on the result screen (e.g. because they are shown regardless of the
     337         * query.)
     338         * @return      Map with the entity id as a key, and a field-value map as value
     339         */
     340        public Map getShowableResultFields() {
     341                def resultIds = getResults()*.id;
     342                return getResultFields().findAll {
     343                        resultIds.contains( it.key )
     344                }
     345        }
     346       
     347        public String toString() {
     348                return ( this.entity ? this.entity + " search" : "Search" ) + " " + this.id
     349        }
    305350}
  • trunk/src/groovy/dbnp/query/StudySearch.groovy

    r1458 r1482  
    1616
    1717import java.util.List;
     18import java.util.Map;
    1819
    1920import dbnp.studycapturing.*
    2021import org.dbnp.gdt.*
     22import org.apache.commons.logging.LogFactory;
    2123
    2224class StudySearch extends Search {
     25        private static final log = LogFactory.getLog(this);
     26       
    2327        public StudySearch() {
     28                super();
    2429                this.entity = "Study";
    2530        }
     
    5964        @Override
    6065        void execute() {
    61                 // TODO: check for authorization for these studies?
    62                 def studies = Study.list();
     66                super.execute();
    6367
     68                def studies = Study.list().findAll { it.canRead( this.user ) };
     69               
    6470                // If no criteria are found, return all studies
    6571                if( !criteria || criteria.size() == 0 ) {
     
    7581                studies = filterOnSamplingEventCriteria( studies );
    7682                studies = filterOnAssayCriteria( studies );
    77 
     83               
    7884                studies = filterOnModuleCriteria( studies );
    7985               
     
    8187                results = studies;
    8288        }
    83        
     89
    8490        /**
    8591         * Filters the given list of studies on the study criteria
     
    97103         */
    98104        protected List filterOnSubjectCriteria( List studies ) {
    99                 return filterOnTemplateEntityCriteria(studies, "Subject", { study, criterion -> 
     105                return filterOnTemplateEntityCriteria(studies, "Subject", { study, criterion ->
    100106                        return study.subjects?.collect { criterion.getFieldValue( it ); }
    101107                })
     
    119125         */
    120126        protected List filterOnEventCriteria( List studies ) {
    121                 return filterOnTemplateEntityCriteria(studies, "Event", { study, criterion -> 
     127                return filterOnTemplateEntityCriteria(studies, "Event", { study, criterion ->
    122128                        return study.events?.collect { criterion.getFieldValue( it ); }
    123129                })
    124130        }
    125        
     131
    126132        /**
    127         * Filters the given list of studies on the sampling event criteria
    128         * @param studies        Original list of studies
    129         * @return                       List with all studies that match the event-criteria
    130         */
    131    protected List filterOnSamplingEventCriteria( List studies ) {
    132                 return filterOnTemplateEntityCriteria(studies, "SamplingEvent", { study, criterion -> 
     133         * Filters the given list of studies on the sampling event criteria
     134         * @param studies       Original list of studies
     135         * @return                      List with all studies that match the event-criteria
     136         */
     137        protected List filterOnSamplingEventCriteria( List studies ) {
     138                return filterOnTemplateEntityCriteria(studies, "SamplingEvent", { study, criterion ->
    133139                        return study.samplingEvents?.collect { criterion.getFieldValue( it ); }
    134140                })
    135    }
    136        
     141        }
     142
    137143        /**
    138144         * Filters the given list of studies on the assay criteria
     
    145151                })
    146152        }
     153
     154        /**
     155         * Returns the saved field data that could be shown on screen. This means, the data
     156         * is filtered to show only data of the query results. Also, the study title and sample
     157         * name are filtered out, in order to be able to show all data on the screen without
     158         * checking further
     159         *
     160         * @return      Map with the entity id as a key, and a field-value map as value
     161         */
     162        public Map getShowableResultFields() {
     163                Map showableFields = super.getShowableResultFields()
     164                showableFields.each { sampleElement ->
     165                        sampleElement.value = sampleElement.value.findAll { fieldElement ->
     166                                fieldElement.key != "Study title" && fieldElement.key != "Subject species"
     167                        }
     168                }
     169                return showableFields
     170        }
    147171}
  • trunk/web-app/css/advancedQuery.css

    r1424 r1482  
    1 label { display: inline-block; zoom: 1; *display: inline; width: 110px; margin-top: 5px; }
     1label { display: inline-block; zoom: 1; *display: inline; width: 110px; margin-top: 10px; }
    22
    33#searchForm ul#criteria { margin-left: 110px; padding-left: 0px; margin-top: -19px; list-style-type: none;  }
    44#searchForm ul#criteria li { margin: 2px 0; padding-left: 0; }
    5 #searchForm ul#criteria li span { display: inline-block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-right: 5px; }
     5#searchForm ul#criteria li span,
     6#searchForm ul#criteria li a { display: inline-block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-right: 5px; }
    67#searchForm ul#criteria li .entityfield { width: 200px; }
    78#searchForm ul#criteria li .operator { width: 100px; }
    89#searchForm ul#criteria li .value { width: 240px; }
    910
     11#searchForm ul#criteria li.emptyList { color: #666; }
    1012#searchForm ul#criteria li.emptyList:hover { cursor: default; }
    1113
     
    1416
    1517#input_criteria { display: block; float: right; width: 260px; border: 1px solid #666; padding: 10px; }
    16 #input_criteria h2 { margin-top: 2px; margin-bottom: 8px; }
     18#input_criteria h2 { margin-top: 2px; margin-bottom: 8px; font-weight: bold; }
    1719#input_criteria label { width: 80px; margin-top: 8px; }
    1820#input_criteria input.text, #input_criteria select { width: 165px; }
    1921#input_criteria input.button {  margin-top: 8px; }
     22#input_criteria .explanation { font-size: 10px; }
     23
     24.ui-menu-item .entity { color: #666; font-style: italic; }
  • trunk/web-app/css/default_style.css

    r1448 r1482  
    196196}
    197197
     198#content .error {
     199        border: 1px solid #f99; /* #006dba; */
     200        margin-bottom: 10px;
     201        margin-top: 10px;
     202
     203        background: #ffe0e0 url(../plugins/famfamfam-1.0.1/images/icons/error.png) 10px 10px no-repeat;
     204        padding: 10px 10px 10px 33px;
     205
     206}
     207
    198208/** END :: content **/
    199209/** START :: footer **/
Note: See TracChangeset for help on using the changeset viewer.