Changeset 1482

Show
Ignore:
Timestamp:
02-02-11 16:40:22 (3 years ago)
Author:
robert@…
Message:

Implemented saving of queries

Location:
trunk
Files:
4 added
16 modified

Legend:

Unmodified
Added
Removed
  • 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/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/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 **/