diff --git a/db/patches/0530_gcmd.sql b/db/patches/0530_gcmd.sql new file mode 100644 index 00000000..f31969e3 --- /dev/null +++ b/db/patches/0530_gcmd.sql @@ -0,0 +1,41 @@ +create table gcmd_keyword ( + identifier varchar not null primary key, + parent_identifier varchar constraint fk_parent + references gcmd_keyword(identifier) + deferrable initially deferred, + label varchar, + definition varchar +); + +create view vw_gcmd_keyword as +select + coalesce(level4.identifier, + level3.identifier, + level2.identifier, + level1.identifier, + term.identifier, + topic.identifier, + category.identifier) as identifier, + category.label as category, + topic.label as topic, + term.label as term, + level1.label as level1, + level2.label as level2, + level3.label as level3, + level4.label as level4 +from gcmd_keyword wrapper left join gcmd_keyword category on category.parent_identifier = wrapper.identifier + left join gcmd_keyword topic on topic.parent_identifier = category.identifier + left join gcmd_keyword term on term.parent_identifier = topic.identifier + left join gcmd_keyword level1 on level1.parent_identifier = term.identifier + left join gcmd_keyword level2 on level2.parent_identifier = level1.identifier + left join gcmd_keyword level3 on level3.parent_identifier = level2.identifier + left join gcmd_keyword level4 on level4.parent_identifier = level3.identifier +where + wrapper.identifier='1eb0ea0a-312c-4d74-8d42-6f1ad758f999' and wrapper.label='Science Keywords'; + +create table publication_gcmd_keyword_map ( + publication_id integer not null references publication(id) on delete cascade on update cascade, + gcmd_keyword_identifier varchar not null references gcmd_keyword(identifier) on delete cascade on update cascade, + primary key (publication_id, gcmd_keyword_identifier) +); + diff --git a/db/patches/0540_gcmd_view.sql b/db/patches/0540_gcmd_view.sql new file mode 100644 index 00000000..37438155 --- /dev/null +++ b/db/patches/0540_gcmd_view.sql @@ -0,0 +1,125 @@ +create view vw_gcmd_keyword as +select + coalesce(level4.identifier, + level3.identifier, + level2.identifier, + level1.identifier, + term.identifier, + topic.identifier, + category.identifier) as identifier, + category.label as category, + topic.label as topic, + term.label as term, + level1.label as level1, + level2.label as level2, + level3.label as level3, + level4.label as level4 +from gcmd_keyword wrapper inner join gcmd_keyword category on category.parent_identifier = wrapper.identifier + inner join gcmd_keyword topic on topic.parent_identifier = category.identifier + inner join gcmd_keyword term on term.parent_identifier = topic.identifier + inner join gcmd_keyword level1 on level1.parent_identifier = term.identifier + inner join gcmd_keyword level2 on level2.parent_identifier = level1.identifier + inner join gcmd_keyword level3 on level3.parent_identifier = level2.identifier + inner join gcmd_keyword level4 on level4.parent_identifier = level3.identifier +where + wrapper.identifier='1eb0ea0a-312c-4d74-8d42-6f1ad758f999' and wrapper.label='Science Keywords' +UNION +select + coalesce( + level3.identifier, + level2.identifier, + level1.identifier, + term.identifier, + topic.identifier, + category.identifier) as identifier, + category.label as category, + topic.label as topic, + term.label as term, + level1.label as level1, + level2.label as level2, + level3.label as level3, + NULL as level4 +from gcmd_keyword wrapper inner join gcmd_keyword category on category.parent_identifier = wrapper.identifier + inner join gcmd_keyword topic on topic.parent_identifier = category.identifier + inner join gcmd_keyword term on term.parent_identifier = topic.identifier + inner join gcmd_keyword level1 on level1.parent_identifier = term.identifier + inner join gcmd_keyword level2 on level2.parent_identifier = level1.identifier + inner join gcmd_keyword level3 on level3.parent_identifier = level2.identifier +where + wrapper.identifier='1eb0ea0a-312c-4d74-8d42-6f1ad758f999' and wrapper.label='Science Keywords' +UNION +select + coalesce( + level2.identifier, + level1.identifier, + term.identifier, + topic.identifier, + category.identifier) as identifier, + category.label as category, + topic.label as topic, + term.label as term, + level1.label as level1, + level2.label as level2, + NULL as level3, + NULL as level4 +from gcmd_keyword wrapper inner join gcmd_keyword category on category.parent_identifier = wrapper.identifier + inner join gcmd_keyword topic on topic.parent_identifier = category.identifier + inner join gcmd_keyword term on term.parent_identifier = topic.identifier + inner join gcmd_keyword level1 on level1.parent_identifier = term.identifier + inner join gcmd_keyword level2 on level2.parent_identifier = level1.identifier +where + wrapper.identifier='1eb0ea0a-312c-4d74-8d42-6f1ad758f999' and wrapper.label='Science Keywords' +UNION +select + coalesce( + level1.identifier, + term.identifier, + topic.identifier, + category.identifier) as identifier, + category.label as category, + topic.label as topic, + term.label as term, + level1.label as level1, +NULL as level2, NULL as level3, NULL as level4 +from gcmd_keyword wrapper inner join gcmd_keyword category on category.parent_identifier = wrapper.identifier + inner join gcmd_keyword topic on topic.parent_identifier = category.identifier + inner join gcmd_keyword term on term.parent_identifier = topic.identifier + inner join gcmd_keyword level1 on level1.parent_identifier = term.identifier +where + wrapper.identifier='1eb0ea0a-312c-4d74-8d42-6f1ad758f999' and wrapper.label='Science Keywords' +UNION +select + coalesce( + term.identifier, + topic.identifier, + category.identifier) as identifier, + category.label as category, + topic.label as topic, + term.label as term, +NULL as level1, NULL as level2, NULL as level3, NULL as level4 +from gcmd_keyword wrapper inner join gcmd_keyword category on category.parent_identifier = wrapper.identifier + inner join gcmd_keyword topic on topic.parent_identifier = category.identifier + inner join gcmd_keyword term on term.parent_identifier = topic.identifier +where + wrapper.identifier='1eb0ea0a-312c-4d74-8d42-6f1ad758f999' and wrapper.label='Science Keywords' +union +select + coalesce( + topic.identifier, + category.identifier) as identifier, + category.label as category, + topic.label as topic, +NULL as term, NULL as level1, NULL as level2, NULL as level3, NULL as level4 +from gcmd_keyword wrapper inner join gcmd_keyword category on category.parent_identifier = wrapper.identifier + inner join gcmd_keyword topic on topic.parent_identifier = category.identifier +where + wrapper.identifier='1eb0ea0a-312c-4d74-8d42-6f1ad758f999' and wrapper.label='Science Keywords' +union +select + coalesce( category.identifier) as identifier, + category.label as category, +NULL as topic, NULL as term, NULL as level1, NULL as level2, NULL as level3, NULL as level4 +from gcmd_keyword wrapper inner join gcmd_keyword category on category.parent_identifier = wrapper.identifier +where + wrapper.identifier='1eb0ea0a-312c-4d74-8d42-6f1ad758f999' and wrapper.label='Science Keywords' +; diff --git a/eg/import_gcmd_keywords b/eg/import_gcmd_keywords new file mode 100755 index 00000000..ca96c80e --- /dev/null +++ b/eg/import_gcmd_keywords @@ -0,0 +1,47 @@ +#!/usr/bin/env perl + +use Mojo::UserAgent; +use Tuba::DB::Objects qw/-nicknames -autoconnect/; +use HTML::Entities qw/decode_entities/; +use v5.14; + +my $me = $ENV{USER} || 'unknown'; +my $ua = Mojo::UserAgent->new(); +my $tx = $ua->get(q[http://gcmdservices.gsfc.nasa.gov/static/kms/sciencekeywords/sciencekeywords.rdf]); + +# http://gcmdservices.gsfc.nasa.gov/static/kms/concept/536a86bd-3dd1-4f4a-9b4a-222a12746db5 +my $changes = 0; + +my $db = GcmdKeyword->meta->db; + +$db->do_transaction(sub { + $db->dbh->do('set constraints all deferred;'); + $tx->res->dom->find('rdf\:RDF > skos\:Concept')->each(sub { + my $dom = shift; + my $identifier = $dom->attr('rdf:about'); + my $label = [ $dom->find('skos\:prefLabel')->each ]->[0]->text; + my @broader = $dom->find('skos\:broader')->each; + die "not a tree" if @broader > 1; + my $parent; + if ($parent = $broader[0]) { + $parent = $parent->attr('rdf:resource'); + my $gk = GcmdKeyword->new(identifier => $parent); + unless ($gk->load(speculative => 1)) { + $gk->save(audit_user => $me) or die $gk->error; + } + } + my @definition = $dom->find('skos\:definition')->each; + my $definition = $definition[0]->text if @definition; + + my $kw = GcmdKeyword->new( identifier => $identifier ); + $kw->load(speculative => 1); + $kw->parent($parent); + $kw->label($label || undef); + $definition =~ s/ / /g; + $kw->definition($definition); + $kw->save(audit_user => $me) or die $definition; + }); +}); + + + diff --git a/eg/import_keywords b/eg/import_keywords index 34e13324..374c06c1 100755 --- a/eg/import_keywords +++ b/eg/import_keywords @@ -8,6 +8,8 @@ use Smart::Comments; use Tuba::DB::Objects qw/-nicknames -autoconnect/; +# TODO use http://gcmdservices.gsfc.nasa.gov/static/kms/sciencekeywords/sciencekeywords.rdf + my $me = $ENV{USER} || 'unknown'; my $ua = Mojo::UserAgent->new(); my $content = $ua->get(q[http://gcmdservices.gsfc.nasa.gov/static/kms/sciencekeywords/sciencekeywords.csv])->res->body; diff --git a/lib/Tuba.pm b/lib/Tuba.pm index e9af054f..81a9fa60 100644 --- a/lib/Tuba.pm +++ b/lib/Tuba.pm @@ -307,7 +307,15 @@ sub startup { my @restrict = $opts->{restrict_identifier} ? ( $identifier => $opts->{restrict_identifier} ) : (); my %defaults = $opts->{defaults} ? %{ $opts->{defaults} } : (); if ($opts->{wildcard}) { - my $reserved = q[^(?:form/update(?:_prov|_rel|_files)?|form/create|update(?:_rel|files|prov)?|put_files|history/)]; + my $reserved = qr[^(?:form/update + (?:_prov|_rel|_files)? + |form/create + |update + (?:_prov|_rel|_files)? + |put_files + |history + ) + ]x; for my $format (@supported_formats) { $resource->get("*$identifier.$format" => \@restrict => { format => $format } ) ->over(not_match => { $identifier => $reserved }) @@ -330,6 +338,7 @@ sub startup { $authed->get("/form/update/*$identifier" => \%defaults) ->to("$cname#update_form")->name("update_form_$name"); $authed->get("/form/update_prov/*$identifier" => \%defaults) ->to("$cname#update_prov_form")->name("update_prov_form_$name"); $authed->get("/form/update_rel/*$identifier" => \%defaults) ->to("$cname#update_rel_form")->name("update_rel_form_$name"); + $authed->get("/form/update_keywords/*$identifier" => \%defaults) ->to("$cname#update_keywords_form")->name("update_keywords_form_$name"); $authed->get("/form/update_files/*$identifier" => \%defaults)->to("$cname#update_files_form")->name("update_files_form_$name"); $authed->get("/history/*$identifier" => \%defaults) ->to("$cname#history") ->name("history_$name"); $authed->delete("*$identifier" => \%defaults) ->to("$cname#remove") ->name("remove_$name"); @@ -337,6 +346,7 @@ sub startup { ->to("$cname#update") ->name("update_$name"); $authed->post("/prov/*$identifier") ->to("$cname#update_prov")->name("update_prov_$name"); $authed->post("/rel/*$identifier") ->to("$cname#update_rel")->name("update_rel_$name"); + $authed->post("/keywords/*$identifier") ->to("$cname#update_keywords")->name("update_keywords_$name"); $authed->post("/files/*$identifier") ->to("$cname#update_files")->name("update_files_$name"); $authed->put("/files/*$identifier/#filename") # a default filename for PUTs would be ambiguous. ->to("$cname#put_files")->name("put_files_$name"); @@ -344,12 +354,14 @@ sub startup { $authed->get("/form/update/:$identifier") ->to("$cname#update_form")->name("update_form_$name"); $authed->get("/form/update_prov/:$identifier" => \%defaults) ->to("$cname#update_prov_form")->name("update_prov_form_$name"); $authed->get("/form/update_rel/:$identifier" => \%defaults) ->to("$cname#update_rel_form")->name("update_rel_form_$name"); + $authed->get("/form/update_keywords/:$identifier" => \%defaults) ->to("$cname#update_keywords_form")->name("update_keywords_form_$name"); $authed->get("/form/update_files/:$identifier" => \%defaults)->to("$cname#update_files_form")->name("update_files_form_$name"); $authed->get("/history/:$identifier" => \%defaults) ->to("$cname#history") ->name("history_$name"); $authed->delete(":$identifier" => \%defaults) ->to("$cname#remove") ->name("remove_$name"); $authed->post(":$identifier" => \%defaults) ->to("$cname#update") ->name("update_$name"); $authed->post("/prov/:$identifier" => \%defaults) ->to("$cname#update_prov")->name("update_prov_$name"); $authed->post("/rel/:$identifier" => \%defaults) ->to("$cname#update_rel")->name("update_rel_$name"); + $authed->post("/keywords/:$identifier" => \%defaults) ->to("$cname#update_keywords")->name("update_keywords_$name"); $authed->post("/files/:$identifier" => \%defaults) ->to("$cname#update_files")->name("update_files_$name"); $authed->put("/files/:$identifier/#filename" => {filename => 'unnamed', %defaults }) ->to("$cname#put_files")->name("put_files_$name"); @@ -430,6 +442,9 @@ sub startup { $r->resource(person => { restrict_identifier => qr/\d+/ } ); $r->get('/person/:name')->to('person#redirect_by_name'); + # GcmdKeyword + $r->resource('gcmd_keyword'); + # Others, some of which aren't yet implemented. $r->resource($_) for qw/dataset model software algorithm activity instrument platform diff --git a/lib/Tuba/Controller.pm b/lib/Tuba/Controller.pm index 625e4d94..47ef4dbc 100644 --- a/lib/Tuba/Controller.pm +++ b/lib/Tuba/Controller.pm @@ -527,6 +527,43 @@ sub put_files { $c->render(text => "ok"); } +=head2 update_keywords + +Assign GCMD keywords to a resource. + +=cut + +sub update_keywords { + my $c = shift; + my $obj = $c->_this_object or return $c->render_not_found; + my $pub = $obj->get_publication(autocreate => 1); + $pub->save(audit => $c->user) unless $pub->id; + if (my $json = $c->req->json) { + my $delete_extra = delete $json->{_delete_extra}; + $json = [ $json ] if ref($json) eq 'HASH'; + my %to_delete = map { ($_->identifier => 1) } @{ $pub->gcmd_keywords }; + + for my $k (@$json) { + ref $k eq 'HASH' or return $c->render(json => { error => { data => $k, msg => "not a hash" }} ); + my $kw = exists $k->{identifier} ? GcmdKeyword->new(%$k) : GcmdKeyword->new_from_flat(%$k); + $kw->load(speculative => 1) or return $c->render(json => { error => { data => $k, msg => 'not found' }} ); + $pub->add_gcmd_keywords($kw); + delete $to_delete{$kw->identifier}; + } + $pub->save(audit_user => $c->user); + if ($delete_extra) { + for my $extra (keys %to_delete) { + PublicationGcmdKeywordMap->new( + publication => $pub->id, + gcmd_keyword_identifier => $extra + )->delete; + } + } + return $c->render(json => 'ok'); + } + return $c->render(text => "html not implemented"); # TODO +} + =head2 update_rel Update the relationships. diff --git a/lib/Tuba/DB/Mixin/Object/GcmdKeyword.pm b/lib/Tuba/DB/Mixin/Object/GcmdKeyword.pm new file mode 100644 index 00000000..bf82c027 --- /dev/null +++ b/lib/Tuba/DB/Mixin/Object/GcmdKeyword.pm @@ -0,0 +1,46 @@ +package Tuba::DB::Object::GcmdKeyword; +use Tuba::Log; +use Data::Dumper; +# Tuba::DB::Mixin::Object::GcmdKeyword; + +sub stringify { + my $self = shift; + my %args = @_; + if ($args{short}) { + return $self->label; + } + if (my $parent = $self->parent) { + return join '>', $self->parent->label, $self->label; + } + return $self->label; +} + +sub new_from_flat { + my $c = shift; + my %h = @_; + # Example : + # { + # 'id' => '5286', + # 'category' => 'EARTH SCIENCE', + # 'topic' => 'HUMAN DIMENSIONS', + # 'term' => 'ENVIRONMENTAL IMPACTS', + # 'level1' => 'FOSSIL FUEL BURNING' + # 'level2' => undef, + # 'level3' => undef, + # }; + my $new; + my @cols = qw/category topic term level1 level2 level3/; + my %cols; + @cols{@cols} = @h{@cols}; + my $ds = DBIx::Simple->new(Tuba::Plugin::Db->connection->dbh); + my @rows = $ds->select('vw_gcmd_keyword', '*', \%cols )->hashes; + return unless @rows > 0; + unless (@rows==1) { + logger()->warn("we got ".@rows." rows for ".dumpit(\%h)); + } + my $identifier = $rows[0]->{identifier}; + return $c->new(identifier => $identifier); +} + +1; + diff --git a/lib/Tuba/DB/Object.pm b/lib/Tuba/DB/Object.pm index f69cac36..dd263415 100644 --- a/lib/Tuba/DB/Object.pm +++ b/lib/Tuba/DB/Object.pm @@ -230,6 +230,7 @@ information : - a list of parent publications - a list of files + - a list of gcmd keywords The parameter 'c' should have a controller object (so that we can look up a URL for an object). @@ -257,6 +258,7 @@ sub as_tree { }; } $tree->{files} = [ map $_->as_tree(@_), $pub->files ]; + $tree->{gcmd_keywords} = [ map $_->as_tree(@_), $pub->gcmd_keywords ]; } $tree->{uri} //= $s->uri($c); } diff --git a/lib/Tuba/Gcmdkeyword.pm b/lib/Tuba/Gcmdkeyword.pm new file mode 100644 index 00000000..132c3ac4 --- /dev/null +++ b/lib/Tuba/Gcmdkeyword.pm @@ -0,0 +1,32 @@ +=head1 NAME + +Tuba::GcmdKeyword : Controller class for files. + +=cut + +package Tuba::Gcmdkeyword; +use Mojo::Base qw/Tuba::Controller/; +use Tuba::DB::Objects qw/-nicknames/; + +sub list { + my $c = shift; + $c->stash(objects => GcmdKeywords->get_objects(with_objects => 'publications', page => $c->page)); + my $count = GcmdKeywords->get_objects_count; + $c->stash(extra_cols => [qw/label/]); + $c->set_pages($count); + $c->SUPER::list(@_); +} + +sub show { + my $c = shift; + my $kw = $c->_this_object or $c->render_not_found; + $c->stash(object => $kw); + $c->SUPER::show(@_); +} + +sub _guess_object_class { + return 'Tuba::DB::Object::GcmdKeyword'; +} + +1; + diff --git a/lib/Tuba/Log.pm b/lib/Tuba/Log.pm index f9ec7d24..699f5b43 100644 --- a/lib/Tuba/Log.pm +++ b/lib/Tuba/Log.pm @@ -12,8 +12,8 @@ In the main app : Someplace else use Tuba::Log; - loggger->info("hi!"); - loggger->info("hi!",dumpit($var)); + logger->info("hi!"); + logger->info("hi!",dumpit($var)); =head1 DESCRIPTION diff --git a/lib/Tuba/files/templates/gcmd_keyword/fields.html.ep b/lib/Tuba/files/templates/gcmd_keyword/fields.html.ep new file mode 100644 index 00000000..89524dd7 --- /dev/null +++ b/lib/Tuba/files/templates/gcmd_keyword/fields.html.ep @@ -0,0 +1,46 @@ + + + +% for my $col (sort {$a->name cmp $b->name} $meta->columns) { +% next if $col->name =~ /^_/; +% my $k = $col->accessor_method_name; +% my $val = $object->$k; + + + + +% } + + + + + +
Fields
<%= $k %> + % if (!$val) { + + % } elsif ($k eq 'doi') { + %= link_to "http://dx.doi.org/$val" => target => "_blank" => begin + <%= $val %> + %= end + % } elsif ($k =~ /issn/ && $val) { + %= link_to "http://www.worldcat.org/issn/$val" => target => "_blank" => begin + <%= $val %> + %= end + % } elsif ($k eq 'url') { + %= link_to $val => target => "_blank" => begin + <%= $val %> + %= end + % } elsif (ref($val) eq 'ARRAY') { + %= include 'view_array', val => $val, header_rows => ($object->can('rows_in_header') ? $object->rows_in_header : 0) + % } else { + <%= $val %> + % } +
+ GCMD Metadata + + %= link_to q[http://gcmdservices.gsfc.nasa.gov/static/kms/concept/].$object->identifier => target => "_blank" => begin + <%= $object->identifier %> + %= end +
+ + diff --git a/lib/Tuba/files/templates/gcmd_keyword/object.html.ep b/lib/Tuba/files/templates/gcmd_keyword/object.html.ep new file mode 100644 index 00000000..1e5abf64 --- /dev/null +++ b/lib/Tuba/files/templates/gcmd_keyword/object.html.ep @@ -0,0 +1,36 @@ + +% layout 'default'; + +
+

<%= $object->meta->table %> : <%= $object->stringify %>

+
+ +%= include 'history_modal'; + +% if ($object->can('identifier') && user_can('update')) { + + <%= link_to $object->uri($self,{ tab => 'update_form' }) => class => "btn" => begin %>edit<%= end %> + history + +% } + +
+<%= include 'other_formats'; =%>
+<%= include 'db_meta'; =%>
+
+ +% if (my $pub = $object->get_publication) { +
Files
+
+
+%= include 'pub_thumbnails', pub => $pub, files => 1; +
+
+% } + +%= include 'prov'; + +%= include 'relationships', object => $object, meta => $meta; + +%= include 'gcmd_keyword/fields'; + diff --git a/lib/Tuba/files/templates/relationships.html.ep b/lib/Tuba/files/templates/relationships.html.ep index 8d754cd8..df8d79b6 100644 --- a/lib/Tuba/files/templates/relationships.html.ep +++ b/lib/Tuba/files/templates/relationships.html.ep @@ -31,5 +31,19 @@ % } + +% if (my $pub = $object->get_publication) { + + GCMD Keywords + +
+ %= include 'collapsible', row_content => begin + %= include 'obj_thumbnails', objs => scalar $pub->gcmd_keywords + %= end +
+ + +% } +