Skip to content

Commit

Permalink
Merge pull request #1554 from reynoldtan/tv4g2-issue1553-chadoProject…
Browse files Browse the repository at this point in the history
…Autocomplete

Project Autocomplete Controller
  • Loading branch information
laceysanderson committed Jul 5, 2023
2 parents ab64c68 + 32db0d8 commit 8c73521
Show file tree
Hide file tree
Showing 3 changed files with 340 additions and 1 deletion.
131 changes: 131 additions & 0 deletions tripal_chado/src/Controller/ChadoProjectAutocompleteController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php

namespace Drupal\tripal_chado\Controller;

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

/**
* Controller, Chado Project Autocomplete.
*/
class ChadoProjectAutocompleteController extends ControllerBase {
/**
* Controller method, autocomplete project name.
*
* @param Request request
* @param int $type_id
* Project type set in projectprop.type_id to restrict projects to specific type.
* Default to 0, return projects regardless of type.
* Must be declared in autocomplete route parameter ie. ['type_id' => 0].
* @param int $count
* Desired number of matching names to suggest.
* Default to 5 items.
* Must be declared in autocomplete route parameter ie. [count => 5].
*
* @return Json Object
* Matching project rows in an array where project name
* is both the value to the array keys label and value.
*/
public function handleAutocomplete(Request $request, int $type_id = 0, int $count = 5) {
// Array to hold matching project records.
$response = [];

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

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

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

if ($type_id > 0) {
// Restrict to type provided by type_id in the route parameter.
$sql = "SELECT name FROM {1:project} AS p LEFT JOIN {1:projectprop} AS t USING (project_id)
WHERE LOWER(p.name) LIKE :keyword AND t.type_id = :type_id ORDER BY p.name ASC LIMIT %d";
$args = [':keyword' => $keyword, ':type_id' => $type_id];
}
else {
// Match projects regardless of type.
$sql = "SELECT name FROM {1:project} WHERE LOWER(name) LIKE :keyword ORDER BY name ASC LIMIT %d";
$args = [':keyword' => $keyword];
}

// Prepare Chado database connection and execute sql query by providing value
// to :keyword and/or :type_id placeholder text.
$connection = \Drupal::service('tripal_chado.database');
$query = sprintf($sql, $count);
$results = $connection->query($query, $args);

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

return new JsonResponse($response);
}

/**
* Fetch the project id number, given a project name value.
*
* @param string $project
* Project name value.
*
* @return integer
* Project id number of the project name or 0 if no matching
* project record was found.
*/
public static function getProjectId(string $project): int {
$id = 0;

if (!empty($project)) {
$sql = "SELECT project_id FROM {1:project} WHERE name = :name LIMIT 1";

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

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

return $id;
}

/**
* Fetch the project name, given a project id number.
*
* @param int $project
* Project id number value.
*
* @return string
* Corresponding project name of the project id number or
* empty string if no matching project record was found.
*/
public static function getProjectName(int $project): string {
$name = '';

if ($project > 0) {
$sql = "SELECT name FROM {1:project} WHERE project_id = :project_id LIMIT 1";

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

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

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

namespace Drupal\Tests\tripal_chado\Functional;

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

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


/**
* Test autocomplete project name.
*/
public function testAutocompleteProject() {
// 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);

$projects = [
'Project Okay',
'Project Good',
'Project Best',
'Project Winner',
'Project Super',
'Project Great',
'Project Green',
'Awesome Project',
'Wow Project',
'Yes Project',
];

// Create test project records and projects with type_id set.
$insert_project = "
INSERT INTO {1:project} (name, description)
VALUES
('$projects[0]', '$projects[0] description'),
('$projects[1]', '$projects[1] description'),
('$projects[2]', '$projects[2] description'),
('$projects[3]', '$projects[3] description'),
('$projects[4]', '$projects[4] description'),
('$projects[5]', '$projects[5] description'),
('$projects[6]', '$projects[6] description'),
('$projects[7]', '$projects[7] description'),
('$projects[8]', '$projects[8] description'),
('$projects[9]', '$projects[9] description')
";
$connection->query($insert_project);

// Set type_id of Null on projects: Good, Winner and Great.
$ids = $connection->query("
SELECT project_id FROM {1:project} WHERE name
IN ('Project Good', 'Project Winner', 'Project Great')
")->fetchAllKeyed(0, 0);

$ids = array_values($ids);
//var_dump($ids);

$insert_type = "
INSERT INTO {1:projectprop} (project_id, type_id)
VALUES
($ids[0], 1),
($ids[1], 1),
($ids[2], 1)
";
$connection->query($insert_type);

$m = $connection->query("select * from {1:projectprop}")
->fetchAllKeyed(0, 1);

$autocomplete = new ChadoProjectAutocompleteController();
$this->assertNotNull($autocomplete);

// Any project regardless of type.
$request = Request::create(
'chado/project/autocomplete/0/10',
'GET',
['q' => 'project']
);

// Test Limit/count.
// Request will return all projects (10 rows) but suggest 1 - 10:
// Error on 0 count.
$suggest_count = range(0, 10);
foreach($suggest_count as $count) {
if ($count > 0) {
$suggest = $autocomplete->handleAutocomplete($request, 0, $count)
->getContent();

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

// Each suggestion matches the projects that were inserted.
foreach(json_decode($suggest) as $item) {
$is_found = (in_array($item->value, $projects)) ? TRUE : FALSE;
$this->assertTrue($is_found);
}
}
else {
$suggest = $autocomplete->handleAutocomplete($request, 0, $count)
->getContent();

$this->assertEquals($suggest, '[]');
}
}

// Restrict to project with projectprop.type_id set to null (id: 1).
// Will return - 'Project Good', 'Project Winner', 'Project Great'.
$request = Request::create(
'chado/project/autocomplete/1/10',
'GET',
['q' => 'project']
);

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

// Test Limit/count.
// Request will return 3 projects but suggest 1 - 3:
$suggest_count = range(1, 3);
foreach($suggest_count as $count) {
$suggest = $autocomplete->handleAutocomplete($request, 0, $count)
->getContent();

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

// Each suggestion matches the projects that were inserted.
foreach(json_decode($suggest) as $item) {
$is_found = (in_array($item->value, $projects)) ? TRUE : FALSE;
$this->assertTrue($is_found);
}
}

// Test partial keyword.
// Will return Project Great and Project Green.
$request = Request::create(
'chado/project/autocomplete/0/10',
'GET',
['q' => 'gre']
);

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

foreach(json_decode($suggest) as $item) {
$is_found = (in_array($item->value, ['Project Great', 'Project Green'])) ? TRUE : FALSE;
$this->assertTrue($is_found);
}

// Test getProjectName().
// Ids of project with type_id set. see insert query above.
$id_name = [];
foreach($ids as $project) {
$name = ChadoProjectAutocompleteController::getProjectName($project);
$id_name[] = $name;
$match = (in_array($name, ['Project Good', 'Project Winner', 'Project Great'])) ? TRUE : FALSE;
$this->assertTrue($match);
}

// Not found.
$not_found = ChadoProjectAutocompleteController::getProjectName(11111111111111111);
$this->assertEquals($not_found, '');

// Test getProjectId().
// Ids of project with type_id set. see insert query above.
foreach($id_name as $project) {
$id = ChadoProjectAutocompleteController::getProjectId($project);
$match = (in_array($id, $ids)) ? TRUE : FALSE;
$this->assertTrue($match);
}

// Not found.
$not_found = ChadoProjectAutocompleteController::getProjectId('Project Not Found');
$this->assertEquals($not_found, 0);
}
}
17 changes: 16 additions & 1 deletion tripal_chado/tripal_chado.routing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,4 +145,19 @@ tripal_chado.cvterm_autocomplete:
_title: 'Chado Vocabulary Term Autocomplete'
_format: json
requirements:
_permission: 'access content'
_permission: 'access content'

tripal_chado.project_autocomplete:
path: 'chado/project/autocomplete/{type_id}/{count}'
defaults:
_controller: '\Drupal\tripal_chado\Controller\ChadoProjectAutocompleteController::handleAutocomplete'
_title: 'Chado Project Autocomplete'
_format: json
requirements:
_permission: 'access content'
options:
parameters:
type_id:
type: integer
count:
type: integer

0 comments on commit 8c73521

Please sign in to comment.