Skip to content

Commit

Permalink
fix: sample attribute lat lon regex and migration
Browse files Browse the repository at this point in the history
  • Loading branch information
alli83 committed Nov 26, 2024
1 parent ea1a37c commit 653665f
Show file tree
Hide file tree
Showing 5 changed files with 285 additions and 50 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## Unreleased

- Fix #831: Check if longitude and lagitude match the WGS84 format for sample attributes
- Feat #1867: Update the gitlab static application security testing (SAST) job using the Semgrep-based analyzer
- Fix #2066: Max length for attribute value set to 1000 in file admin form
- Feat #1968: Add curators manual for operating tools on bastion server
Expand Down
2 changes: 2 additions & 0 deletions data/dev/attribute.csv
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ id,attribute_name,definition,model,structured_comment_name,value_syntax,allowed_
347,last_modified,the date the file was last modified,modified,last_modified,date,"",1,"",""
356,Description,"description of the sample (may include many attributes as human readable text, but these should also be added as independent attribute:value pairs).","",description,text,"",1,"",""
376,estimated genome size,"An estimate of the size of the genome of the species being studied, in basepairs (Gb, Mb or Kb)","",est_genome_size,text,"",1,"",""
391,geographic location (latitude),"The geographical latitudinal origin of the sample, the value should be reported in decimal degrees and in WGS84 system","",latitude,number,"","","",""
392,geographic location (longitude),"The geographical longitudinal origin of the sample, the value should be reported in decimal degrees and in WGS84 system","",longitude,number,"","","",""
448,alternative accession-biosample,"","",alt_acc_biosample,"","","","",""
455,keyword,"","",keywords,"","","","",""
472,camera parameters,"","",camera_parameters,text,"",m,"",""
Expand Down
136 changes: 89 additions & 47 deletions protected/controllers/AdminSampleController.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public function accessRules()
{
return array(
array('allow', // admin only
'actions'=>array('admin','delete','index','view','create','update'),
'actions'=>array('admin','delete','index','view','create','update', 'checkAttribute'),
'roles'=>array('admin'),
),
array('allow', 'actions' => array('create1', 'choose'), 'users' => array('@')),
Expand Down Expand Up @@ -193,50 +193,43 @@ public function actionChoose() {
public function actionUpdate($id)
{
$model = $this->loadModel($id);
//$old_code= $model->code;
// Uncomment the following line if AJAX validation is needed
// $this->performAjaxValidation($model);

if (isset($_POST['Sample'])) {
$model->attributes = $_POST['Sample'];
$model->name = $_POST['Sample']['name'];

if (strpos($_POST['Sample']['species_id'], ":") !== false) {
$array = explode(":", $_POST['Sample']['species_id']);
$tax_id = $array[0];
if (!empty($tax_id)) {
$species = $this->findSpeciesRecord($tax_id, $model);
$this->updateSampleAttributes($model);
if (!$model->hasErrors()) {
$this->redirect(array('view', 'id' => $model->id));
}
} else {
$model->addError('error', 'Taxon ID is empty!');
}
} else {
if ($sampleAttribute = Yii::$app->request->post('Sample')) {
$model->name = $sampleAttribute['name'];

if (!strpos($sampleAttribute['species_id'], ':')) {
$model->addError('error', 'The input format is wrong, should be tax_id:common_name');
}
}

$species = Species::model()->findByPk($model->species_id);
$array = explode(':', $sampleAttribute['species_id']);
$tax_id = $array[0];

$model->species_id = $species->tax_id . ":";
$has_common_name = false;
if ($species->common_name != null) {
$has_common_name = true;
$model->species_id .= $species->common_name;
}
if (!$tax_id) {
$model->addError('error', 'Taxon ID is empty!');
}

if (!$this->findSpeciesRecord($tax_id, $model)) {
$model->addError('error', 'The species does not exist');
}

if ($model->save()) {
$this->updateSampleAttributes($model);
}

if ($species->scientific_name != null) {
if ($has_common_name) {
$model->species_id .= ",";
if (!$model->hasErrors()) {
$this->redirect(array('view', 'id' => $model->id));
}
$model->species_id .= $species->scientific_name;
}
$this->render('update', array(
'model' => $model,
'species' => $species,
));

$species = Species::model()->findByPk($model->species_id);

$model->species_id = sprintf('%s: %s', $species->tax_id, $species->common_name ?: '');
$model->species_id .= sprintf('%s%s', $species->common_name ? ', ' : '', $species->scientific_name ?: '');

$this->render('update', array(
'model' => $model,
'species' => $species,
));
}

/**
Expand Down Expand Up @@ -315,6 +308,47 @@ protected function performAjaxValidation($model)
}
}

public function actionCheckAttribute()
{
if (!Yii::app()->request->isAjaxRequest) {
throw new CHttpException(400,'Invalid request. An Error occurred');
}

$errorMessage = [];
$attributesList = $_POST['attr'];

foreach (explode('",', $attributesList) as $attributes) {
$attributes = str_replace('"', '', $attributes);
$attributeData = explode('=', $attributes);
if (count($attributeData) === 2) {
$attributeData[0] = trim($attributeData[0]);
if ($attributeData[0] !== 'longitude' && $attributeData[0] !== 'latitude') {
continue;
}

$errorMessage = $this->checkWrongLatLongAttribute($attributeData[0], $attributeData[1]);
}
}

echo CJSON::encode(['messages' => $errorMessage]);
Yii::app()->end();
}

private function checkWrongLatLongAttribute(string $attribute, string $attributeValue): array
{
$errorMessage = [];
if (preg_match('/^[^0-9]*$/', $attributeValue)) {
$errorMessage[] = sprintf('Attribute value for %s doesn\'t match WGS84 decimal format.
For geographic location (country, sea, region) use another attribute name', $attribute);
} else if ($attributeData[0] === 'latitude' && !preg_match('/^[-+]?(?:90(?:\.0+)?|[1-8]?\d(?:\.\d+)?)$/', $attributeValue)) {
$errorMessage[] = sprintf('Attribute value for %s doesn\'t match WGS84 decimal format', $attribute);
} else if ($attributeData[0] === 'longitude' && !preg_match('/^[-+]?(?:180(?:\.0+)?|1[0-7]\d(?:\.\d+)?|\d{1,2}(?:\.\d+)?)$/', $attributeValue)) {
$errorMessage[] = sprintf('Attribute value for %s doesn\'t match WGS84 decimal format', $attribute);
}

return $errorMessage;
}

/**
* Upate sample attribute
*
Expand All @@ -332,20 +366,28 @@ private function updateSampleAttributes($model)
$sampleAttribute->sample_id = $model->id;
$attributes = str_replace('"', '', $attributes);
$attributeData = explode('=', $attributes);
if (count($attributeData) == 2) {
if (count($attributeData) === 2) {
// Get attribute model
$attribute = Attributes::model()->findByAttributes(array('structured_comment_name' => trim($attributeData[0])));
if (!$attribute) {
$model->addError('error', 'Attribute name for the input ' . $attributeData[0] . "=" . $attributeData[1] . ' is not valid - please select a valid attribute name!');
} else {
// Let's save the new sample attribute
$sampleAttribute->value = trim($attributeData[1]);
$sampleAttribute->attribute_id = $attribute->id;
if (!$sampleAttribute->save(true)) {
foreach ($sampleAttribute->getErrors() as $errors) {
foreach ($errors as $errorMessage) {
$model->addError('error', $errorMessage);
}

continue;
}

if ($attributeData[0] === 'longitude' || $attributeData[0] === 'latitude') {
if ($this->checkWrongLatLongAttribute($attributeData[0], $attributeData[1])) {
continue;
}
}

// Let's save the new sample attribute
$sampleAttribute->value = trim($attributeData[1]);
$sampleAttribute->attribute_id = $attribute->id;
if (!$sampleAttribute->save()) {
foreach ($sampleAttribute->getErrors() as $errors) {
foreach ($errors as $errorMessage) {
$model->addError('error', $errorMessage);
}
}
}
Expand Down
84 changes: 81 additions & 3 deletions protected/views/adminSample/_form.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@
<p class="note">Fields with <span class="required">*</span> are required.</p>

<?php if ($model->hasErrors()) : ?>
<div class="alert alert-danger">
<div id="sample_error" class="alert alert-danger">
<?php echo $form->errorSummary($model); ?>
</div>
<?php endif; ?>

<div id="ajax-error" class='alert alert-danger' style="display: none;">An error occurred</div>

<div class="form-group">
<?php echo $form->labelEx($model, 'species_id', array('class' => 'control-label')); ?>
<?php
Expand Down Expand Up @@ -65,10 +67,86 @@

<div class="pull-right btns-row">
<a href="/adminSample/admin" class="btn background-btn-o">Cancel</a>
<?php echo CHtml::submitButton($model->isNewRecord ? 'Create' : 'Save', array('class' => 'btn background-btn')); ?>
<button id='checkAttribute' type='button' class="btn btn-primary"><?php echo $model->isNewRecord ? 'Create' : 'Save'?></button>

</div>

<div class='modal fade' id='confirmation_sample_modal' role='dialog'>
<div class='modal-dialog modal-lg'>
<div class='modal-content'>
<div class='modal-header'>
<button type='button' class='close' data-dismiss='modal'>&times;</button>
<h4 class='modal-title'>Important</h4>
</div>
<div class='modal-body'>
<div id='check-attribute-warning' class='alert alert-danger' style='display: none;'>
</div>
<div id="check-attribute-confirmation" class="mt-4">
</div>
</div>
<div class='modal-footer'>
<a id="hideModal" class='btn background-btn-o'>Cancel</a>
<?php echo CHtml::submitButton('Confirm', array('id' => 'submit_confirmation', 'class' => 'btn background-btn')); ?>
</div>
</div>

</div>
</div>
<?php $this->endWidget(); ?>
</div>

</div>
</div>
<script>
$(document).ready(function() {
$('#hideModal').click(function(e) {
$('#confirmation_sample_modal').modal('hide');
})

$('#checkAttribute').click(function (e) {
e.preventDefault()

let myWarning = $('#check-attribute-warning')[0];
let myConfirmation = $('#check-attribute-confirmation')[0];
myWarning.innerHTML = '';
myWarning.style.display = 'none'
myConfirmation.innerHTML = '';
$('#submit_confirmation')[0].style.display = ''
$('#ajax-error')[0].style.display = 'none';

$.ajax({
url: "<?php echo Yii::app()->createUrl('adminSample/checkAttribute') ?> ",
type: 'POST',
data: {
attr: $('.form').find("textarea[name='Sample[attributesList]']").val()
},
dataType: 'json',
success: function(response) {
$('#confirmation_sample_modal').modal('show');
let messages = response.messages

if (!messages.length) {
let el = document.createElement('div');
el.textContent= 'Are you sure you want to continue?'
el.className = 'mt-4'
myConfirmation.appendChild(el);

return;
}

myWarning.style.display = 'block'
$('#submit_confirmation')[0].style.display = 'none'

messages.forEach((message) => {
let el = document.createElement('li');
el.textContent = message;

myWarning.appendChild(el);
})
},
error: function(xhr, status, error) {
$('#ajax-error')[0].style.display = 'block';
}
});
});
});
</script>
Loading

0 comments on commit 653665f

Please sign in to comment.