Skip to content

Commit

Permalink
Merge pull request #1486 from reynoldtan/tv4g2-issue1482-chadoCvtermA…
Browse files Browse the repository at this point in the history
…utocomplete

Chado CV Term Autocomplete Functionality
  • Loading branch information
laceysanderson committed May 10, 2023
2 parents 2b4a7c0 + 9d51eb1 commit 3ff13d4
Show file tree
Hide file tree
Showing 3 changed files with 241 additions and 1 deletion.
108 changes: 108 additions & 0 deletions tripal_chado/src/Controller/ChadoCVTermAutocompleteController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php

namespace Drupal\tripal_chado\Controller;

use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;

/**
* Controller routines for the chado CV Term autocomplete.
*/
class ChadoCVTermAutocompleteController extends ControllerBase {
/**
* Contoller method, autocomplete cvterm name.
*
* @param Request request
* @param int $cv
* @TODO: Limit the match of term to a CV.
* @param int $count
* Desired number of matching names to suggest.
* Default to 5 items.
*
* @return Json Object
* Matching cvterm rows where each row is formatted as string:
* cvterm.name (db.name:dbxref.accession) and is the value for
* the object keys label and value.
*/
public function handleAutocomplete(Request $request, int $count = 5) {
// Array to hold matching cvterm names.
$response = null;

if ($request->query->get('q')) {
// Get typed in string input from the URL.
$string = trim($request->query->get('q'));

if (strlen($string) > 1 && $count > 0) {
// Proceed to autocomplete when string is at least 2 characters
// long and result count is set to a value greater than 0.

// Transform string as a search keyword pattern.
$keyword = '%' . strtolower($string) . '%';

// Query cvterm (joins: dbxref - accession and db - dn name) for names matching
// the keyword pattern and return each row in the format specified.
// Tables indicate schema sequence number #1 to use default schema.
$sql = sprintf("
SELECT ct.name AS term, db.name AS dbname, dx.accession
FROM {1:cvterm} AS ct
LEFT JOIN {1:dbxref} AS dx USING(dbxref_id)
LEFT JOIN {1:db} USING(db_id)
WHERE ct.name LIKE :keyword ORDER BY ct.name ASC LIMIT %d
", $count);

// Prepare Chado database connection and execute sql query by providing value
// for :keyword placeholder text.
$connection = \Drupal::service('tripal_chado.database');
$results = $connection->query($sql, [':keyword' => $keyword]);

// Compose response result.
if ($results) {
foreach ($results as $record) {
$term = $record->term . ' (' . $record->dbname . ':' . $record->accession . ')';
$response[] = [
'value' => $term, // Value returned and value displayed by textfield.
'label' => $term // Value shown in the list of options.
];
}
}
}
}

return new JsonResponse($response);
}

/**
* Fetch the cvterm.cvterm_id given a cvterm name (db.name:dbxref.accession)
* value returned by the handler method above.
*
* @param string $term
* String value returned by authocomplete handler method.
*
* @return integer
* Id number corresponding to chado.cvterm_id field of the matching term
* or 0 if no match was found.
*/
public static function getCVtermId(string $term): int {
$id = 0;

if (strlen($term) > 0) {
$sql = "
SELECT ct.cvterm_id FROM {1:cvterm} AS ct
LEFT JOIN {1:dbxref} AS dx USING(dbxref_id)
LEFT JOIN {1:db} USING(db_id)
WHERE CONCAT(ct.name, ' (', db.name, ':', dx.accession, ')') = :term
LIMIT 1
";

$connection = \Drupal::service('tripal_chado.database');
$result = $connection->query($sql, [':term' => $term]);

if($result) {
$id = $result->fetchField();
}
}

return $id;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<?php

namespace Drupal\Tests\tripal_chado\Functional;

use Drupal\Tests\tripal_chado\Functional\ChadoTestBrowserBase;
use Drupal\tripal_chado\Controller\ChadoCVTermAutocompleteController;
use Symfony\Component\HttpFoundation\Request;

/**
* Test autocomplete cvterm name.
*/
class ChadoTableCvtermAutocompleteTest extends ChadoTestBrowserBase {
/**
* Registered user with access content privileges.
*
* @var \Drupal\user\Entity\User
*/
private $registered_user;


/**
* Test autocomplete cvterm name.
*/
public function testAutocompleteCvterm() {
// Setup registered user.
$this->registered_user = $this->drupalCreateUser(
['access content'],
);

$this->drupalLogin($this->registered_user);

// Ensure we see all logging in tests.
\Drupal::state()->set('is_a_test_environment', TRUE);

// Create a new test schema for us to use.
$connection = $this->createTestSchema(ChadoTestBrowserBase::PREPARE_TEST_CHADO);

// Test Handler:
$autocomplete = new ChadoCVTermAutocompleteController();
// Prepare a Request:$request entry. Search for null term
// and suggest at least 5 items.
$request = Request::create(
'chado/cvterm/autocomplete/5',
'GET',
['q' => 'null']
);

$suggest = $autocomplete->handleAutocomplete($request)
->getContent();

// Find if null cvterm was suggested.
$null_found = 0;
foreach(json_decode($suggest) as $item) {
if (str_contains($item->value, 'null')) {
$null_found++;
}
}

$this->assertTrue($null_found > 0);


// Test Get Id.
// Each item in the result for term null should have
// an integer value which is the cvterm id number.
foreach(json_decode($suggest) as $item) {
// ChadoCVTermAutocompleteController::getCVtermId()
$id = $autocomplete->getCVtermId($item->value);

$this->assertNotNull($id);
$this->assertIsInt($id);
}


// Test limit.
$request = Request::create(
'chado/cvterm/autocomplete/5',
'GET',
['q' => 'ul']
);
// This will return > 6 terms ie. n[ul]l, vocab[ul]ary, pop[ul]ation, form[ul]a etc.
// but should only suggest exactly 6 items.

$suggest = $autocomplete->handleAutocomplete($request, 6)
->getContent();

$this->assertEquals(count(json_decode($suggest)), 6);


// Test exact term and 1 suggestion (exact match).
$query = $connection->select('1:cvterm', 'c');
$query->condition('c.name', 'null', '=');
$query->fields('c', ['cvterm_id']);
$null_cvterm_id = $query->execute()->fetchField();

$request = Request::create(
'chado/cvterm/autocomplete/5',
'GET',
['q' => 'null']
);

$suggest = $autocomplete->handleAutocomplete($request, 1)
->getContent();

foreach(json_decode($suggest) as $item) {
// ChadoCVTermAutocompleteController::getCVtermId()
$id = $autocomplete->getCVtermId($item->value);

$this->assertNotNull($id);
$this->assertIsInt($id);
$this->assertEquals($id, $null_cvterm_id);
}


// Test invalid values as id passed to GetId.
// Not found
$not_ids = [0, 'lorem.ipsum', 'null', '@$#%', 'null (abc:xyz)', ' ', '.'];
foreach($not_ids as $i) {
$id = $autocomplete->getCVtermId($i);
$this->assertEquals($id, 0);
}
}
}
12 changes: 11 additions & 1 deletion tripal_chado/tripal_chado.routing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,14 @@ entity.chado_term_mapping.delete_form:
_entity_form: 'chado_term_mapping.delete'
_title: 'Delete Mapping'
requirements:
_permission: 'administer tripal'
_permission: 'administer tripal'


tripal_chado.cvterm_autocomplete:
path: 'chado/cvterm/autocomplete/{count}'
defaults:
_controller: '\Drupal\tripal_chado\Controller\ChadoCVTermAutocompleteController::handleAutocomplete'
_title: 'Chado Vocabulary Term Autocomplete'
_format: json
requirements:
_permission: 'access content'

0 comments on commit 3ff13d4

Please sign in to comment.