fix password reset link.
[sgn.git] / lib / SGN / Controller / AJAX / Onto.pm
blobfee59b0a3b7e52ebf3bd03a95ba1921c178dc844
1 =head1 NAME
3 SGN::Controller::AJAX::Onto - a REST controller class to provide the
4 backend for the SGN ontology browser
6 =head1 DESCRIPTION
8 Essentially provides four services: roots, children, parents, and
9 cache, that the SGN ontology browser relies on. Output is JSON, as a
10 list of hashes, that has the following keys: accession, has_children,
11 cvterm_name, cvterm_id, and for some functions, parent.
13 The ontologies that should be displayed must be configured in the configuration file (sgn.conf or sgn_local.conf for SGN). Insert a line of the following format into the conf file:
15 C<onto_root_namespaces GO (Gene Ontology), PO (Plant Ontology), SO (Sequence Ontology), PATO (Phenotype and Trait Ontology), SP (Solanaceae Ontology)>
17 where onto_root_namespaces is the conf key, "GO" the two letter code of the ontology (as it appears in the db database table), and in parenthesis is the human readable name of the ontology.
19 =head1 AUTHOR
21 Lukas Mueller <lam87@cornell.edu>
23 =head1 PUBLIC ACTIONS
25 =cut
27 package SGN::Controller::AJAX::Onto;
29 use Moose;
30 use SGN::Model::Cvterm;
31 use CXGN::Chado::Cvterm;
32 use CXGN::Onto;
33 use Data::Dumper;
34 use JSON;
36 use namespace::autoclean;
38 BEGIN { extends 'Catalyst::Controller::REST' }
40 __PACKAGE__->config(
41 default => 'application/json',
42 stash_key => 'rest',
43 map => { 'application/json' => 'JSON', 'text/html' => 'JSON' },
46 =head2 compose_trait
48 Creates a new term in the designated composed trait cv and links it to component terms through cvterm_relationship
50 =cut
52 sub compose_trait: Path('/ajax/onto/store_composed_term') Args(0) {
54 my $self = shift;
55 my $c = shift;
57 #my @ids = $c->req->param("ids[]");
58 #print STDERR "Ids array for composing in AJAX Onto = @ids\n";
60 my $new_trait_names = decode_json $c->req->param("new_trait_names");
61 #print STDERR Dumper $new_trait_names;
62 my $new_terms;
63 eval {
64 my $onto = CXGN::Onto->new( { schema => $c->dbic_schema('Bio::Chado::Schema', 'sgn_chado') } );
65 $new_terms = $onto->store_composed_term($new_trait_names);
67 if ($@) {
68 $c->stash->{rest} = { error => "An error occurred saving the new trait details: $@" };
70 else {
71 my $message = '';
72 my @names;
73 foreach (@$new_terms){
74 $message .= 'Saved new trait <a href="/cvterm/'.$_->[0].'/view">'.$_->[1].'</a><br>';
75 push @names, $_->[1];
77 $c->stash->{rest} = { success => $message,
78 names => \@names };
83 =head2 get_trait_from_exact_components
85 searches for and returns (if found) a composed trait that contains the exact components supplied
87 =cut
89 sub get_trait_from_exact_components: Path('/ajax/onto/get_trait_from_exact_components') Args(0) {
91 my $self = shift;
92 my $c = shift;
93 my @component_ids = $c->req->param("ids[]");
94 my $schema = $c->dbic_schema('Bio::Chado::Schema', 'sgn_chado');
96 my $trait_id = SGN::Model::Cvterm->get_trait_from_exact_components($schema, \@component_ids);
97 if (!$trait_id) {
98 $c->stash->{rest} = { error => "No exact matches found."};
100 else {
101 $c->stash->{rest} = { trait_id => $trait_id };
105 =head2 get_trait_from_component_categories
107 searches for and returns traits that contain one of the ids from each id category supplied
109 =cut
111 sub get_traits_from_component_categories: Path('/ajax/onto/get_traits_from_component_categories') Args(0) {
113 my $self = shift;
114 my $c = shift;
115 my @allowed_composed_cvs = split ',', $c->config->{composable_cvs};
116 my $composable_cvterm_delimiter = $c->config->{composable_cvterm_delimiter};
117 my $composable_cvterm_format = $c->config->{composable_cvterm_format};
118 my @object_ids = $c->req->param("object_ids[]");
119 my @attribute_ids = $c->req->param("attribute_ids[]");
120 my @method_ids = $c->req->param("method_ids[]");
121 my @unit_ids = $c->req->param("unit_ids[]");
122 my @trait_ids = $c->req->param("trait_ids[]");
123 my @tod_ids = $c->req->param("tod_ids[]");
124 my @toy_ids = $c->req->param("toy_ids[]");
125 my @gen_ids = $c->req->param("gen_ids[]");
127 print STDERR "Obj ids are @object_ids\n Attr ids are @attribute_ids\n Method ids are @method_ids\n unit ids are @unit_ids\n trait ids are @trait_ids\n tod ids are @tod_ids\n toy ids are @toy_ids\n gen ids are @gen_ids\n";
128 my $schema = $c->dbic_schema('Bio::Chado::Schema', 'sgn_chado');
130 my $traits = SGN::Model::Cvterm->get_traits_from_component_categories($schema, \@allowed_composed_cvs, $composable_cvterm_delimiter, $composable_cvterm_format, {
131 object => \@object_ids,
132 attribute => \@attribute_ids,
133 method => \@method_ids,
134 unit => \@unit_ids,
135 trait => \@trait_ids,
136 tod => \@tod_ids,
137 toy => \@toy_ids,
138 gen => \@gen_ids,
141 if (!$traits) {
142 $c->stash->{rest} = { error => "No matches found."};
144 else {
145 $c->stash->{rest} = {
146 existing_traits => $traits->{existing_traits},
147 new_traits => $traits->{new_traits}
153 =head2 children
155 Public Path: /<ns>/children
157 L<Catalyst::Action::REST> action.
159 Provides a list of child terms, each child term being a hashref (or
160 equivalent) with keys accession, cvterm_name, cvterm_id, has_children,
161 and relationship.
163 =cut
165 sub children : Local : ActionClass('REST') { }
167 sub children_GET {
168 my ( $self, $c ) = @_;
169 my $schema = $c->dbic_schema('Bio::Chado::Schema', 'sgn_chado');
171 my ($db_name, $accession) = split ":", $c->request->param('node');
173 my $db = $schema->resultset('General::Db')->search({ name => uc($db_name) })->first();
174 my $dbxref = $db->find_related('dbxrefs', { accession => $accession });
176 my $cvterm = $dbxref->cvterm;
178 my $cvrel_rs = $cvterm->children(); # returns a result set
180 my @response_list = ();
181 while (my $cvrel_row = $cvrel_rs->next()) {
182 my $relationship_node = $cvrel_row->type();
183 my $child_node = $cvrel_row->subject();
185 #only report back children of the same cv namespace
186 # if ($child_node->cv_id() != $cvterm->cv_id()) {
187 # next();
190 my $responsehash = $self->flatten_node($child_node, $relationship_node);
191 push @response_list, $responsehash;
193 @response_list = sort { lc $a->{cvterm_name} cmp lc $b->{cvterm_name} } @response_list;
194 $c->stash->{rest} = \@response_list;
198 =head2 parents
200 Public Path: /<ns>/parents
202 L<Catalyst::Action::REST> action.
204 Returns a list of hashes with parents information in json, a list of
205 hashrefs with the keys: accession, relationship, has_children, cvterm_id
206 and cvterm_name.
208 =cut
210 sub parents : Local : ActionClass('REST') { }
212 sub parents_GET {
213 my ($self, $c) = @_;
215 my $schema = $c->dbic_schema('Bio::Chado::Schema', 'sgn_chado');
217 my ($db_name, $accession) = split ":", $c->request->param('node');
218 my $dbxref;
219 my %response;
220 my $db = $schema->resultset('General::Db')->search({ 'upper(name)' => uc($db_name) })->first();
221 if (!$db || !$accession) {
222 #not sure we need here to send an error key, since cache is usually called after parents (? )
223 $response{error} = "Did not pass a legal ontology term ID! ( $db_name : $accession)";
224 $c->stash->{rest} = \%response;
225 return;
227 $dbxref = $db->find_related('dbxrefs', { accession => $accession }) if $db;
228 if (!$dbxref) {
229 $response{error} = "Could not find term $db_name : $accession in the database! Check your input and try again";
230 $c->stash->{rest} = \%response;
231 return;
233 my $cvterm = $dbxref->cvterm;
235 my @response_list = ();
236 if ($cvterm) {
237 my $parents_rs = $cvterm->recursive_parents(); # returns a result set
238 while (my $parent = $parents_rs->next()) {
239 #only report back children of the same cv namespace
240 if ($parent->cv_id() != $cvterm->cv_id()) {
241 next();
243 my $responsehash = $self->flatten_node($parent, undef);
244 push @response_list, $responsehash;
246 } else {
247 $response{error} = "Could not find term $db_name : $accession in the database! Check your input and try again. THIS MAY BE AN INTERNAL DATABASE PROBLEM! Please contact sgn-feedback\@sgn.cornell.edu for help.";
248 $c->stash->{rest} = \%response;
249 return;
251 $c->stash->{rest} = \@response_list;
254 =head2 menu
256 Usage:
257 Desc:
258 Ret:
259 Args:
260 Side Effects:
261 Example:
263 =cut
265 sub menu : Local : ActionClass('REST') { }
267 sub menu_GET {
268 my $self = shift;
269 my $c = shift;
271 my $menudata = $c->config->{onto_root_namespaces};
273 print STDERR "MENUDATA: $menudata\n";
274 my @menuitems = split ",", $menudata;
276 my $menu = '<select name="cv_select">';
278 foreach my $mi (@menuitems) {
279 print STDERR "MENU ITEM: $mi\n";
280 if ($mi =~ /\s*(\w+)?\s*(.*)$/) {
281 my $value = $1;
282 my $name = $2;
284 $menu .= qq { <option value="$value">$value $name</option>\n };
288 $menu .= "</select>\n";
289 $c->stash->{rest} = [ $menu ];
296 =head2 roots
298 Public Path: /<ns>/roots
300 L<Catalyst::Action::REST> action.
302 Provides the default roots for drawing the ontology browser
304 Query/Body Params:
306 nodes: optional, a string with namespace definitions, separated by white
307 space (for example, "PO SP SO"). If not provided, will This overrides the standard
308 namespaces provided when called without arguments.
310 =cut
312 sub roots : Local : ActionClass('REST') { }
314 sub roots_GET {
315 my $self = shift;
316 my $c = shift;
317 my $schema = $c->dbic_schema('Bio::Chado::Schema', 'sgn_chado');
319 my $namespace = $c->request->param('nodes');
320 my @namespaces = ();
321 my @response_nodes = ();
322 #my $empty_cvterm = CXGN::Chado::Cvterm->new($c->dbc()->dbh());
323 if (!$namespace) { # should namespaces be db names ? (SO, GO,PO, SP, PATO)
324 @namespaces = (
325 'GO',
326 'PO',
327 'SP',
328 'SO',
329 'PATO',
330 'CO',
333 else {
334 @namespaces = split /\s+/, $namespace; #split on whitespace
336 my @roots = ();
337 my $is_rel = 0;
338 foreach my $ns (@namespaces) {
339 $is_rel = 1 if $ns eq 'OBO_REL';
340 my $q = "SELECT cvterm.cvterm_id FROM cvterm
341 JOIN dbxref USING(dbxref_id) JOIN db USING(db_id)
342 LEFT JOIN cvterm_relationship ON (cvterm.cvterm_id=cvterm_relationship.subject_id)
343 WHERE cvterm_relationship.subject_id IS NULL AND is_obsolete= ? AND is_relationshiptype = ? AND db.name= ? ";
344 my $sth = $schema->storage->dbh->prepare($q);
345 $sth->execute(0,$is_rel,$ns);
346 while (my ($cvterm_id) = $sth->fetchrow_array() ) {
347 my $root = $schema->resultset("Cv::Cvterm")->find( { cvterm_id => $cvterm_id } );
348 push @roots, $root;
351 my @response_list = ();
353 foreach my $r (@roots) {
354 my $hashref = $self->flatten_node($r, undef);
355 push @response_list, $hashref;
357 $c->stash->{rest}= \@response_list;
361 =head2 match
363 Public Path: /<ns>/match
365 L<Catalyst::Action::REST> action.
367 Query/Body Params:
369 db_name
370 term_name
372 =cut
374 sub match : Local : ActionClass('REST') { }
376 sub match_GET {
377 my $self = shift;
378 my $c = shift;
379 my $db_name = $c->request->param("db_name");
380 my $term_name = $c->request->param("term_name");
382 my $schema = $c->dbic_schema('Bio::Chado::Schema', 'sgn_chado');
383 my $query = "SELECT distinct cvterm.cvterm_id as cvterm_id , cv.name as cv_name , cvterm.name as cvterm_name , db.name || ':' || dbxref.accession as accession
384 FROM db
385 JOIN dbxref USING (db_id ) JOIN cvterm USING (dbxref_id)
386 JOIN cv USING (cv_id )
387 LEFT JOIN cvtermsynonym USING (cvterm_id )
388 WHERE db.name = ? AND (cvterm.name ilike ? OR cvtermsynonym.synonym ilike ? OR cvterm.definition ilike ?) AND cvterm.is_obsolete = 0
389 GROUP BY cvterm.cvterm_id,cv.name, cvterm.name, dbxref.accession, db.name ";
390 my $sth= $schema->storage->dbh->prepare($query);
391 $sth->execute($db_name, "\%$term_name\%", "\%$term_name\%", "\%$term_name\%");
392 my @response_list;
393 while (my $hashref = $sth->fetchrow_hashref ) {
394 push @response_list, $hashref;
396 $c->stash->{rest} = \@response_list;
399 =head2 cache
401 Public Path: /<ns>/cache
403 L<Catalyst::Action::REST> action.
405 Provides a list of parents and their direct children in a denormalized
406 list. The parameter node is used to determine all the children that
407 need to be cached for the parentage view to render fast (without
408 having to call the children ajax function).
410 =cut
412 sub cache : Local : ActionClass('REST') { }
414 sub cache_GET {
415 my $self =shift;
416 my $c = shift;
418 $self->{duplicates} = {};
419 $self->{cache_list} = [];
421 my $schema = $c->dbic_schema('Bio::Chado::Schema', 'sgn_chado');
422 my %response;
423 my ($db_name, $accession) = split ":", $c->request->param('node');
424 if (!$db_name || !$accession) {
425 $response{error} = "Looks like you passed an illegal ontology term ID ! ($db_name : $accession) Please try again.";
426 $c->stash->{rest} = \%response;
427 return;
430 my $db = $schema->resultset('General::Db')->search({ name => $db_name })->first();
431 my $dbxref = $db->find_related('dbxrefs', { accession => $accession });
432 if (!$dbxref) {
433 $response{error} = "Did not find ontology term $db_name : $accession in the database. Please try again. If you think this term should exist please contact sgn-feedback\@sgn.cornell.edu";
434 $c->stash->{rest} = \%response;
435 return;
438 my $cvterm = $dbxref->cvterm;
439 if (!$cvterm) {
440 $response{error} = "Did not find ontology term $db_name : $accession in the database. This may be an internal database issue. Please contact sgn-feedback\@sgn.cornell.edu and we will fic this error ASAP";
441 $c->stash->{rest} = \%response;
442 return;
444 my $parents_rs = $cvterm->recursive_parents(); # returns a result set
445 if (!$parents_rs->next) {
446 $response{error} = "did not find recursive parents for cvterm " . $cvterm->name;
447 $c->stash->{rest} = \%response;
448 return;
451 # $self->add_cache_list(undef, $cvterm, $cvterm->type());
452 while (my $p = $parents_rs->next()) {
453 my $children_rs = $p->children();
454 while (my $rel_rs = $children_rs->next()) { # returns a list of cvterm rows
455 my $child = $rel_rs->subject();
456 $self->add_cache_list($p, $child, $rel_rs->type());
459 $c->stash->{rest} = $self->{cache_list};
463 =head1 PRIVATE ACTIONS
465 =head2 add_cache_list
467 Private action.
469 Adds an entry to the cache list.
471 Argsuments: 3 cvterm row objects: Parent, child, relationship
473 =cut
475 sub add_cache_list :Private {
476 my $self = shift;
477 my $parent = shift; # object
478 my $child = shift; # object
479 my $relationship = shift; #object
481 my $unique_hashkey = $parent->cvterm_id()." ".$child->cvterm_id();
482 if (exists($self->{duplicates}->{$unique_hashkey})) {
483 return;
486 $self->{duplicates}->{$unique_hashkey}++;
488 my $hashref = $self->flatten_node($child, $relationship);
489 ###$hashref->{parent} = $parent->get_full_accession();
491 my $dbxref = $parent->dbxref();
492 my $parent_accession = $dbxref->db()->name().":".$dbxref->accession();
493 $hashref->{parent} = $parent_accession;
494 ##print STDERR "Adding to cache list: parent=$parent. child=$child\n";
495 push @{$self->{cache_list}}, $hashref;
499 ### used for cvterm resultset
500 sub flatten_node {
501 my $self = shift;
502 my $node_row = shift;
503 my $rel_row = shift;
505 my $has_children = 0;
506 if ($node_row->children()->first()) {
507 $has_children = 1;
510 my $rel_name = "";
511 if ($rel_row) {
512 $rel_name = $rel_row->name();
515 my $dbxref = $node_row->dbxref();
517 my $hashref =
518 { accession => $dbxref->db->name().":".$dbxref->accession,
519 cvterm_name => $node_row->name(),
520 cvterm_id => $node_row->cvterm_id(),
521 has_children => $has_children,
522 relationship => $rel_name,