Changeset 49 for trunk


Ignore:
Timestamp:
Apr 12, 2011, 3:39:13 PM (8 years ago)
Author:
robert@…
Message:
  • Added sequence length histograms
  • Fixed bugs in files that remained on the file system after uploading (while they shouldn't)
  • Added extra options in run- and assay screens
Location:
trunk
Files:
6 added
25 edited

Legend:

Unmodified
Added
Removed
  • trunk/grails-app/conf/BaseFilters.groovy

    r36 r49  
    175175                                        return true;
    176176                                }
     177                               
     178                                // Also never perform synchronization when files are uploaded. That could lead to concurrent modification
     179                                // errors, since the progress of the upload is retrieved many times, while the processing is still busy
     180                                if( controllerName == "fasta" ) {
     181                                        return true;
     182                                }
    177183
    178184                                // Never perform synchronization on a POST request
  • trunk/grails-app/conf/Config.groovy

    r36 r49  
    4747// remain in the upload directory. This mechanism ensures that the upload directory is cleaned after some time.
    4848// The time is given in seconds.
    49 massSequencing.fileUploadMaxAge = 2 * 24 * 3600 // 2 days
     49massSequencing.fileUploadMaxAge = 24 * 3600 // 1 day
    5050
    5151// Directory to save the uploaded files permanently
  • trunk/grails-app/controllers/nl/tno/massSequencing/AssayController.groovy

    r44 r49  
    4242                // Send the assay information to the view
    4343                [assay: assay, editable: assay.study.canWrite( session.user ), otherRuns: otherRuns]
     44        }
     45       
     46        def sequenceLengthHistogram = {
     47                def id = params.long( 'id' );
     48                def assay = id ? Assay.get( id ) : null
     49               
     50                if( !id || !assay ) {
     51                        flash.message = "No assay selected";
     52                        redirect( action: "index" );
     53                        return;
     54                }
     55               
     56                [ assay: assay, histogram: fastaService.sequenceLengthHistogram( assay.assaySamples.toList() ) ]
    4457        }
    4558
     
    340353                fileService.delete( session.filename  )
    341354                session.filename = ''
     355        }
     356       
     357       
     358        /**
     359         * Deletes all sequences for a given assay
     360         */
     361        def deleteSequenceData = {
     362                Assay assay = getAssay( params.id );
     363               
     364                if( !assay ) {
     365                        redirect(controller: 'assay', action: 'index')
     366                        return
     367                }
     368               
     369                def assaySamples = assay.assaySamples?.toList();
     370                if( !assaySamples ) {
     371                        flash.message = "No samples found for given assay";
     372                        redirect( controller: 'assay', action: 'show', id: assay.id );
     373                        return
     374                }
     375               
     376                def numFiles = fastaService.deleteSequenceData( assaySamples );
     377               
     378                flash.message = "All " + numFiles + " files have been removed from the assay.";
     379                redirect( controller: 'assay', action: 'show', id: assay.id );
    342380        }
    343381
  • trunk/grails-app/controllers/nl/tno/massSequencing/AssaySampleController.groovy

    r44 r49  
    11package nl.tno.massSequencing
    22
     3import java.util.List;
     4
    35class AssaySampleController {
     6        def fastaService
    47
    5     /**
    6     * Shows information about this assaySample in dialog style
    7     */
     8        /**
     9        * Shows information about this assaySample in dialog style
     10        */
    811        def show = {
    912                AssaySample assaySample = AssaySample.get( params.id as long );
    10                
     13
    1114                if( !assaySample ) {
    1215                        render "Sample not found";
    1316                        return
    1417                }
    15                
     18
    1619                if (!assaySample.assay.study.canRead( session.user ) ) {
    1720                        flash.error = "You don't have the right authorizaton to access sample " + assaySample.sample.name
     
    2023                }
    2124
    22                
     25
    2326                [assaySample: assaySample, entityType: params.entityType]
    2427        }
    25        
    26        
     28
     29        def sequenceLengthHistogram = {
     30                def id = params.long( 'id' );
     31                def assaySample = id ? AssaySample.get( id ) : null
     32
     33                if( !id || !assaySample ) {
     34                        flash.message = "No sample selected";
     35                        redirect( action: "index" );
     36                        return;
     37                }
     38
     39                [ assaySample: assaySample, histogram: fastaService.sequenceLengthHistogram( [assaySample] ) ]
     40        }
     41
     42        /**
     43         * Exports data about one or more runs in fasta format
     44         */
     45        def exportAsFasta = {
     46                def assaySamples = getAssaySamples( params );
     47                def name
     48
     49                if( assaySamples?.size() == 0 ) {
     50                        flash.error = "No samples selected";
     51                        redirect( action: "list" );
     52                        return;
     53                }
     54
     55                name = "samples";
     56
     57                // Export the sequences and quality scores
     58                response.setHeader "Content-disposition", "attachment; filename=${name}.zip"
     59                try {
     60                        fastaService.export( assaySamples.unique(), response.getOutputStream() );
     61                        response.outputStream.flush();
     62                } catch( Exception e ) {
     63                        log.error( "Exception occurred during export of sequences. Probably the user has cancelled the download." );
     64                }
     65        }
     66
    2767        /**
    2868         * Shows a form to edit the specified assaySample in dialog mode
     
    3676                        return
    3777                }
    38                
     78
    3979                if (!assaySample.assay.study.canWrite( session.user ) ) {
    4080                        flash.error = "You don't have the right authorizaton to access sample " + assaySample.sample.name
     
    4585                [parent: params.parent ?: "run", parentId: params.parentId ?: assaySample.run?.id, assaySample: assaySample]
    4686        }
    47        
     87
    4888        def update = {
    4989                // load assaySample with id specified by param.id
     
    65105                redirect( controller: params.parent ?: "run", action: 'show', id: params.parentId ?: assaySample.run?.id )
    66106        }
     107
     108        protected List getAssaySamples( params ) {
     109                def ids = params.list( 'ids' );
     110
     111                ids = ids.findAll { it.isLong() }.collect { Long.parseLong( it ) }
     112
     113                if( !ids ) {
     114                        def message = "No assaysample ids given"
     115                        flash.error = message
     116                        redirect( controller: "run", action: "index" );
     117                        return;
     118                }
     119
     120                return ids.collect { id -> AssaySample.get( id ) }.findAll{ it }
     121        }
    67122}
  • trunk/grails-app/controllers/nl/tno/massSequencing/FastaController.groovy

    r44 r49  
    22
    33import org.codehaus.groovy.grails.commons.ConfigurationHolder
     4import org.hibernate.SessionFactory
    45import grails.converters.*;
    56
     
    78        def fileService
    89        def fastaService
     10        def sessionFactory
    911       
    1012        /**************************************************************************
     
    187189        }
    188190
     191        /**
     192         * Returns from the upload wizard without saving the data. The uploaded files are removed
     193         */
     194        def returnWithoutSaving = {
     195                // Delete all uploaded files from disk
     196                session.processedFiles?.parsed?.success?.each {
     197                        fileService.delete( it.filename );
     198                }
     199
     200                // Redirect to the correct controller           
     201                switch( params.entityType ) {
     202                        case "run":
     203                        case "assay":
     204                                redirect( controller: params.entityType, action: "show", id: params.id );
     205                                return;
     206                        default:
     207                                response.setStatus( 404, "No entity found" );
     208                                render "";
     209                                return;
     210                }
     211               
     212               
     213        }
     214       
    189215        /**
    190216         * Saves processed files to the database, based on the selections made by the user
     
    266292                }
    267293
     294                // Return all files that have not been moved
     295                session.processedFiles?.parsed?.success?.each {
     296                        fileService.delete( it.filename );
     297                }
     298               
    268299                // Return a message to the user
    269300                if( numSuccesful == 0 ) {
     
    321352                 
    322353                def numFiles = sequenceData.numFiles();
    323                 sequenceData.delete();
    324 
     354                def sample = sequenceData.sample;
     355                 
     356                // Set flushmode to auto, since otherwise the sequencedata will
     357                // not be removed
     358                sessionFactory.getCurrentSession().setFlushMode( org.hibernate.FlushMode.AUTO );
     359               
     360                sample.removeFromSequenceData( sequenceData );
     361                sequenceData.delete(flush:true);
     362                sample.resetStats();
     363                sample.save();
     364               
    325365                flash.message = numFiles + " file" + (numFiles != 1 ? "s have" : " has" ) + " been deleted from this sample"
    326366
  • trunk/grails-app/controllers/nl/tno/massSequencing/RunController.groovy

    r44 r49  
    511511
    512512                if( !run ) {
    513                         redirect(controller: 'study')
     513                        redirect(controller: 'run', action: 'index')
    514514                        return
    515515                }
     
    546546
    547547                redirect( action: 'show', id: params.id)
     548        }
     549       
     550        /**
     551         * Deletes all sequences for a given run
     552         */
     553        def deleteSequenceData = {
     554                Run run = getRun( params.id );
     555               
     556                if( !run ) {
     557                        redirect(controller: 'run', action: 'index')
     558                        return
     559                }
     560               
     561                def assaySamples = run.assaySamples?.toList();
     562                if( !assaySamples ) {
     563                        flash.message = "No samples found for given run";
     564                        redirect( controller: 'run', action: 'show', id: run.id );
     565                        return
     566                }
     567               
     568                def numFiles = fastaService.deleteSequenceData( assaySamples );
     569               
     570                flash.message = "All " + numFiles + " files have been removed from the run.";
     571                redirect( controller: 'run', action: 'show', id: run.id );
    548572        }
    549573
     
    618642        }
    619643       
     644        def sequenceLengthHistogram = {
     645                def id = params.long( 'id' );
     646                def run = id ? Run.get( id ) : null
     647               
     648                if( !id || !run ) {
     649                        flash.message = "No run selected";
     650                        redirect( action: "index" );
     651                        return;
     652                }
     653               
     654                [ run: run, histogram: fastaService.sequenceLengthHistogram( run.assaySamples?.toList() ) ]
     655        }
     656       
    620657        protected List getAssaySamples( params ) {
    621658                def ids = params.list( 'ids' );
  • trunk/grails-app/controllers/nl/tno/massSequencing/StudyController.groovy

    r42 r49  
    2525        }
    2626       
     27        def sequenceLengthHistogram = {
     28                def id = params.long( 'id' );
     29                def study = id ? Study.get( id ) : null
     30               
     31                if( !id || !study ) {
     32                        flash.message = "No study selected";
     33                        redirect( action: "index" );
     34                        return;
     35                }
     36               
     37                [ study: study, histogram: fastaService.sequenceLengthHistogram( study.assays*.assaySamples.flatten().unique().findAll { it } ) ]
     38        }
    2739
    2840        /**
  • trunk/grails-app/controllers/nl/tno/massSequencing/files/FileController.groovy

    r44 r49  
    210210                }
    211211        }
     212       
     213        /**
     214         * Clears the temporary directory by removing all files being there longer than a certain treshold
     215         * @see FileService.cleanDirectory
     216         */
     217        def cleanTemp = {
     218                fileService.cleanDirectory();
     219                flash.message = "Temporary directory has been cleaned."
     220                redirect( controller: "run" );
     221        }
    212222
    213223
  • trunk/grails-app/controllers/nl/tno/massSequencing/integration/RestController.groovy

    r48 r49  
    451451                }
    452452               
    453                 println "getMeasurements: " + getMeasurementTypes().keySet().asList()
    454453                render getMeasurementTypes().keySet().asList() as JSON
    455454        }
     
    689688         */
    690689        def compactTable( results ) {
    691                 def i = 0
    692                 def sampleTokenIndex = [:]
    693690                def sampleTokens = results.collect( { it['sampleToken'] } ).unique()
    694                 sampleTokens.each{ sampleTokenIndex[it] = i++ }
    695 
    696                 i = 0
    697                 def measurementTokenIndex= [:]
    698691                def measurementTokens = results.collect( { it['measurementToken'] } ).unique()
    699                 measurementTokens.each{ measurementTokenIndex[it] = i++ }
    700692
    701693                def data = []
  • trunk/grails-app/domain/nl/tno/massSequencing/AssaySample.groovy

    r42 r49  
    253253                }
    254254        }
     255       
     256       
     257        /**
     258         * Delete all sequences from a sample
     259         * @param assaySample
     260         * @return
     261         */
     262        public int deleteSequenceData() {
     263                if( !sequenceData )
     264                        return 0;
     265               
     266                def numFiles = 0;
     267                sequenceData.each { sequenceData ->
     268                        numFiles += sequenceData.numFiles();
     269                 
     270                        removeFromSequenceData( sequenceData );
     271                        sequenceData.delete(flush:true);
     272                }
     273
     274                resetStats();
     275                save();
     276               
     277                return numFiles;
     278        }
     279
    255280}
  • trunk/grails-app/domain/nl/tno/massSequencing/SequenceData.groovy

    r42 r49  
    5858                // Reset statistics of the assay sample, to ensure the deleted files are removed from statistics
    5959                sample?.resetStats();
     60               
     61                return true;
    6062        }
    6163       
  • trunk/grails-app/domain/nl/tno/massSequencing/Study.groovy

    r42 r49  
    4343        }
    4444       
     45        /**
     46        * Returns the total number of sequences found in this assay. It is computed
     47        * as the sum of the number of sequences of all samples
     48        * @return
     49        */
     50   public int numSequences() {
     51           if( !assays )
     52                   return 0
     53           
     54           int numSequences = 0;
     55           assays.each { numSequences += it.numSequences() }
     56
     57           return numSequences;
     58   }
     59       
    4560        public boolean equals( Object o ) {
    4661                if( o == null )
  • trunk/grails-app/services/nl/tno/massSequencing/FastaService.groovy

    r44 r49  
    55import java.io.Writer;
    66import java.util.ArrayList;
     7import java.util.List;
    78import java.util.zip.ZipEntry
    89import java.util.zip.ZipOutputStream
     
    5960                for( int i = 0; i < filenames.size(); i++ ) {
    6061                        def filename = filenames[ i ];
    61 
     62                       
    6263                        if( fileService.isZipFile( filename ) ) {
    6364                                // ZIP files are extracted and appended to the filenames list.
     
    7273                                                filenames.add( it );
    7374                                        }
     75                                       
     76                                        // Delete the zip file itself
     77                                        fileService.delete( filename );
     78                                } else {
     79                                        failure << [ filename: filename, originalfilename: fileService.originalFilename( filename ), type: "zip", message: 'Zip file could not be extracted' ];
    7480                                }
    7581                        } else {
     
    8995                                                        onProgress( filesProcessed, bytesProcessed, 0, 0 );
    9096                                                } );
    91 
     97                                               
    9298                                                contents.filename = file.getName();
    9399                                                contents.originalfilename = fileService.originalFilename( contents.filename )
     
    108114                }
    109115
    110                 return [ success: success, failure: failure ];
     116                return [ 'success': success, 'failure': failure ];
    111117        }
    112118
     
    166172                        // Best matching sample
    167173                        def assaySample = null
     174                        def checked = true;
    168175                        if( matches ) {
    169176                                // Match with files from excelsheet
    170177                               
    171178                                // First find the best matching filename in the list of matches.
    172                                 def sampleNameIdx = fuzzySearchService.mostSimilarWithIndex( matchWith, matches*.basename );
     179                                def sampleNameIdx = fuzzySearchService.mostSimilarWithIndex( matchWith, matches*.basename, 0.8 );
    173180                               
    174181                                // If one is found, use the sample name associated with it to do the matching with samples
    175182                                if( sampleNameIdx != null ) {
    176183                                        matchWith = matches[ sampleNameIdx ].sample;
     184                                } else {
     185                                        // If no match is found in the excel sheet, this sample is unchecked by default
     186                                        checked = false;
    177187                                }
    178188                        }
     
    188198                                                feasibleQuals: feasibleQuals,
    189199                                                qual: qual,
    190                                                 assaySample: assaySample
     200                                                assaySample: assaySample,
     201                                                checked: checked
    191202                                        ]
    192203                }
     
    293304
    294305                return [ success: true, type: "fasta", filename: file.getName(), numSequences: numSequences ];
     306        }
     307
     308        /**
     309         * Parses the given FASTA file and returns a list with sequence lengths
     310         * @param file                  File to parse
     311         * @return List                 [ 400: 12, 410: 15, 'unknown': 14 ]
     312         */
     313        protected def parseFastaLengths( File file ) {
     314
     315                long startTime = System.nanoTime();
     316                log.trace "Start parsing FASTA " + file.getName()
     317
     318                // Count the number of lines, starting with '>' (and where the following line contains a character other than '>')
     319                def histogram = [:]
     320                def lengthPattern = ~/length=(\d+)/
     321                def length
     322                def lengthMatches
     323                try {
     324                        file.eachLine { line ->
     325                                if( line ) {
     326                                        if( line[0] == '>' ) {
     327                                                // Comments line: find length=###
     328                                                lengthMatches = ( line =~ lengthPattern );
     329                                                if( lengthMatches ) {
     330                                                        length = Integer.valueOf( lengthMatches[0][1] );
     331                                                } else {
     332                                                        length = 'unknown'
     333                                                }
     334
     335                                                histogram[ length ] = ( histogram[length] ?: 0 ) + 1;
     336                                        }
     337                                }
     338                        }
     339                } catch( Exception e ) {
     340                        log.error( "Exception while reading fasta file " + file + ": " + e.getMessage() )
     341                }
     342
     343                log.trace "Finished parsing FASTA " + file.getName() + ": " + ( System.nanoTime() - startTime ) / 1000000L
     344
     345                return histogram;
    295346        }
    296347
     
    508559        }
    509560
     561        /*
     562         * Creates a histogram of sequence lengths for the given assaysamples
     563         * @param       assaySamples    List of assaySamples to create the histogram for
     564         * @return      List                    List with length-number tuples. Example:
     565         *                                                      [ [400, 12], [402, 9] ]
     566         */
     567        public List sequenceLengthHistogram( ArrayList assaySamples ) {
     568                if( !assaySamples || assaySamples.size() == 0 )
     569                        return [];
     570                       
     571                // Determine the directory the uploaded files are stored in
     572                File permanentDirectory = fileService.absolutePath( ConfigurationHolder.config.massSequencing.fileDir );
     573                       
     574                // Initialize the histogram
     575                def histogram = [:];   
     576               
     577                // Loop through all samples and find the histogram values for each of them
     578                assaySamples.each { assaySample ->
     579                        if( assaySample.numSequences() > 0 ) {
     580                                assaySample.sequenceData.each { sequenceData ->
     581                                        File sequenceFile = fileService.get( sequenceData.sequenceFile, permanentDirectory );
     582                                       
     583                                        parseFastaLengths( sequenceFile ).each { k, v ->
     584                                                histogram[ k ] = ( histogram[ k ] ?: 0 ) + v;
     585                                        };
     586                                }
     587                        }
     588                }
     589               
     590                def lengthList = [];
     591                histogram.each {
     592                        if( it.key != 'unknown' )
     593                                lengthList << [ it.key, it.value ]
     594                }
     595               
     596                return lengthList;
     597        }
     598
    510599        /**
    511600         * Exports the fasta data of a list of assaysamples
     
    683772                }
    684773        }
    685 
     774       
     775        /**
     776         * Deletes all sequences from a list of assaysamples
     777         * @param assaySample
     778         * @return
     779         */
     780        public int deleteSequenceData( ArrayList assaySamples ) {
     781                def numFiles = 0;
     782                assaySamples?.each { assaySample ->
     783                        numFiles += assaySample.deleteSequenceData();
     784                }
     785               
     786                return numFiles;
     787        }
    686788
    687789        /**
  • trunk/grails-app/views/assay/_addFilesDialog.gsp

    r44 r49  
    55                <input type="hidden" name="entityType" value="assay" />
    66                <p>
    7                         Select sequence and quality files to upload. It is possible to zip the files before upload.
     7                        Select sequence (.fna, .fasta) and quality (.fqa, .qual) files to upload. It is possible to zip the files before upload. You can add multiple files, if needed.
    88                </p>
    99                <p>
    1010                        The filenames should match the sample names of the samples they belong to. It is also possible to provide an
    11                         excel sheet to describe which file belongs to which sample. The format should be like this <g:link controller="assay" action="downloadMatchExcel" id="${assay.id}">example</g:link>.
     11                        excel sheet to describe which file belongs to which sample. The format should be like this <g:link controller="assay" action="downloadMatchExcel" id="${assay.id}">example</g:link>, with each fasta file appearing once.
    1212                </p>
    1313                <g:fileUpload name="sequencefiles" value="" multiple="${true}" onUpload="handleFileUploadData" onDelete="deleteProcessButton"/>
  • trunk/grails-app/views/assay/index.gsp

    r34 r49  
    2323                                                <th>avg sequences / sample</th>
    2424                                                <th class="nonsortable"></th>
     25                                                <th class="nonsortable"></th>
    2526                                        </tr>
    2627                                </thead>
     
    4445                                                                        </td>
    4546                                                                        <td><g:link controller="assay" action="show" id="${assay.id}"><img src="${fam.icon( name: 'application_form_magnify' )}" alt="view" title="view" /></g:link></td>
     47                                                                        <td>
     48                                                                                <g:if test="${assay.numSequences() > 0}">
     49                                                                                        <g:link controller="assay" action="sequenceLengthHistogram" id="${assay.id}"><img src="${fam.icon( name: 'chart_bar' )}" alt="Sequence length histogram" title="Sequence length histogram" /></g:link>
     50                                                                                </g:if>
     51                                                                                <g:else>
     52                                                                                        <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." />
     53                                                                                </g:else>
     54                                                                        </td>                                                   
    4655                                                                       
    4756                                                                </tr>
  • trunk/grails-app/views/assay/show.gsp

    r45 r49  
    6262        </g:if>
    6363        <g:else>
    64                 <table class="paginate">
     64                <form id="sampleForm"></form>
     65                <table class="paginate" id="samples">
    6566                        <thead>
    6667                                <tr>
     68                                        <th width="5" class="nonsortable"><input type="checkbox" id="checkAll" onClick="checkAllPaginated(this);" /></th>
    6769                                        <th nowrap>name</th>
    6870                                        <th nowrap>run</th>
     
    7072                                        <th nowrap># sequences</th>
    7173                                        <th nowrap># qual</th>
     74                                        <th class="nonsortable"></th>
    7275                                        <th class="nonsortable"></th>
    7376                                </tr>
     
    7780                                <g:each in="${assaySamples}" var="assaySample">
    7881                                        <tr>
     82                                                <td><g:checkBox name="ids" value="${assaySample.id}" checked="${false}" onClick="updateCheckAll(this);" /></td>
    7983                                                <td><a href="#" onClick="showSample(${assaySample.id}, 'assay'); return false;">${assaySample.sample.name}</a></td>
    8084                                                <td>${assaySample.run?.name}</td>
     
    104108                                                        </g:else>
    105109                                                </td>
     110                                                <td class="button">
     111                                                        <g:if test="${assaySample.numSequences() > 0}">
     112                                                                <g:link controller="assaySample" action="sequenceLengthHistogram" id="${assaySample.id}"><img src="${fam.icon( name: 'chart_bar' )}" alt="Sequence length histogram" title="Sequence length histogram" /></g:link>
     113                                                        </g:if>
     114                                                        <g:else>
     115                                                                <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." />
     116                                                        </g:else>
     117                                                </td>                                           
    106118                                        </tr>
    107119                                </g:each>
     
    115127                                <a class="editAssociation disabled" onClick="return false;" href="#">Edit sample data</a>
    116128                        </g:else>
     129                       
     130                        <a class="fasta" href="#" onClick="submitPaginatedForm( $( '#sampleForm' ), '<g:createLink controller="assaySample" action="exportAsFasta" />', '#samples', 'Please select one or more samples to export' ); return false;">Export as fasta</a>
     131                       
    117132                        <g:if test="${!editable || assay.runs == null || assay.runs.size() == 0}">
    118133                                <a class="addSequences disabled" onClick="return false;" href="#">Add sequence files</a>
     
    121136                                <a class="addSequences" onClick="showAddFilesDialog(); return false;" href="#">Add sequence files</a>
    122137                        </g:else>
     138                        <g:if test="${editable && assay.numFiles() > 0 }">
     139                                <g:link class="delete" onClick="return confirm( 'Are you sure you want to remove all sequence data from this assay?' );" controller="assay" action="deleteSequenceData" id="${assay.id}">Delete all sequences</g:link>
     140                        </g:if>
    123141                </p>
    124142
  • trunk/grails-app/views/fasta/showProcessResult.gsp

    r34 r49  
    66                <script>
    77                        var entityId = ${entity.id};
     8
     9                        function showSampleDataWarning( select, i ) {
     10                                if( $( 'option:selected', $(select) ).hasClass( 'containsdata' ) ) {
     11                                        $( '#warningSampleContainsData_' + i ).show();
     12                                } else {
     13                                        $( '#warningSampleContainsData_' + i ).hide();
     14                                }
     15                        }
    816                </script>
    917        </head>
     
    1826        </h1>
    1927        <g:if test="${matchedFiles.size() > 0}">
    20                 <h2>Match files with samples</h2>
     28                <p>
     29                        Match each sequencefile with the qualityfile and the sample it belongs to. Samples are already chosen for some files based
     30                        on the given excelsheet or the filenames.
     31                </p>
     32                <p>
     33                        By unchecking a sequence file you can exclude the file from being used.
     34                </p>
     35                 
    2136                <%
    2237                        def sortedSamples = entity.assaySamples.findAll { it.assay.study.canWrite( session.user ) }.sort() { a,b -> a.sample.name <=> b.sample.name }
     
    3954                                        <td>
    4055                                                <g:hiddenField name="file.${i}.fasta" value="${file.fasta.filename}" />
    41                                                 <g:checkBox name="file.${i}.include" value="${true}" />
     56                                                <g:checkBox name="file.${i}.include" value="${file.checked}" />
    4257                                        </td>
    4358                                        <td class="name">${file.fasta.originalfilename}</td>
     
    5166                                                </g:else>
    5267                                        </td>
    53                                         <td>
     68                                        <td nowrap>
    5469                                                <g:if test="${sortedSamples.size()}">
    55                                                         <select name="file.${i}.assaySample">
     70                                                        <select name="file.${i}.assaySample" onChange="showSampleDataWarning(this, ${i});">
    5671                                                                <g:each in="${sortedSamples}" var="assaySample">
    5772                                                                        <option
    5873                                                                                <g:if test="${file.assaySample?.id == assaySample.id}">
     74                                                                                        <g:set var="selectedAssaySample" value="${assaySample}" />
    5975                                                                                        selected="selected"
    6076                                                                                </g:if>
    61                                                                                 value="${assaySample.id}">${assaySample.sample.name}</option>
     77                                                                                value="${assaySample.id}" class="<g:if test="${assaySample?.sequenceData?.size() > 0}">containsdata</g:if><g:else>nodata</g:else>">${assaySample.sample.name}</option>
    6278                                                                </g:each>
    6379                                                        </select>
     80                                                        <span id="warningSampleContainsData_${i}" class="warningSampleContainsData" <g:if test="${selectedAssaySample?.sequenceData?.size() > 0}">style="display: inline;"</g:if>>
     81                                                                <img src="${fam.icon( name: "exclamation" ) }" title="This sample already contains sequences. Adding sequences doesn't replace the old ones." />
     82                                                        </span>
    6483                                                </g:if>
    6584                                                <g:else>
     
    101120       
    102121        <p>
    103                 <g:link controller="${entityType}" action="show" id="${entity.id}">Return to ${entityType}</g:link>     
     122                <g:link controller="fasta" action="returnWithoutSaving" params="[entityType: entityType, id: entity.id]">Return to ${entityType}</g:link>       
    104123        </p>
    105124</body>
  • trunk/grails-app/views/layouts/main.gsp

    r34 r49  
    1717                <script type="text/javascript" src="${resource(dir: 'js', file: 'topnav.js')}"></script>
    1818                <script type="text/javascript" src="${resource(dir: 'js', file: 'jquery.dataTables.min.js')}"></script>
     19
     20                <script type="text/javascript" src="${resource(dir: 'js', file: 'jquery.flot.min.js')}"></script>
     21                <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="${resource(dir: 'js', file: 'excanvas.min.js')}"></script><![endif]-->
     22
    1923                <script type="text/javascript">
    2024                        var baseUrl = '${resource(dir: '')}';
  • trunk/grails-app/views/run/_addFilesDialog.gsp

    r44 r49  
    55                <input type="hidden" name="entityType" value="run" />
    66                <p>
    7                         Select sequence and quality files to upload. It is possible to zip the files before upload.
     7                        Select sequence (.fna, .fasta) and quality (.fqa, .qual) files to upload. It is possible to zip the files before upload. You can add multiple files, if needed.
    88                </p>
    99                <p>
    1010                        The filenames should match the sample names of the samples they belong to. It is also possible to provide an
    11                         excel sheet to describe which file belongs to which sample. The format should be like this <g:link controller="run" action="downloadMatchExcel" id="${run.id}">example</g:link>.
     11                        excel sheet to describe which file belongs to which sample. The format should be like this <g:link controller="run" action="downloadMatchExcel" id="${run.id}">example</g:link>, with each fasta file appearing once.
    1212                </p>
    1313                <g:fileUpload name="sequencefiles" value="" multiple="${true}" onUpload="handleFileUploadData" onDelete="deleteProcessButton" />
  • trunk/grails-app/views/run/index.gsp

    r34 r49  
    3535                                                <th width="5" class="nonsortable"></th>
    3636                                                <th width="5" class="nonsortable"></th>
     37                                                <th width="5" class="nonsortable"></th>
    3738                                        </tr>
    3839                                </thead>
     
    4748                                                        <td>
    4849                                                                <g:if test="${run.deletable(user)}">
    49                                                                         <g:link controller="run" action="deleteRun" id="${run.id}"><img src="${fam.icon( name: 'delete' )}" alt="delete" title="delete" /></g:link>
     50                                                                        <g:link onClick="return confirm( 'Are you sure you want to delete this run?' );" controller="run" action="deleteRun" id="${run.id}"><img src="${fam.icon( name: 'delete' )}" alt="delete" title="delete" /></g:link>
    5051                                                                </g:if>
    5152                                                                <g:else>
     
    5354                                                                </g:else>
    5455                                                        </td>
     56                                                        <td>
     57                                                                <g:if test="${run.numSequences() > 0}">
     58                                                                        <g:link controller="run" action="sequenceLengthHistogram" id="${run.id}"><img src="${fam.icon( name: 'chart_bar' )}" alt="Sequence length histogram" title="Sequence length histogram" /></g:link>
     59                                                                </g:if>
     60                                                                <g:else>
     61                                                                        <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                                                                </g:else>
     63                                                        </td>                                                   
    5564                                                </tr>
    5665                                        </g:each>
  • trunk/grails-app/views/run/show.gsp

    r44 r49  
    9292        </g:if>
    9393        <g:else>
    94                 <table class="paginate">
     94                <form id="sampleForm"></form>
     95                <table class="paginate" id="samples">
    9596                        <thead>
    9697                                <tr>
     98                                        <th width="5" class="nonsortable"><input type="checkbox" id="checkAll" onClick="checkAllPaginated(this);" /></th>
    9799                                        <th nowrap>name</th>
    98100                                        <th nowrap>study</th>
     
    103105                                        <th class="nonsortable"></th>
    104106                                        <th class="nonsortable"></th>
     107                                        <th class="nonsortable"></th>
    105108                                </tr>
    106109                        </thead>                       
     
    108111                                <g:each in="${assaySamples}" var="assaySample">
    109112                                        <tr>
     113                                                <td><g:checkBox name="ids" value="${assaySample.id}" checked="${false}" onClick="updateCheckAll(this);" /></td>
    110114                                                <td><a href="#" onClick="showSample(${assaySample.id}, 'run'); return false;">${assaySample.sample.name}</a></td>
    111115                                                <td>${assaySample.assay.study.name}</td>
     
    144148                                                        </g:else>
    145149                                                </td>
     150                                                <td class="button">
     151                                                        <g:if test="${assaySample.numSequences() > 0}">
     152                                                                <g:link controller="assaySample" action="sequenceLengthHistogram" id="${assaySample.id}"><img src="${fam.icon( name: 'chart_bar' )}" alt="Sequence length histogram" title="Sequence length histogram" /></g:link>
     153                                                        </g:if>
     154                                                        <g:else>
     155                                                                <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." />
     156                                                        </g:else>
     157                                                </td>                                                   
    146158                                        </tr>
    147159                                </g:each>
     
    162174                        </g:else>
    163175
     176                        <a class="fasta" href="#" onClick="submitPaginatedForm( $( '#sampleForm' ), '<g:createLink controller="assaySample" action="exportAsFasta" />', '#samples', 'Please select one or more samples to export' ); return false;">Export as fasta</a>
     177
    164178                        <g:if test="${writableAssaySamples.size() == 0 || !run.assays?.size()}">
    165179                                <a class="addSequences disabled" onClick="return false;" href="#">Add sequence files</a>
     
    169183                        </g:else>
    170184
     185                        <g:if test="${writableAssaySamples.size() > 0 && run.numFiles() > 0 }">
     186                                <g:link class="delete" onClick="return confirm( 'Are you sure you want to remove all sequence data from this run? Only sequences are removed from samples you have write access to.' );" controller="run" action="deleteSequenceData" id="${run.id}">Delete all sequences</g:link>
     187                        </g:if>
     188                        <g:else>
     189                                <a class="delete disabled" onClick="return false;" href="#">Delete all sequences</a>
     190                        </g:else>
    171191                </p>
    172192                <g:if test="${writableAssaySamples.size() > 0}">
  • trunk/web-app/css/buttons.css

    r26 r49  
    3939        background-image:  url(../plugins/famfamfam-1.0.1/images/icons/application_edit.png);
    4040}
     41
     42.options a.delete {
     43        background-image: url(../plugins/famfamfam-1.0.1/images/icons/delete.png);
     44}
  • trunk/web-app/css/fileuploader.new.css

    r25 r49  
    2121.upload_info { color: #006DBA; display: inline;  margin-left: 5px; }
    2222.upload_info a { text-decoration: underline; }
    23 .upload_del img { border-width: 0; padding-left: 6px; }
     23.upload_del img { border-width: 0; padding-left: 6px; float: none !important; }
    2424.upload_del { display: none; }
    2525.upload_info .error { color: red; }
  • trunk/web-app/css/metagenomics.css

    r44 r49  
    504504.uploadContainer { display: inline-block; *display: inline; zoom: 1 }
    505505
     506.warningSampleContainsData { display: none; }
     507
    506508/* Makes sure the filenames in the dialog don't exceed 200px */
    507509.dataTables_wrapper .uploadedFile { display: inline-block; zoom: 1; *display: inline; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; width: 190px; height: 15px; }
  • trunk/web-app/js/addFilesDialog.js

    r25 r49  
    1616
    1717function showAddFilesDialog() {
    18         $( "#addFilesDialog" ).dialog( "open" );
     18        $( "#addFilesDialog" ).dialog( "open" ).width( 550 ).height( 400 );
    1919}
    2020
Note: See TracChangeset for help on using the changeset viewer.