Changeset 60 for trunk


Ignore:
Timestamp:
May 25, 2011, 4:33:47 PM (8 years ago)
Author:
robert@…
Message:
  • Improved speed by using even more ajax calls in pagination
  • Improved importing taxonomy files, changed mysql specific statements
  • Implemented taxonomy output
Location:
trunk
Files:
1 added
14 edited

Legend:

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

    r59 r60  
    143143        }
    144144       
     145        def exportTax = {
     146                //def assaySamples = [ AssaySample.get( 29 ), AssaySample.get( 45 ), AssaySample.get( 71 ), AssaySample.get( 27 )]
     147                def assaySamples = [ AssaySample.get( 102 ), AssaySample.get( 125 ), AssaySample.get( 129 ), AssaySample.get( 146 ), AssaySample.get( 104 ) ]
     148
     149                response.setHeader("Content-disposition", "attachment;filename=\"classification.txt\"")
     150                response.setContentType("application/octet-stream")
     151               
     152                classificationService.exportClassifications( assaySamples, response.outputStream )
     153               
     154                response.outputStream.close()
     155        }
     156       
    145157        def datatables = {
    146158                long runId = 3;
    147159                long userId = 2;
    148                 println "writable assays: " +  Run.executeQuery( "SELECT COUNT(*) FROM Run r LEFT JOIN r.assays a WHERE r.id = ?", [runId])
    149                 println "writable assays: " +  Run.executeQuery( "SELECT COUNT(*) FROM Run r LEFT JOIN r.assays a WHERE r.id = ? AND NOT EXISTS( FROM a.study.auth auth WHERE auth.canWrite = true AND auth.user.id = ?)", [runId, userId ])
     160//              println "writable assays: " +  Run.executeQuery( "SELECT COUNT(*) FROM Run r LEFT JOIN r.assays a WHERE r.id = ?", [runId])
     161//              println "writable assays: " +  Run.executeQuery( "SELECT COUNT(*) FROM Run r LEFT JOIN r.assays a WHERE r.id = ? AND NOT EXISTS( FROM a.study.auth auth WHERE auth.canWrite = true AND auth.user.id = ?)", [runId, userId ])
     162               
     163                println "Samples: " + Run.executeQuery( "SELECT s.id, s.sample.name, s.assay.study.name, s.assay.name, s.fwMidName, SUM( sd.numSequences ), SUM( CASE WHEN sd.qualityFile IS NULL THEN 0 ELSE sd.numSequences END ), (SELECT SUM( c.unclassified ) FROM nl.tno.massSequencing.classification.Classification c WHERE c.assaySample = s) as numClassified, s.run.id, s.assay.id, s.assay.study.id FROM nl.tno.massSequencing.AssaySample s LEFT JOIN s.sequenceData as sd WHERE s.run.id = :runId AND EXISTS( FROM nl.tno.massSequencing.auth.Auth a3 WHERE s.assay.study = a3.study AND a3.user = :user AND a3.canRead = true ) GROUP BY s.id, s.sample.name, s.assay.study.name, s.assay.name, s.fwMidName, s.run.id, s.assay.id, s.assay.study.id ORDER BY 6 asc", [ "runId": 3L, "user": User.get(2) ] );
    150164
    151165                //println "total assaysamples: " + Run.executeQuery( 'SELECT r.id, r.name, COUNT( a ), SUM( sd.numSequences ) FROM Run r LEFT JOIN r.assaySamples a LEFT JOIN a.sequenceData sd GROUP BY r.id, r.name' );
  • trunk/grails-app/controllers/nl/tno/massSequencing/AssayController.groovy

    r59 r60  
    3131   def showAssayList = {
    3232           // Determine the total number of assaysamples for this run
    33            int total = Assay.executeQuery( "SELECT COUNT(*) FROM Assay a WHERE a.study.trashcan = false")[ 0 ]
     33           def ids = Assay.executeQuery( "SELECT a.id FROM Assay a WHERE a.study.trashcan = false")
     34           int total = ids.size();
    3435
    3536           // Which columns are shown on screen and should be retrieved from the database
    3637           def columns = [
    37                    null,
     38                   "a.id",
    3839                   "a.name",
    3940                   "a.study.name",
    4041                   "COUNT( DISTINCT s )",
    4142                   "SUM( sd.numSequences ) / COUNT( DISTINCT s )",
    42                    null,
    43                    null,
    4443                   "a.study.studyToken"
    4544           ]
    4645           
    47            def idColumn = 'a.id';
    48            def groupColumns = [ idColumn ] + columns[1..2] + columns[ 7 ];
    49 
     46           def groupColumns = columns[0..2] + columns[ 5 ];
     47           def orderByMapping = null;
     48           
    5049           // Retrieve data from assaySample table
    5150           def from = "Assay a LEFT JOIN a.assaySamples s LEFT JOIN s.sequenceData sd"
     
    9089                   columns,
    9190                   groupColumns,
    92                    idColumn,
    9391                   from,
    9492                   total,
     93                   ids,
     94                   orderByMapping,
    9595                   where,
    9696                   parameters
    9797           ) as JSON
    9898   }
    99 
    10099
    101100        def show = {
     
    139138           
    140139           // Determine the total number of assaysamples for this run
    141            def total = (int) Run.executeQuery( "SELECT COUNT(*) FROM AssaySample s WHERE s.assay.id = :assayId", [ "assayId": assay.id ] )[ 0 ];
     140           def ids = Run.executeQuery( "SELECT s.id FROM AssaySample s WHERE s.assay.id = :assayId", [ "assayId": assay.id ] );
     141           def total = ids.size();
    142142
    143143           // Which columns are shown on screen and should be retrieved from the database
    144144           def columns = [
    145                    null,
     145                   "s.id",
    146146                   "s.sample.name",
    147147                   "run.name",
     
    149149                   "SUM( sd.numSequences )",
    150150                   "SUM( CASE WHEN sd.qualityFile IS NULL THEN 0 ELSE sd.numSequences END )",
    151                    null,
    152                    null,
    153151                   's.assay.id',
    154152                   's.assay.study.id',
    155153           ]
    156154           
    157            def idColumn = 's.id';
    158            def groupColumns = [ idColumn ] + columns[1..3] + columns[8..9];
    159 
     155           def groupColumns = columns[0..3] + columns[6..7];
     156           def orderByMapping = null;   // Meaning: order by column 2 on screen = order by column 1 in the table (screen starts at column 0, table starts at column 1 )
     157           
    160158           // Retrieve data from assaySample table
    161159           def from = "AssaySample s LEFT JOIN s.sequenceData as sd LEFT JOIN s.run as run"
     
    170168           def convertClosure = {
    171169                   def sampleId = it[ 0 ];
     170                   def sampleName = it[ 1 ];
    172171                   def assayId = it[ 6 ];
    173172                   def studyId = it[ 7 ];
     
    192191                   [
    193192                           g.checkBox( name: "ids", value: sampleId, checked: false, onClick: "updateCheckAll(this);" ),
    194                            it[ 1 ],     // it.sample.name
     193                           g.link( url: "#", onClick:"showSample( " + sampleId + ", 'run' );", title: "Show sample details" ) { sampleName },   // it.sample.name
    195194                           it[ 2 ],     // it.run.name
    196195                           it[ 3 ],     // it.fwMidName
     
    209208                   columns,
    210209                   groupColumns,
    211                    idColumn,
    212210                   from,
    213211                   total,
     212                   ids,
     213                   orderByMapping,
    214214                   where,
    215215                   parameters
  • trunk/grails-app/controllers/nl/tno/massSequencing/RunController.groovy

    r59 r60  
    1717                [runs: Run.list(), user: session.user]
    1818        }
    19        
    20         /**
    21         * Returns JSON data for the datatable with runs
    22         * @see http://www.datatables.net/usage/server-side
    23         * @see DataTablesService.retrieveData
    24         */
    25    def showRunList = {
    26            // Determine the total number of assaysamples for this run
    27            def total = Run.count()
    28 
    29            // Which columns are shown on screen and should be retrieved from the database
    30            def columns = [
    31                    null,
    32                    "r.name",
    33                    "COUNT( a )",
    34                    "SUM( sd.numSequences )",
    35                    null,
    36                    null,
    37                    null
    38            ]
    39            
    40            def idColumn = 'r.id';
    41            def groupColumns = [ idColumn ] + columns[1];
    42 
    43            // Retrieve data from assaySample table
    44            def from = "Run r LEFT JOIN r.assaySamples a LEFT JOIN a.sequenceData sd"
    45 
    46            // This closure determines what to do with a row that is retrieved from the database.
    47            def convertClosure = {
    48                    def runId = it[ 0 ];
    49                    def runName = it[ 1 ];
    50                    def numSamples = it[ 2 ];
    51                    def numSequences = it[ 3 ];
    52                    
    53                    // Create buttons in the last three columns
    54                    def editButton = g.link( controller: "run", action: "show", id: it[ 0 ], "title": "View run"  ) { '<img src="' + fam.icon( name: 'application_form_magnify' ) + '" title="View run" />' };
    55                    def deleteButton = '';
    56                    def chartButton = '';
    57                    
    58                    if( numSequences > 0 ) {
    59                            chartButton = g.link( controller: "run", action: "sequenceLengthHistogram", id: runId, title: "Sequence length histogram" ) { '<img src="' + fam.icon( name: 'chart_bar' ) + '" alt="Sequence length histogram" title="Sequence length histogram" />' }
    60                    } else {
    61                            chartButton = '<img src="' + fam.icon( name: 'chart_bar' ) + '" class="disabled" alt="No histogram available because no sequences are uploaded." title="No histogram available because no sequences are uploaded." />'
    62                    }
    63                    
    64                    if( numSequences > 0 || Run.hasNonWritableAssays( runId, session.user.id ) ) {
    65                            deleteButton = '<img src="' + fam.icon( name: 'delete' ) + '" class="disabled" alt="Run can not be deleted because data is associated with it." title="Run can not be deleted because data is associated with it." />'
    66                    } else {
    67                                 deleteButton = g.link( title:"Delete run", onClick:"return confirm( 'Are you sure you want to delete this run?' );", controller:"run", action:"deleteRun", id: runId ) { '<img src="' + fam.icon( name: 'delete' ) + '" alt="Delete run" title="Delete run" />' }
    68                    }
    69                    
    70                    [
    71                            g.checkBox( name: "ids", value: runId, checked: false, onClick: "updateCheckAll(this);" ),
    72                            g.link( title:"View run", controller:"run", action:"show", id: runId ) { runName },  // it.name
    73                            numSamples > 0 ? g.formatNumber( number: numSamples, format: "###,###,##0" ) : "-",  // it.numSequences(),
    74                            numSequences > 0 ? g.formatNumber( number: numSequences, format: "###,###,##0" ) : "-",      // it.numQualScores(),
    75                            editButton,
    76                            deleteButton,
    77                            chartButton
    78                    ]
    79            }
    80            
    81            // Send the data to the user
    82            render dataTablesService.retrieveData(
    83                    params,
    84                    Run.class,
    85                    convertClosure,
    86                    columns,
    87                    groupColumns,
    88                    idColumn,
    89                    from,
    90                    total,
    91            ) as JSON
    92    }
    93 
     19
     20        /**
     21         * Returns JSON data for the datatable with runs
     22         * @see http://www.datatables.net/usage/server-side
     23         * @see DataTablesService.retrieveData
     24         */
     25        def showRunList = {
     26                // Determine the total number of assaysamples for this run
     27                def ids = Run.executeQuery( "SELECT r.id FROM Run r" );
     28                def total = ids.size();
     29
     30                // Which columns are shown on screen and should be retrieved from the database
     31                def columns = [
     32                        "r.id",
     33                        "r.name",
     34                        "COUNT( DISTINCT a )",
     35                        "SUM( sd.numSequences )"
     36                ]
     37
     38                def groupColumns = columns[0..1];
     39                def orderByMapping = null;      // Meaning: order by column 2 on screen = order by column 1 in the table (screen starts at column 0, table starts at column 1 )   
     40
     41                // Retrieve data from assaySample table
     42                def from = "Run r LEFT JOIN r.assaySamples a LEFT JOIN a.sequenceData sd"
     43
     44                // This closure determines what to do with a row that is retrieved from the database.
     45                def convertClosure = {
     46                        def runId = it[ 0 ];
     47                        def runName = it[ 1 ];
     48                        def numSamples = it[ 2 ];
     49                        def numSequences = it[ 3 ];
     50
     51                        // Create buttons in the last three columns
     52                        def editButton = g.link( controller: "run", action: "show", id: it[ 0 ], "title": "View run"  ) { '<img src="' + fam.icon( name: 'application_form_magnify' ) + '" title="View run" />' };
     53                        def deleteButton = '';
     54                        def chartButton = '';
     55
     56                        if( numSequences > 0 ) {
     57                                chartButton = g.link( controller: "run", action: "sequenceLengthHistogram", id: runId, title: "Sequence length histogram" ) { '<img src="' + fam.icon( name: 'chart_bar' ) + '" alt="Sequence length histogram" title="Sequence length histogram" />' }
     58                        } else {
     59                                chartButton = '<img src="' + fam.icon( name: 'chart_bar' ) + '" class="disabled" alt="No histogram available because no sequences are uploaded." title="No histogram available because no sequences are uploaded." />'
     60                        }
     61
     62                        if( numSequences > 0 || Run.hasNonWritableAssays( runId, session.user.id ) ) {
     63                                deleteButton = '<img src="' + fam.icon( name: 'delete' ) + '" class="disabled" alt="Run can not be deleted because data is associated with it." title="Run can not be deleted because data is associated with it." />'
     64                        } else {
     65                                deleteButton = g.link( title:"Delete run", onClick:"return confirm( 'Are you sure you want to delete this run?' );", controller:"run", action:"deleteRun", id: runId ) { '<img src="' + fam.icon( name: 'delete' ) + '" alt="Delete run" title="Delete run" />' }
     66                        }
     67
     68                        [
     69                                g.checkBox( name: "ids", value: runId, checked: false, onClick: "updateCheckAll(this);" ),
     70                                g.link( title:"View run", controller:"run", action:"show", id: runId ) { runName },     // it.name
     71                                numSamples > 0 ? g.formatNumber( number: numSamples, format: "###,###,##0" ) : "-",     // it.numSequences(),
     72                                numSequences > 0 ? g.formatNumber( number: numSequences, format: "###,###,##0" ) : "-", // it.numQualScores(),
     73                                editButton,
     74                                deleteButton,
     75                                chartButton
     76                        ]
     77                }
     78
     79                // Send the data to the user
     80                render dataTablesService.retrieveData(
     81                                params,
     82                                Run.class,
     83                                convertClosure,
     84                                columns,
     85                                groupColumns,
     86                                from,
     87                                total,
     88                                ids,
     89                                orderByMapping
     90                                ) as JSON
     91        }
    9492
    9593        def show = {
     
    140138               
    141139                // Determine the total number of assaysamples for this run
    142                 def total = (int) Run.executeQuery( "SELECT COUNT(*) FROM AssaySample s WHERE s.run.id = :runId AND EXISTS( FROM Auth a3 WHERE s.assay.study = a3.study AND a3.user = :user AND a3.canRead = true )", [ "runId": run.id, "user": session.user  ] )[ 0 ];
    143 
     140                def ids = AssaySample.executeQuery( "SELECT s.id FROM AssaySample s WHERE s.run.id = :runId AND EXISTS( FROM Auth a3 WHERE s.assay.study = a3.study AND a3.user = :user AND a3.canRead = true )", [ "runId": run.id, "user": session.user  ] );
     141                def total = ids.size();
     142               
    144143                // Which columns are shown on screen and should be retrieved from the database
    145144                def columns = [
    146                         null,
     145                        "s.id",
    147146                        "s.sample.name",
    148147                        "s.assay.study.name",
     
    151150                        "SUM( sd.numSequences )",
    152151                        "SUM( CASE WHEN sd.qualityFile IS NULL THEN 0 ELSE sd.numSequences END )",
    153                         null,
    154                         null,
    155                         null,
    156152                        's.run.id',
    157153                        's.assay.id',
     
    159155                ]
    160156               
    161                 def idColumn = 's.id';
    162                 def groupColumns = [ idColumn ] + columns[1..4] + columns[10..12];
    163 
     157                def groupColumns = columns[0..4] + columns[7..9];
     158                def orderByMapping = null;      // Meaning: order by column 2 on screen = order by column 1 in the table (screen starts at column 0, table starts at column 1 )
     159               
    164160                // Retrieve data from assaySample table
    165161                def from = "AssaySample s LEFT JOIN s.sequenceData as sd"
     
    172168                def convertClosure = {
    173169                        def sampleId = it[ 0 ];
     170                        def sampleName = it[ 1 ];
    174171                        def runId = it[ 7 ];
    175172                        def assayId = it[ 8 ];
     
    202199                        [
    203200                                g.checkBox( name: "ids", value: sampleId, checked: false, onClick: "updateCheckAll(this);" ),
    204                                 it[ 1 ],        // it.sample.name
     201                                g.link( url: "#", onClick:"showSample( " + sampleId + ", 'assay' );", title: "Show sample details" ) { sampleName },    // it.sample.name
    205202                                it[ 2 ],        // it.assay.study.name
    206203                                it[ 3 ],        // it.assay.name
     
    221218                        columns,
    222219                        groupColumns,
    223                         idColumn,
    224220                        from,
    225221                        total,
     222                        ids,
     223                        orderByMapping,
    226224                        where,
    227225                        parameters
    228226                ) as JSON
     227       
    229228        }
    230229
  • trunk/grails-app/domain/nl/tno/massSequencing/classification/Taxon.groovy

    r59 r60  
    172172                def leafName = path[ -1 ];
    173173               
     174               
     175                println "Searching for " + path;
     176               
    174177                // Find all taxa that match the given leafnode
    175178                def leafs = Taxon.findAll(
     
    178181                )
    179182               
     183                println "Leafs found: " + leafs*.name
     184               
    180185                // If none is found, return null
    181186                if( !leafs || !leafs[0] )
     
    190195                for( leaf in leafs ) {
    191196                        leafPath = leaf.givePathNames();
     197                       
     198                        println "Path for leaf " + leaf.name + " (" + leaf.level + "): " + leafPath
    192199                        numLeafs = leafPath.size();
    193200                       
    194                         for( i = startLevel; i < numLeafs; i++ ) {
    195                                 if( leafPath[ i ] != path[ i - startLevel ] ) {
     201                        for( i = 0; i < numLeafs; i++ ) {
     202                                if( leafPath[ i ] != path[ i ] ) {
    196203                                        continue findLeafs
    197204                                }
  • trunk/grails-app/services/nl/tno/massSequencing/ClassificationService.groovy

    r59 r60  
    33import java.io.BufferedWriter;
    44import java.io.File;
     5import java.io.OutputStream;
    56import java.io.Writer;
    67import java.util.ArrayList;
     
    910import org.hibernate.StatelessSession
    1011import org.hibernate.Transaction
     12import org.hibernate.engine.SessionImplementor
    1113import java.util.zip.*
    1214import nl.tno.massSequencing.classification.*
     
    1416class ClassificationService implements nl.tno.massSequencing.imports.Importer {
    1517        def fileService
     18        def csvService
    1619        def sessionFactory
    1720
     
    140143
    141144                def taxonRegex = /"?([^"(]+)"?(\(\d+\))?/
    142                 def startLevel = 0    // The first level that is present in the file. Sometimes, the root element (level 0) is not mentioned in the file, in that case, this values should be set to 1
     145                def startLevel = 1    // The first level that is present in the file. Sometimes, the root element (level 0) is not mentioned in the file, in that case, this value should be set to 1
    143146                def unclassified = "unclassified"
    144147
     
    149152                // Create a stateless session in order to speed up inserts
    150153                StatelessSession statelessSession = sessionFactory.openStatelessSession();
    151 
     154               
    152155                // For each line in the taxonomy file, read the sequence and save it
    153156                def discardedSequences = 0;
     
    201204                        throw e;
    202205                } finally {
     206                        // Flush the last bit of inserts. Unfortunately, this is not automatically done when jdbc batch_size > 1
     207                        // See also http://opensource.atlassian.com/projects/hibernate/browse/HHH-4042
     208                        SessionImplementor sessionImpl = (SessionImplementor) statelessSession;
     209                        sessionImpl.getBatcher().executeBatch();
     210               
     211                        // Close the session and the reader
    203212                        statelessSession.close();
    204213                        taxonomyReader.close();
     
    228237                if( !assaySamples )
    229238                        return;
    230                        
     239
    231240                // Clear hibernate session for otherwise the session will be out of sync with the
    232241                // database
     
    245254                def connection = sessionFactory.currentSession.connection()
    246255                def statement = connection.createStatement();
     256
     257                // This statements searches within the sequences for every taxon that is mentioned (as a leaf-node in the tree)
     258                // and inserts statistics about those taxons into the classification table.
     259                //
     260                // However, if we have the following sequences:
     261                //              sequence1               Bacteria -> Firmicutes -> Clostridia -> (unclassified)
     262                //              sequence2               Bacteria -> (unclassified)
     263                //
     264                // The statement will insert values for
     265                //              Clostridia: 1
     266                //              Bacteria: 2
     267                //
     268                // and nothing for firmicutes. Of course, these values can be computed at any time, since
     269                // the taxonomy tree is known. Since these values are used for searching and exporting (e.g. show all samples
     270                // where firmicutes are present), it is essential that the extra values are also added. This is done in the second statement
     271
     272                // Insert statistics for all sequences and (leaf) taxa
    247273                statement.executeUpdate( connection.nativeSQL( """
    248274                        INSERT INTO     classification (assay_sample_id, taxon_id, unclassified, total)
     
    261287                        LEFT JOIN       assay_sample sa ON sd.sample_id = sa.id
    262288                        WHERE           sa.id IN (""" + assaySamples.id.join( ", " ) + """ )
    263                         GROUP BY        sa.id, s.classification_id
     289                        GROUP BY        sa.id, s.classification_id, t.lft, t.rgt
    264290           """ ) );
    265         }
    266 
     291
     292                // Update the table with missing values
     293                statement.executeUpdate( connection.nativeSQL( """
     294                INSERT INTO     classification (assay_sample_id, taxon_id, unclassified, total)
     295                SELECT * FROM (
     296                        SELECT          a.id as assay_sample_id, t.id as taxon_id, 0 as unclassified, 
     297                                                (
     298                                                        SELECT          sum( c.unclassified )
     299                                                        FROM            classification c
     300                                                        LEFT JOIN       taxonomy t3 ON c.taxon_id = t3.id
     301                                                        WHERE           c.assay_sample_id = a.id
     302                                                                AND     t3.lft >= t.lft
     303                                                                AND     t3.rgt <= t.rgt
     304                                                ) AS total
     305                        FROM            taxonomy t, assay_sample a
     306                        WHERE           a.id IN (""" + assaySamples.id.join( ", " ) + """ )
     307                        AND             t.id NOT IN (
     308                                                        SELECT          t2.id
     309                                                        FROM            classification c
     310                                                        LEFT JOIN       taxonomy t2 ON c.taxon_id = t2.id
     311                                                        WHERE           c.assay_sample_id = a.id
     312                                                )
     313                ) missingClassifications WHERE missingClassifications.total IS NOT NULL
     314          """ ) );
     315        }
    267316
    268317        /**
     
    277326                return inputFiles.find { it.type == type && it.numLines == numLines && !alreadyStored?.contains( it.filename ) };
    278327        }
     328
     329        /**
     330         * Exports all known data about the classifications of these samples to an excel file
     331         * @param assaySamples  Assaysamples to export information about
     332         * @param stream                        Outputstream to write the data to
     333         * @return
     334         */
     335        def exportClassifications( def assaySamples, OutputStream stream ) {
     336                if( assaySamples == null )
     337                        assaySamples = []
     338
     339                // Create csv file; sheetIndex is ignored
     340                def sheetIndex = 0;
     341
     342                // Create headerrow
     343                def headers = [ "level", "rankId", "taxon" ];
     344                def ids = [];
     345                assaySamples.each {
     346                        headers << it.sample.name
     347                        ids << it.id
     348                }
     349               
     350                // Structure to store the data in
     351                def data = [];
     352
     353                // Retrieve classification information for all assaySamples
     354                def classifications = Classification.executeQuery( "FROM Classification c LEFT JOIN c.taxon t LEFT JOIN c.assaySample a WHERE a IN (:assaySamples) ORDER BY t.lft, a.sample.name", [ "assaySamples": assaySamples ] );
     355
     356                // Find the maximum level present in this classification list
     357                def levels = Classification.executeQuery( "SELECT MAX(t.level), MIN(t.level) FROM Classification c LEFT JOIN c.taxon t LEFT JOIN c.assaySample a WHERE a IN (:assaySamples)", [ "assaySamples": assaySamples ] )[ 0 ];
     358                def maxLevel = levels[ 0 ]
     359                def minLevel = levels[ 1 ]
     360
     361                // Determine the starting prefix and the starting counter for the rankId
     362                def rankGlue = ".";
     363                def startCounter = 1
     364                def rootId = 0;
     365                def rankPrefix = [:]; rankPrefix [ minLevel ] = rootId;
     366                def rankCounter = [:]; rankCounter[ 1 ] = startCounter;
     367
     368                // Closure to retrieve a new id for the given level
     369                def rankId = { level ->
     370                        def rank = rankPrefix[ level ] + rankGlue + rankCounter[ level ];
     371                        rankCounter[ level ]++;
     372                        rankPrefix[ ( level + 1 ) ] = rank
     373                        rankCounter[ ( level + 1 ) ] = startCounter
     374                       
     375                        return rank;
     376                }
     377               
     378                def currentLine = [];
     379                def hasValues = false;
     380               
     381                def currentTaxonId = -1;
     382                def currentLevel = -1;
     383               
     384                def unClassifiedLine = [];
     385                def unClassifiedLines = [:]
     386                def hasUnClassified = false;
     387               
     388                def numAssaySamples = assaySamples.size();
     389               
     390                // Determine index of assaysample in list
     391                def assaySampleIndex = [:]
     392                for( def i = 0; i < assaySamples.size(); i++ ) {
     393                        assaySampleIndex[ assaySamples[ i ].id ] = i;
     394                }
     395               
     396                // Append classifications entry to the list with details about the numbers of unclassified sequences
     397                //  AND a.id IN (:assaySampleIds)
     398                def extraClassifications = Classification.executeQuery( "SELECT a.id, SUM( c.unclassified ), (SELECT SUM(sd.numSequences) FROM SequenceData sd WHERE sd.sample = a) FROM AssaySample a, Classification c WHERE c.assaySample = a AND  a IN (:assaySamples) GROUP BY a.id", [ "assaySamples": assaySamples ] );
     399                def rootLine = [ minLevel - 1, rootId, "root" ]
     400                extraClassifications.each {
     401                        // Add a classification in order to show the 'unclassified' values
     402                        classifications << [
     403                                [ 'total': it[ 2 ], 'unclassified': it[ 2 ] - it[ 1 ] ],
     404                                ['id':0,'level': minLevel - 1, 'name': ""],
     405                                [ 'id': it[ 0 ] ]
     406                        ]
     407                       
     408                        // Create a root line
     409                        rootLine[ assaySampleIndex[ it[ 0 ] ] + 3 ] = it[ 2 ];
     410                }
     411                data << rootLine;
     412               
     413                // Adding a line to ensure that the totals will be shown
     414                classifications << [ null, ['id':-1,'level':-1, 'name': "unclassified"], [ 'id': 0 ] ]
     415               
     416                classifications.each { line ->
     417                        def classification = line[ 0 ];
     418                        def taxon = line[ 1 ];
     419                        def assaySample = line[ 2 ];
     420                       
     421                        if( taxon.id != currentTaxonId ) {
     422                                // Start with a new taxon.
     423                                // Save the unclassified values for the previous level. These have to be shown it the subtree
     424                                // underneath the previous taxon is finished.
     425                                if( hasUnClassified )
     426                                        unClassifiedLines[ currentLevel ] = unClassifiedLine
     427                               
     428                                // If a line was generated, add it to the list
     429                                if( currentLine && hasValues && currentLevel >= minLevel )
     430                                        data << currentLine;
     431                                       
     432                                // First see whether a unclassified line must be put in place
     433                                // by checking whether a unClassifiedLine for the next level is given
     434                                if( taxon.level <= currentLevel ) {
     435                                        // Check which 'unclassified' lines must be shown. Every line of a subtree that is being closed
     436                                        // should be shown. 'unclassified' at the highest level should be shown first
     437                                        currentLevel.downto( taxon.level ) { lvl ->
     438                                                if( unClassifiedLines[ lvl ] ) {
     439                                                        // Each unclassified line should be repeated up to the highest level, in order
     440                                                        // to have the same number of sequences at every level.
     441                                                        def ucl = unClassifiedLines.remove( lvl );
     442                                                        lvl.upto( maxLevel - 1 ) { unclassifiedLevel ->
     443                                                                def newLine = [ unclassifiedLevel + 1 ] + rankId( unclassifiedLevel + 1 ) + ucl[2..-1];
     444                                                                data << newLine;
     445                                                        }
     446                                                }
     447                                        }
     448                                }
     449
     450                                // Determine the rankID for the given taxon. It is composed of the parentTaxon with a counter
     451                                def rank;
     452                               
     453                                if( taxon.level >= minLevel ) {
     454                                        rank = rankId( taxon.level )
     455                                }
     456                               
     457                                // Create a new line, because we arrived at a new taxon
     458                                currentLine = [ taxon.level, rank, taxon.name ]
     459                                hasValues = false;
     460                               
     461                                // Create a corresponding 'unclassified' line. This line will be saved for the end of the subtree
     462                                // The rank for the unclassified line is determined later on, because it depends on the subtree
     463                                unClassifiedLine = [ taxon.level + 1, "", "Unclassified (" + taxon.level + ") " + taxon.name  ]
     464                               
     465                                // Create as many entries in the list as there are samples
     466                                numAssaySamples.times {
     467                                        currentLine << ""
     468                                        unClassifiedLine << ""
     469                                }
     470                               
     471                                // Update taxonId and level
     472                                currentTaxonId = taxon.id;
     473                                currentLevel = taxon.level;
     474                                hasUnClassified = false;
     475                        }
     476                       
     477                        // Find index of this assaySample in the list, and save the classification total and unclassified value in the correct column
     478                        def index = assaySampleIndex[ assaySample.id ];
     479                        if( index != null ) {
     480                                currentLine[ index + 3 ] = classification.total ?: "";
     481                                unClassifiedLine[ index + 3 ] = classification.unclassified ?: "";
     482                       
     483                                // Set flag whether this line has to be included or not
     484                                if( classification.total > 0 )
     485                                        hasValues = true;
     486                               
     487                                // Set flag whether unclassified values have been found for this taxon. These values
     488                                // are only saved for levels < maxLevel, since at the highest level, all sequences are
     489                                // unclassified at a higher level
     490                                if( classification.unclassified > 0 && taxon.level < maxLevel )
     491                                        hasUnClassified = true;
     492                        }
     493                }
     494               
     495                // Create an excel sheet
     496                def wb = csvService.create();
     497
     498                // Put the headers on the first row
     499                csvService.writeHeader( wb, headers, sheetIndex );
     500                csvService.writeData( wb, data, sheetIndex, 1 );
     501
     502                // Write the data to the output stream
     503                csvService.output( wb, stream );
     504
     505                return true;
     506        }
     507       
    279508}
  • trunk/grails-app/services/nl/tno/massSequencing/DataTablesService.groovy

    r59 r60  
    4242         *
    4343         */
    44         def retrieveData( def params, Class domainClass, Closure convertClosure, List<String> columns, List<String> groupColumns, String idColumn, String from, int total, String where = "", def queryParameters = []) {
     44        def retrieveData( def params, Class domainClass, Closure convertClosure, List<String> columns, List<String> groupColumns, String from, int total, def ids, Map orderByMapping = null, String where = "", def queryParameters = []) {
    4545
    4646                // Create SELECT statement
    47                 def selectColumns = [ idColumn ] + columns.findAll { it }
    48                 def selectHQL = "SELECT " + selectColumns.join( ", " )
     47                def selectHQL = "SELECT " + columns.join( ", " )
    4948               
    5049                // Create FROM statement
     
    6867                        def orderColumnNum = params.int( 'iSortCol_0' );
    6968                        orderColumnNum = orderColumnNum >= columns.size() || orderColumnNum < 0 ? 0 : orderColumnNum
     69
     70                        // Search for the column number in the orderByMapping. If no mapping is given, the
     71                        // numbers are taken 1-on-1
     72                        def orderColumn;
     73                        if( orderByMapping ) {
     74                                orderColumn = orderByMapping[ orderColumnNum ] ?: orderColumnNum + 1;
     75                        } else {
     76                                orderColumn = orderColumnNum + 1;
     77                        }
    7078                       
    71                         def orderColumn = columns[ orderColumnNum ] ?: columns.findAll { it }[ 0 ];
    7279                        def orderDirection = params.get( 'sSortDir_0' )
    7380       
     
    8491                // Retrieve data itself
    8592                def retrieveHQL = [ selectHQL, fromHQL, whereHQL, groupHQL, orderHQL ].join( " " );
     93               
     94                println "HQL: " + retrieveHQL;
     95                println "ORDER " + params.int( 'iSortCol_0' );
     96               
    8697                def data = domainClass.executeQuery( retrieveHQL, queryParameters, [ max: num, offset: start ])
    8798               
     
    92103                        "iTotalRecords": total,
    93104                        "iTotalDisplayRecords": total,
    94                         "aaData": data.collect( convertClosure )
     105                        "aaData": data.collect( convertClosure ),
     106                        "aIds": ids
    95107                ]
    96108
  • trunk/grails-app/services/nl/tno/massSequencing/FastaService.groovy

    r59 r60  
    621621                zipFile.flush();
    622622                zipFile.closeEntry();
     623               
     624                // Export an excel file with information about the classification samples
     625                zipFile.putNextEntry( new ZipEntry( name + "classification.summary" ) );
     626                classificationService.exportClassifications( assaySamples, zipFile );
     627                zipFile.flush();
     628                zipFile.closeEntry();
    623629
    624630                zipFile.close();
  • trunk/grails-app/views/assay/index.gsp

    r59 r60  
    1414                        <form id="assayForm">
    1515                        </form>
    16                         <table id="assays" class="paginate_serverside" rel="<g:createLink controller="assay" action="showAssayList" />">
     16                        <table id="assays" class="paginate serverside" rel="<g:createLink controller="assay" action="showAssayList" />">
    1717                                <thead>
    1818                                        <tr>
  • trunk/grails-app/views/assay/show.gsp

    r59 r60  
    6666        <g:else>
    6767                <form id="sampleForm"><input type="hidden" name="assayId" value="${assay.id}" /></form>
    68                 <table class="paginate_serverside" rel="<g:createLink controller="assay" action="showSampleData" id="${assay.id}" />" id="samples">
     68                <table class="paginate serverside" rel="<g:createLink controller="assay" action="showSampleData" id="${assay.id}" />" id="samples">
    6969                        <thead>
    7070                                <tr>
  • trunk/grails-app/views/import/showProcessScreen.gsp

    r59 r60  
    4545                                  clearTimeout( progressInterval );
    4646
    47                                   alert( "Error " + xhr.getStatus() + ": " + xhr.getStatusText() );
     47                                  alert( "Error " + xhr.status + ": " + xhr.statusText + ". Please contact your system administrator." );
    4848                                  window.location.replace( "${errorUrl?.encodeAsJavaScript()}" );
    4949                        }
  • trunk/grails-app/views/run/index.gsp

    r59 r60  
    2626                        <form id="runForm">
    2727                        </form>
    28                         <table id="runs" class="paginate_serverside" rel="<g:createLink controller="run" action="showRunList" />">
     28                        <table id="runs" class="paginate serverside" rel="<g:createLink controller="run" action="showRunList" />">
    2929                                <thead>
    3030                                        <tr>
  • trunk/grails-app/views/run/show.gsp

    r59 r60  
    101101        <g:else>
    102102                <form id="sampleForm"><input type="hidden" name="runId" value="${run.id}" /></form>
    103                 <table class="paginate_serverside" rel="<g:createLink controller="run" action="showSampleData" id="${run.id}" />" id="samples">
     103                <table class="paginate serverside" rel="<g:createLink controller="run" action="showSampleData" id="${run.id}" />" id="samples">
    104104                        <thead>
    105105                                <tr>
  • trunk/web-app/js/forms.js

    r52 r60  
    88
    99function submitPaginatedForm( form, url, tableSelector, nothingInFormMessage ) {
     10        // Remove all inputs created before
    1011        $( '.created', form ).remove();
    1112       
    1213        // Find paginated form elements
    13         var oTable = $( tableSelector ).dataTable();
    14         var data = $( 'input', oTable.fnGetNodes() ).serializeArray();
     14        var paginatedTable = $(tableSelector);
     15        var tableId = paginatedTable.attr( 'id' );
     16        var formFilled;
    1517       
    16         var formFilled = false
     18        switch( tableType[ tableId ] ) {
     19                case "clientside":
     20                        var oTable = paginatedTable.dataTable();
     21                        var data = $( 'input', oTable.fnGetNodes() ).serializeArray();
     22                       
     23                        var formFilled = false
     24                       
     25                        $.each( data, function(idx, el) {
     26                                if( el.value != "" ) {
     27                                        var input = $( '<input type="hidden" class="created">');
     28                                        input.attr( 'name', el.name );
     29                                        input.attr( 'value', el.value );
     30                                        form.append( input );
     31                                        formFilled = true;
     32                                }
     33                        });
     34                        break;
     35                case "serverside":
     36                        var ids = elementsSelected[ tableId ];
     37                        formFilled = ( ids.length > 0 );
     38                       
     39                        $.each( ids, function(idx, id) {
     40                                var input = $( '<input type="hidden" class="created" name="ids">');
     41                                input.attr( 'value', id );
     42                                form.append( input );
     43                        });
     44                        break;
     45        }
    1746       
    18         $.each( data, function(idx, el) {
    19                 if( el.value != "" ) {
    20                         var input = $( '<input type="hidden" class="created">');
    21                         input.attr( 'name', el.name );
    22                         input.attr( 'value', el.value );
    23                         form.append( input );
    24                         formFilled = true;
    25                 }
    26         });
    2747       
    2848        // Show a message if the form is not filled
  • trunk/web-app/js/paginate.js

    r59 r60  
     1var numElements = new Array();          // Hashmap with the key being the id of the table, in order to facilitate multiple tables
     2var elementsSelected = new Array();     // Hashmap with the key being the id of the table, in order to facilitate multiple tables
     3var tableType = new Array();            // Hashmap with the key being the id of the table, in order to facilitate multiple tables       
     4var allElements = new Array();          // Hashmap with the key being the id of the table, in order to facilitate multiple tables
     5
    16function initializePagination( selector ) {
    27        if( selector == undefined ) {
     
    510       
    611        // Initialize default pagination
    7         $( selector + ' .paginate').each(function(idx, el) {
     12        $( selector + ' .paginate:not(.serverside)').each(function(idx, el) {
    813                var $el = $(el);
     14               
     15                tableType[ $el.attr( 'id' ) ] = "clientside";
     16               
    917                $el.dataTable({
    1018                        bJQueryUI: true,
     
    2230
    2331        // Initialize serverside pagination
    24         $( selector + ' .paginate_serverside').each(function(idx, el) {
     32        $( selector + ' .paginate.serverside').each(function(idx, el) {
    2533                var $el = $(el);
    2634               
    2735                // Determine data url from rel attribute
    2836                var dataUrl = $el.attr('rel');
     37                var id = $el.attr( 'id' );
     38               
     39                tableType[ id ] = "serverside";
     40                elementsSelected[ id ] = new Array();
    2941               
    3042                $el.dataTable({
     
    5668                                                fnCallback( data, textStatus, jqXHR );
    5769                                                showHidePaginatedButtonsForTableWrapper( $el.parent() );
     70                                               
     71                                                // Save total number of elements
     72                                                numElements[ id ] = data[ "iTotalRecords" ];
     73                                                allElements[ id ] = data[ "aIds" ];
     74                                               
     75                                                // Find which checkboxes are selected
     76                                                checkSelectedCheckboxes( $el.parent() );
    5877                                        }
    5978                                } );
     
    102121}
    103122
     123/**
     124 * Check selectboxes that had been selected before
     125 * @param wrapper
     126 */
     127function checkSelectedCheckboxes( wrapper ) {
     128        var inputsOnScreen = $( 'input[type=checkbox]', $(wrapper) );
     129        var tableId = $( ".paginate", $(wrapper) ).attr( 'id' );
     130       
     131        for( var i = 0; i < inputsOnScreen.length; i++ ) {
     132                var input = $(inputsOnScreen[ i ] );
     133                if( input.attr( 'id' ) != "checkAll" ) {
     134                        if( jQuery.inArray( parseInt( input.val() ), elementsSelected[ tableId ] ) > -1 ) {
     135                                input.attr( 'checked', true );
     136                        } else {
     137                                input.attr( 'checked', false );
     138                        }
     139                }
     140        }
     141}
     142
    104143function checkAllPaginated( input ) {
    105144        var paginatedTable = $(input).closest( '.paginate' );
    106         var dataTable = paginatedTable.closest( '.dataTables_wrapper' );
     145        var table_id = paginatedTable.attr( 'id' );
     146       
     147        switch( tableType[ table_id ] ) {
     148                case "clientside":      return checkAllPaginatedClientSide( paginatedTable );
     149                case "serverside":      return checkAllPaginatedServerSide( paginatedTable );
     150        }
     151}
     152
     153function checkAllPaginatedClientSide( paginatedTable ) {
     154        var paginatedTable = $(input).closest( '.paginate' );
    107155        var checkAll = $( '#checkAll', paginatedTable );
    108156       
     
    124172        })
    125173       
     174        updateCheckAllClientSide( checkAll );
     175}
     176
     177function checkAllPaginatedServerSide( paginatedTable ) {
     178        var tableId = paginatedTable.attr( 'id' );
     179        var checkAll = $( '#checkAll', paginatedTable );
     180       
     181        // If everything is selected, the action is to deselect everything. Otherwise
     182        // select everything
     183        if( numElements[ tableId ] == elementsSelected[ tableId ].length ) {
     184                elementsSelected[ tableId ] = new Array();
     185        } else {
     186                // Otherwise, select everything
     187                elementsSelected[ tableId ] = allElements[ tableId ]
     188        }
     189       
     190        checkSelectedCheckboxes( paginatedTable.parent() );
    126191        updateCheckAll( checkAll );
    127192}
    128193
    129194function updateCheckAll( input ) {
     195        var paginatedTable = $(input).closest( '.paginate' );
     196       
     197        // Determine type
     198        var tableId = paginatedTable.attr( 'id' );
     199       
     200        switch( tableType[ tableId ] ) {
     201                case "clientside":      return updateCheckAllClientSide( input );
     202                case "serverside":      return updateCheckAllServerSide( input );
     203        }
     204}
     205
     206function updateCheckAllClientSide( input ) {
    130207        var paginatedTable = $(input).closest( '.paginate' );
    131208        var dataTable = paginatedTable.closest( '.dataTables_wrapper' );
     
    153230}
    154231
     232function updateCheckAllServerSide( input ) {
     233        var paginatedTable = $(input).closest( '.paginate' );
     234        var dataTable = paginatedTable.closest( '.dataTables_wrapper' );
     235        var tableId = paginatedTable.attr( 'id' );
     236       
     237        // If the input is a normal checkbox, the user clicked on it. Update the elementsSelected array
     238        if( $(input).attr( 'id' ) != "checkAll" ) {
     239                var arrayPos = jQuery.inArray( parseInt( $(input).val() ), elementsSelected[ tableId ] );
     240                if( $(input).attr( 'checked' ) ) {
     241                        // Put the id in the elementsSelected array, if it is not present
     242                        if( arrayPos == -1 ) {
     243                                elementsSelected[ tableId ][ elementsSelected[ tableId ].length ] = parseInt( $(input).val() );
     244                        }
     245                } else {
     246                        // Remove the id from the elementsSelected array, if it is present
     247                        if( arrayPos > -1 ) {
     248                                elementsSelected[ tableId ].splice( arrayPos, 1 );
     249                        }
     250                }
     251        }
     252       
     253        var checkAll = $( '#checkAll', paginatedTable );
     254       
     255        checkAll.attr( 'checked', elementsSelected[ tableId ].length > 0 );
     256       
     257        if( elementsSelected[ tableId ].length > 0 && elementsSelected[ tableId ].length < numElements[ tableId ] ) {
     258                checkAll.addClass( 'transparent' );
     259        } else {
     260                checkAll.removeClass( 'transparent' );
     261        }
     262}
     263
    155264$(function() { initializePagination(); });
Note: See TracChangeset for help on using the changeset viewer.