Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
iimog committed Nov 4, 2016
2 parents 3d1cf13 + 022fa92 commit ff0a844
Show file tree
Hide file tree
Showing 57 changed files with 1,775 additions and 292 deletions.
3 changes: 3 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"presets": ["es2015"]
}
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ install:
- conda install --yes numpy scipy h5py
- pip install biom-format --user
- composer install
- . $HOME/.nvm/nvm.sh
- nvm install stable
- nvm use stable
- npm install
- node_modules/bower/bin/bower install

Expand All @@ -31,3 +34,4 @@ before_script:

script:
- phpunit
- gulp test
39 changes: 15 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,43 +16,34 @@ npm install
bower install
composer install
```
**Important: do not edit the js or css files in `web/assets` directly.
They are generated with `gulp scss` and `gulp babel` from `app/Resources/client`**

### Test, LINT, generate API, ...
Use gulp for all those things:
```{bash}
# PHP Code Sniffer
gulp phpcs
# apigen API generator
gulp apigen
# phpunit testing
# before testing php you need to set up a database and configure it
cp test/php/config.php.generated test/php/config.php
# modify the config.php
gulp phpunit
vendor/bin/phpunit
# check js files for lints
gulp jshint
# javascript testing with jasmine
gulp jasmine
# javascript testing (of helpers)
gulp test
# check scss files for lints
gulp sassLint
# generate css files from scss
gulp sass
# run a performance test with artillery (make sure to set the appropriate host)
# npm install -g artillery
artillery run test/artillery/simple.json
# artillery report artillery_report_<timestamp>.json
# run a ui test with selenium (make sure to set the appropriate host)
# pip install -U selenium
python test/selenium/simple.py
```
## Changes
### 0.3.2 <2016-11-04>
- Add organism_id mapping to project details page
- Export project as biom (v1)
- Download mapping results as csv
- Mapping to organism_id by species name
- Make project ID and comment editable
- Improve project details page
- Use pre-compilation of jsx files with babel
- Re-write message system (using React)
### 0.3.1 <2016-10-20 Th>
- Add exception handling to API
- Add generalized WebserviceTestCase
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,4 @@ $(document).ready(function() {
} );
$(document).ready(function() {
$('#metadata_community').DataTable();
} );


} );
File renamed without changes.
64 changes: 64 additions & 0 deletions app/Resources/client/jsx/base/message.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/* global ReactDOM */

// This is the react template,called from showMessageDialog later
function MessageDialog(props) {
return (
<div className={"alert alert-dismissable " + props.type} role="alert">
<button type="button" className="close" aria-label="Close" onClick={() => {removeMessageDialog(props.id)}}>
<span aria-hidden="true">&times;</span>
</button>
{props.message}
</div>
)
}

class MessageArea extends React.Component {
render() {
const messageDialogs = this.props.messages.map((element) => {
return <MessageDialog type={element.type} message={element.message} key={element.key} id={element.key}/>
});
return <div>{messageDialogs}</div>
}
}

/**
* This function generates consecutive uids starting from 0
*/
var uid = (() => {
var id=0;
return () => id++;
})();

let messages = [];

/**
* This function appends a bootstrap dialog to the message area with the given message and type
* @param {type} message - The text that should be shown in the dialog
* @param {type} type - The type (color) of the dialog. Possible values: alert-success, alert-warning, alert-danger, alert-info (default)
* @returns {void}
*/
function showMessageDialog(message, type){
var knownTypes = ['alert-success', 'alert-warning', 'alert-danger', 'alert-info'];
if(knownTypes.indexOf(type) === -1){
var shortTypes = ['success', 'warning', 'danger', 'info'];
if(shortTypes.indexOf(type) === -1){
type = 'alert-info';
} else {
type = "alert-" + type;
}
}
messages.push({message: message, type: type, key: uid()});
updateMessageDialogs();
}

function removeMessageDialog(key) {
messages = messages.filter(message => message.key !== key);
updateMessageDialogs();
}

function updateMessageDialogs() {
ReactDOM.render(
<MessageArea messages={messages}/>,
document.getElementById('global-message-area')
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* @param eolObject {Object} object returned by the eol pages API
* @returns {String} bestName
*/
getBestVernacularNameEOL = function(eolObject){
function getBestVernacularNameEOL(eolObject){
var bestName = "";
if(typeof eolObject.scientificName !== "undefined"){
bestName = eolObject.scientificName;
Expand Down
65 changes: 65 additions & 0 deletions app/Resources/client/jsx/project/details/details.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/* global dbversion */
/* global biom */
/* global internalProjectId */
/* global blackbirdPreviewPath */
$('document').ready(function () {
// Set header of page to project-id
$('.page-header').text(biom.id);

// Fill overview table with values
$('#project-overview-table-id').text(biom.id);
$('#project-overview-table-comment').text(biom.comment);
$('#project-overview-table-rows').text(biom.shape[0]);
$('#project-overview-table-cols').text(biom.shape[1]);
$('#project-overview-table-nnz').text(biom.nnz + " (" + (100 * biom.nnz / (biom.shape[0] * biom.shape[1])).toFixed(2) + "%)");

// Set action if edit dialog is shown
$('#editProjectDialog').on('shown.bs.modal', function () {
$('#editProjectDialogProjectID').val(biom.id);
$('#editProjectDialogComment').val(biom.comment);
$('#editProjectDialogProjectID').focus();
});

// Set action if edit dialog is saved
$('#editProjectDialogSaveButton').click(function () {
biom.id = $('#editProjectDialogProjectID').val();
biom.comment = $('#editProjectDialogComment').val();
saveBiomToDB();
});

$('#project-export-as-biom-v1').click(() => {
exportProjectAsBiom();
});

});

/**
* Saves the current value of the global biom variable to the postgres database
*/
function saveBiomToDB() {
biom.write().then(function (biomJson) {
var webserviceUrl = Routing.generate('api', {'namespace': 'edit', 'classname': 'updateProject'});
$.ajax(webserviceUrl, {
data: {
"dbversion": dbversion,
"project_id": internalProjectId,
"biom": biomJson
},
method: "POST",
success: function () {
location.reload();
}
});
}, function (failure) {
console.log(failure);
});
}

function exportProjectAsBiom() {
biom.write().then(function (biomJson) {
var blob = new Blob([biomJson], {type: "text/plain"});
saveAs(blob, biom.id+".json");
}, function (failure) {
console.log(failure);
});
}
145 changes: 145 additions & 0 deletions app/Resources/client/jsx/project/details/mapping.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/* global dbversion */
/* global biom */
/* global _ */
$('document').ready(() => {
// Calculate values for mapping overview table
let sampleOrganismIDs = biom.getMetadata({dimension: 'columns', attribute: ['fennec', dbversion, 'organism_id']}).filter(element => element !== null);
let otuOrganismIDs = biom.getMetadata({dimension: 'rows', attribute: ['fennec', dbversion, 'organism_id']}).filter(element => element !== null);
var mappedSamples = sampleOrganismIDs.length;
var percentageMappedSamples = 100 * mappedSamples / biom.shape[1];
var mappedOTUs = otuOrganismIDs.length;
var percentageMappedOTUs = 100 * mappedOTUs / biom.shape[0];

// Add values to mapping overview table
$('#mapping-otu').text(mappedOTUs);
$('#progress-bar-mapping-otu').css('width', percentageMappedOTUs + '%').attr('aria-valuenow', percentageMappedOTUs);
$('#progress-bar-mapping-otu').text(percentageMappedOTUs.toFixed(0) + '%');
$('#mapping-sample').text(mappedSamples);
$('#progress-bar-mapping-sample').css('width', percentageMappedSamples + '%').attr('aria-valuenow', percentageMappedSamples);
$('#progress-bar-mapping-sample').text(percentageMappedSamples.toFixed(0) + '%');

// Add semi-global dimension variable (stores last mapped dimension)
var dimension = 'rows';
var method = 'ncbi_taxid';

// Set action for click on mapping "GO" button
$('#mapping-action-button').on('click', function () {
dimension = $('#mapping-dimension-select').val();
method = $('#mapping-method-select').val();
let ids = getIdsForMethod(method, dimension);
let uniq_ids = ids.filter(value => value !== null);
uniq_ids = _.uniq(uniq_ids);
$('#mapping-action-busy-indicator').show();
$('#mapping-results-section').hide();
if (uniq_ids.length === 0) {
handleMappingResult(dimension, ids, [], method);
} else {
var webserviceUrl = getWebserviceUrlForMethod(method);
$.ajax(webserviceUrl, {
data: {
dbversion: dbversion,
ids: uniq_ids
},
method: 'POST',
success: function (data) {
handleMappingResult(dimension, ids, data, method);
}
});
}
});

/**
* Returns the array with search id for the respective method in the given dimension
* @param method
* @param dimension
* @return {Array}
*/
function getIdsForMethod(method, dimension) {
let ids = [];
if(method === 'ncbi_taxid'){
ids = biom.getMetadata({dimension: dimension, attribute: 'ncbi_taxid'});
} else if(method === 'organism_name'){
ids = biom[dimension].map((element) => element.id);
}
return ids;
}

/**
* Returns the webserviceUrl for the given mapping method
* @param method
* @return {string}
*/
function getWebserviceUrlForMethod(method) {
let method2service = {
'ncbi_taxid': 'byNcbiTaxid',
'organism_name': 'byOrganismName'
};
let webserviceUrl = Routing.generate('api', {'namespace': 'mapping', 'classname': method2service[method]});
return webserviceUrl;
}

/**
* Returns a string representation for the IDs used for mapping in the chosen method
* @param method
* @return {string}
*/
function getIdStringForMethod(method) {
let idString = "";
if (method === 'ncbi_taxid'){
idString = "NCBI taxid";
} else if (method === 'organism_name') {
idString = "Organism name";
}
return idString;
}

/**
* Create the results component from the returned mapping and store result in global biom object
* @param {string} dimension
* @param {Array} idsFromBiom those are the ids used for mapping in the order they appear in the biom file
* @param {Array} mapping from ids to organism_ids as returned by webservice
* @param {string} method of mapping
*/
function handleMappingResult(dimension, idsFromBiom, mapping, method) {
let organism_ids = new Array(idsFromBiom.length).fill(null);
var idsFromBiomNotNullCount = 0;
var idsFromBiomMappedCount = 0;
for (let i = 0; i < idsFromBiom.length; i++) {
if (idsFromBiom[i] !== null) {
idsFromBiomNotNullCount++;
if (idsFromBiom[i] in mapping && mapping[idsFromBiom[i]] !== null) {
idsFromBiomMappedCount++;
organism_ids[i] = mapping[idsFromBiom[i]];
}
}
}
biom.addMetadata({dimension: dimension, attribute: ['fennec', dbversion, 'organism_id'], values: organism_ids});
biom.addMetadata({dimension: dimension, attribute: ['fennec', dbversion, 'assignment_method'], defaultValue: method});
var idString = getIdStringForMethod(method);
$('#mapping-action-busy-indicator').hide();
$('#mapping-results-section').show();
$('#mapping-results').text(`From a total of ${idsFromBiom.length} organisms: ${idsFromBiomNotNullCount} have a ${idString}, of which ${idsFromBiomMappedCount} could be mapped to organism_ids.`);
}

// Set action for click on mapping "Save to database" button
$('#mapping-save-button').on('click', function () {
saveBiomToDB();
});

// Set action for click on mapping "Download as csv" button
$('#mapping-download-csv-button').on('click', function () {
var ids = biom[dimension].map(function (element) {
return element.id;
});
var ids = getIdsForMethod(method, dimension);
var fennec_id = biom.getMetadata({dimension: dimension, attribute: ['fennec', dbversion, 'organism_id']});
var id_header = dimension === 'rows' ? 'OTU_ID' : 'Sample_ID';
let idString = getIdStringForMethod(method);
var csv = `${id_header}\t${idString}\tFennec_ID\n`;
for(var i=0; i<ids.length; i++){
csv += ids[i]+"\t"+ids[i]+"\t"+fennec_id[i]+"\n";
}
var blob = new Blob([csv], {type: "text/plain;charset=utf-8"});
saveAs(blob, "mapping.csv");
});
});
Loading

0 comments on commit ff0a844

Please # to comment.