Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions db/00205/AddTimeOntologyYear.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#!/usr/bin/env perl

=head1 NAME

AddTimeOntologyYear.pm

=head1 SYNOPSIS

mx-run AddTimeOntologyYear [options] [-F]

this is a subclass of L<CXGN::Metadata::Dbpatch>
see the perldoc of parent class for more details.

=head1 DESCRIPTION

This patch:
- Adds new cvterm for time ontology: year

=head1 AUTHOR

Katherine Eaton

=head1 COPYRIGHT & LICENSE

Copyright 2026 University of Alberta

This program is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=cut

package AddTimeOntologyYear;

use Moose;
use Bio::Chado::Schema;
use SGN::Model::Cvterm;
extends 'CXGN::Metadata::Dbpatch';

has '+description' => ( default => <<'' );
Adds new cvterm for time ontology age in years: year 1-100.

sub patch {
my $self=shift;

print STDOUT "Executing the patch:\n " . $self->name . ".\n\nDescription:\n ". $self->description . ".\n\nExecuted by:\n " . $self->username . " .";

print STDOUT "\nChecking if this db_patch was executed before or if previous db_patches have been executed.\n";

print STDOUT "\nExecuting the SQL commands.\n";
my $schema = Bio::Chado::Schema->connect( sub { $self->dbh->clone } );

print STDERR "INSERTING CV TERMS...\n";
my $is_a_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, 'is_a', 'relationship')->cvterm_id();
my $time_ontology_rs = $schema->resultset("Cv::Cvterm")->find({name => "Time"});

# Create the root cvterm
# next available dbxref accession in time ontology is '0000481'
my $dbxref_accession = 481;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little wary of hardcoding the next highest accession number in an ontology - I will need to fish around to see if other databases have messed with their time ontologies in such a way that it would break this

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a very good point, thank you. Initially I'd thought of using the autocreated: year 1 accession for the dbxref accession. But then I thought this might be inconsistent in a bad way, since all the other time ontology accessions have hard-coded numeric accessions. Another option might be to put it in its own ontology (ex. YEAR:000001). But that might also be consfusing if year is the only time unit to have its own ontology.

I'm curious to see what you find out in the other databases!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, maybe putting year in it's own time ontology was a bad suggestion. I imagine that will over complicate things downstream to have time in multiple ontologies (ex. plotting post-composed time traits #6096).

Copy link
Copy Markdown
Contributor

@ryan-preble ryan-preble May 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think your general structure of placing a new root term in the TIME ontology and then assigning that root term to be a TIY composable ontology is the right approach. But I checked and it seems other breedbases have added additional time terms to their DB that throws off the accession numbers. I think the best approach would be to solve for the next highest accession number in the db patch and use that to form the root term, then start adding new accession numbers from that starting point. Changing sgn.conf to use 481 makes sense because sgn_local.conf can override that value for all the databases that have modified their TIME ontologies away from the default. We will just have to be careful to set the correct values in sgn_local.conf wherever relevant

Also, one more thing: I am pretty sure cvterm create_with() will ignore the definition parameter. When I wrote a dbpatch that created new ontologies, I had to manually update the definition after making it with create_with().

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've now changed the definition from inside create_with() to manual afterwards. Jumping over to Lukas' responses to discuss auto-detection of accession vs hard-coding a much larger root number.

my $time_in_years_rs = $schema->resultset("Cv::Cvterm")->create_with({
name => "time in years",
definition=> "Time in years",
cv => 'cxgn_time_ontology',
db => 'TIME',
dbxref => sprintf("%07d", $dbxref_accession)
});
# Link root cvterm to the time ontology
$schema->resultset("Cv::CvtermRelationship")->find_or_create({
subject_id => $time_in_years_rs->cvterm_id(),
type_id => $is_a_cvterm_id,
object_id => $time_ontology_rs->cvterm_id()
});

# Create years 1 through 100
foreach my $year (1..100) {
$dbxref_accession += 1;
my $year_rs = $schema->resultset("Cv::Cvterm")->create_with({
name => "year $year",
cv => 'cxgn_time_ontology',
db => 'TIME',
dbxref => sprintf("%07d", $dbxref_accession)
});
# link to time in years root cvterm
$schema->resultset("Cv::CvtermRelationship")->find_or_create({
subject_id => $year_rs->cvterm_id(),
type_id => $is_a_cvterm_id,
object_id => $time_in_years_rs->cvterm_id()
});
}

print "You're done!\n";
}

####
1; #
####
3 changes: 3 additions & 0 deletions js/source/legacy/CXGN/ComposeTrait.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ function get_component_ids () {
if (jQuery("#experiment_treatment_select").val()) { component_ids.push(jQuery("#experiment_treatment_select").val()); }
if (jQuery("#tod_select").val()) { component_ids.push(jQuery("#tod_select").val()); }
if (jQuery("#toy_select").val()) { component_ids.push(jQuery("#toy_select").val()); }
if (jQuery("#tiy_select").val()) { component_ids.push(jQuery("#tiy_select").val()); }
if (jQuery("#gen_select").val()) { component_ids.push(jQuery("#gen_select").val()); }
if (jQuery("#evt_select").val()) { component_ids.push(jQuery("#evt_select").val()); }
if (jQuery("#meta_select").val()) { component_ids.push(jQuery("#meta_select").val()); }
Expand All @@ -62,6 +63,7 @@ function retrieve_matching_traits (component_ids) {
}
ids["tod_ids"] = jQuery("#tod_select").val();
ids["toy_ids"] = jQuery("#toy_select").val();
ids["tiy_ids"] = jQuery("#tiy_select").val();
ids["gen_ids"] = jQuery("#gen_select").val();
ids["evt_ids"] = jQuery("#evt_select").val();
ids["meta_ids"] = jQuery("#meta_select").val();
Expand All @@ -76,6 +78,7 @@ jQuery.ajax( {
'trait_ids': ids["trait_ids"],
'tod_ids': ids["tod_ids"],
'toy_ids': ids["toy_ids"],
'tiy_ids': ids["tiy_ids"],
'gen_ids': ids["gen_ids"],
'evt_ids': ids["evt_ids"],
'meta_ids': ids["meta_ids"],
Expand Down
4 changes: 3 additions & 1 deletion lib/SGN/Controller/AJAX/Onto.pm
Original file line number Diff line number Diff line change
Expand Up @@ -362,11 +362,12 @@ sub get_traits_from_component_categories: Path('/ajax/onto/get_traits_from_compo
my @trait_ids = $c->req->param("trait_ids[]");
my @tod_ids = $c->req->param("tod_ids[]");
my @toy_ids = $c->req->param("toy_ids[]");
my @tiy_ids = $c->req->param("tiy_ids[]");
my @gen_ids = $c->req->param("gen_ids[]");
my @evt_ids = $c->req->param("evt_ids[]");
my @meta_ids = $c->req->param("meta_ids[]");

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 evt ids are @evt_ids\n metadata ids are @meta_ids\n";
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 tiy ids are @tiy_ids\n gen ids are @gen_ids\n evt ids are @evt_ids\n metadata ids are @meta_ids\n";
my $schema = $c->dbic_schema('Bio::Chado::Schema', 'sgn_chado');

my $traits = SGN::Model::Cvterm->get_traits_from_component_categories($schema, \@allowed_composed_cvs, $composable_cvterm_delimiter, $composable_cvterm_format, {
Expand All @@ -377,6 +378,7 @@ sub get_traits_from_component_categories: Path('/ajax/onto/get_traits_from_compo
trait => \@trait_ids,
tod => \@tod_ids,
toy => \@toy_ids,
tiy => \@tiy_ids,
gen => \@gen_ids,
evt => \@evt_ids,
meta => \@meta_ids,
Expand Down
2 changes: 2 additions & 0 deletions lib/SGN/Controller/Ontology.pm
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ sub compose_trait : Path('/tools/compose_trait') :Args(0) {
$c->stash->{composable_cvs_allowed_combinations} = $c->config->{composable_cvs_allowed_combinations};
$c->stash->{composable_tod_root_cvterm} = $c->config->{composable_tod_root_cvterm};
$c->stash->{composable_toy_root_cvterm} = $c->config->{composable_toy_root_cvterm};
$c->stash->{composable_tiy_root_cvterm} = $c->config->{composable_tiy_root_cvterm};
$c->stash->{composable_gen_root_cvterm} = $c->config->{composable_gen_root_cvterm};
$c->stash->{composable_evt_root_cvterm} = $c->config->{composable_evt_root_cvterm};
$c->stash->{composable_meta_root_cvterm} = $c->config->{composable_meta_root_cvterm};
Expand Down Expand Up @@ -205,6 +206,7 @@ sub compose_treatment : Path('/tools/compose_treatment') :Args(0) {
$c->stash->{composable_cvs_allowed_combinations} = $c->config->{composable_cvs_allowed_combinations};
$c->stash->{composable_tod_root_cvterm} = $c->config->{composable_tod_root_cvterm};
$c->stash->{composable_toy_root_cvterm} = $c->config->{composable_toy_root_cvterm};
$c->stash->{composable_tiy_root_cvterm} = $c->config->{composable_tiy_root_cvterm};
$c->stash->{composable_gen_root_cvterm} = $c->config->{composable_gen_root_cvterm};
$c->stash->{composable_evt_root_cvterm} = $c->config->{composable_evt_root_cvterm};
$c->stash->{composable_meta_root_cvterm} = $c->config->{composable_meta_root_cvterm};
Expand Down
26 changes: 21 additions & 5 deletions mason/ontology/compose_trait.mas
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ $composable_cvs => undef
$composable_cvs_allowed_combinations => undef
$composable_tod_root_cvterm => undef
$composable_toy_root_cvterm => undef
$composable_tiy_root_cvterm => undef
$composable_gen_root_cvterm => undef
$composable_evt_root_cvterm => undef
$composable_meta_root_cvterm => undef
Expand Down Expand Up @@ -115,6 +116,14 @@ $composable_meta_root_cvterm => undef
<button class="btn btn-default btn-sm" id="toy_select_all" name="toy_select_all"/>Select All</button>
<button class="btn btn-default btn-sm" id="toy_select_clear" name="toy_select_clear"/>Clear</button>
</div>
<div id="tiy_div" class="col-md-6" style="display: none;">
<label for="tiy_select_div" class="control-label">Time in Years</label>
<p class="help-block"><small><i>Optional</i> Pick the year in which the new trait is measured</small></p>
<input type="text" id="tiy_search" class="form-control input-sm" placeholder="Search year...">
<div id="tiy_select_div"></div><br>
<button class="btn btn-default btn-sm" id="tiy_select_all" name="tiy_select_all"/>Select All</button>
<button class="btn btn-default btn-sm" id="tiy_select_clear" name="tiy_select_clear"/>Clear</button>
</div>
<div id="gen_div" class="col-md-6" style="display: none;">
<label for="gen_select_div" class="control-label">Generation</label>
<p class="help-block"><small><i>Optional</i> Pick the generation on which the new trait is measured</small></p>
Expand Down Expand Up @@ -249,7 +258,7 @@ name = name.replace('_root_select', '');
get_select_box('trait_components', name +'_select_div', { 'id' : name +'_select', 'name': name +'_select', 'multiple': 'true', 'cv_id': cv_id, 'size':'10' });
});

jQuery(document).on('change','#object_select, #attribute_select, #method_select, #unit_select, #trait_select, #tod_select, #toy_select, #gen_select, #evt_select, #meta_select', function() { // retrieve matching traits each time component selection changes
jQuery(document).on('change','#object_select, #attribute_select, #method_select, #unit_select, #trait_select, #tod_select, #toy_select, #tiy_select, #gen_select, #evt_select, #meta_select', function() { // retrieve matching traits each time component selection changes
if (jQuery(this).attr('name') == 'trait_select') {
//console.log("trait select changed");
//clearAllOptions(document.getElementById('object_select'));
Expand All @@ -264,15 +273,15 @@ jQuery(document).on('change','#object_select, #attribute_select, #method_select,
display_matching_traits();
});

jQuery('#object_select_all, #attribute_select_all, #method_select_all, #unit_select_all, #trait_select_all, #tod_select_all, #toy_select_all, #gen_select_all, #evt_select_all, #meta_select_all').click( // select all data in a panel
jQuery('#object_select_all, #attribute_select_all, #method_select_all, #unit_select_all, #trait_select_all, #tod_select_all, #toy_select_all, #tiy_select_all, #gen_select_all, #evt_select_all, #meta_select_all').click( // select all data in a panel
function() {
var name = jQuery(this).attr('name');
var select_id = name.substring(0, name.length - 4);
selectAllOptions(document.getElementById(select_id));
display_matching_traits();
});

jQuery('#object_select_clear, #attribute_select_clear, #method_select_clear, #unit_select_clear, #trait_select_clear, #gen_select_clear, #tod_select_clear, #toy_select_clear, #evt_select_clear, #meta_select_clear').click( // clear all selections in a panel
jQuery('#object_select_clear, #attribute_select_clear, #method_select_clear, #unit_select_clear, #trait_select_clear, #gen_select_clear, #tod_select_clear, #toy_select_clear, #tiy_select_clear, #evt_select_clear, #meta_select_clear').click( // clear all selections in a panel
function() {
var name = jQuery(this).attr('name');
var select_id = name.substring(0, name.length - 6);
Expand Down Expand Up @@ -348,6 +357,7 @@ function create_multi_selects(allowed_names) {
jQuery('#compose_trait').prop('disabled', true);
jQuery('#tod_div').hide();
jQuery('#toy_div').hide();
jQuery('#tiy_div').hide();
jQuery('#gen_div').hide();
jQuery('#evt_div').hide();
jQuery('#object_div').hide();
Expand All @@ -358,6 +368,7 @@ function create_multi_selects(allowed_names) {
jQuery('#meta_div').hide();
jQuery('#tod_select').val([]);
jQuery('#toy_select').val([]);
jQuery('#tiy_select').val([]);
jQuery('#gen_select').val([]);
jQuery('#evt_select').val([]);
jQuery('#object_select').val([]);
Expand Down Expand Up @@ -388,9 +399,10 @@ function create_multi_selects(allowed_names) {
jQuery('#method_div').show();
break;
case "time":
jQuery("#tod_div,#toy_div,#gen_div","evt_div","meta_div").show();
jQuery("#tod_div,#toy_div,#tiy_div,#gen_div","evt_div","meta_div").show();
get_select_box('ontology_children', 'tod_select_div', { 'selectbox_id' : 'tod_select', 'selectbox_name': 'tod_select', 'multiple': 'true', 'parent_node_cvterm': '<% $composable_tod_root_cvterm %>', 'rel_cvterm': 'is_a', 'rel_cv': 'relationship' });
get_select_box('ontology_children', 'toy_select_div', { 'selectbox_id' : 'toy_select', 'selectbox_name': 'toy_select', 'multiple': 'true', 'parent_node_cvterm': '<% $composable_toy_root_cvterm %>', 'rel_cvterm': 'is_a', 'rel_cv': 'relationship' });
get_select_box('ontology_children', 'tiy_select_div', { 'selectbox_id' : 'tiy_select', 'selectbox_name': 'tiy_select', 'multiple': 'true', 'parent_node_cvterm': '<% $composable_tiy_root_cvterm %>', 'rel_cvterm': 'is_a', 'rel_cv': 'relationship' });
get_select_box('ontology_children', 'gen_select_div', { 'selectbox_id' : 'gen_select', 'selectbox_name': 'gen_select', 'multiple': 'true', 'parent_node_cvterm': '<% $composable_gen_root_cvterm %>', 'rel_cvterm': 'is_a', 'rel_cv': 'relationship' });
get_select_box('ontology_children', 'evt_select_div', { 'selectbox_id' : 'evt_select', 'selectbox_name': 'evt_select', 'multiple': 'true', 'parent_node_cvterm': '<% $composable_evt_root_cvterm %>', 'rel_cvterm': 'is_a', 'rel_cv': 'relationship' });
get_select_box('ontology_children', 'meta_select_div', { 'selectbox_id' : 'meta_select', 'selectbox_name': 'meta_select', 'multiple': 'true', 'parent_node_cvterm': '<% $composable_meta_root_cvterm %>', 'rel_cvterm': 'is_a', 'rel_cv': 'relationship' });
Expand All @@ -403,6 +415,10 @@ function create_multi_selects(allowed_names) {
jQuery("#toy_div").show();
get_select_box('ontology_children', 'toy_select_div', { 'selectbox_id' : 'toy_select', 'selectbox_name': 'toy_select', 'multiple': 'true', 'parent_node_cvterm': '<% $composable_toy_root_cvterm %>', 'rel_cvterm': 'is_a', 'rel_cv': 'relationship' });
break;
case "tiy":
jQuery("#tiy_div").show();
get_select_box('ontology_children', 'tiy_select_div', { 'selectbox_id' : 'tiy_select', 'selectbox_name': 'tiy_select', 'multiple': 'true', 'parent_node_cvterm': '<% $composable_tiy_root_cvterm %>', 'rel_cvterm': 'is_a', 'rel_cv': 'relationship' });
break;
case "gen":
jQuery("#gen_div").show();
get_select_box('ontology_children', 'gen_select_div', { 'selectbox_id' : 'gen_select', 'selectbox_name': 'gen_select', 'multiple': 'true', 'parent_node_cvterm': '<% $composable_gen_root_cvterm %>', 'rel_cvterm': 'is_a', 'rel_cv': 'relationship' });
Expand Down Expand Up @@ -436,7 +452,7 @@ jQuery(document).ready(function() {
});

jQuery(document).ready(function () {
const searchable = ['trait', 'toy'];
const searchable = ['trait', 'toy', 'tiy'];

searchable.forEach(function (prefix) {
const searchSelector = '#' + prefix + '_search';
Expand Down
Loading
Loading