First working query example with promise based backend
Change-Id: Iee7360d123dc09876d942a32013077d78ea50b91
diff --git a/lib/Kalamar/Controller/Search2.pm b/lib/Kalamar/Controller/Search2.pm
index 2cdff6c..e018fec 100644
--- a/lib/Kalamar/Controller/Search2.pm
+++ b/lib/Kalamar/Controller/Search2.pm
@@ -1,13 +1,193 @@
package Kalamar::Controller::Search2;
use Mojo::Base 'Mojolicious::Controller';
+use Data::Dumper;
+use Mojo::Collection 'c';
+use Mojo::ByteStream 'b';
-has api => 'http://10.0.10.52:9000/api/';
+# This should be implemented as a helper
+has api => '/api/';
has no_cache => 0;
has items_per_page => 25;
+# Catch connection errors
+sub _catch_http_errors {
+ my $tx = shift;
+ my $err = $tx->error;
+
+ if ($err) {
+ return Mojo::Promise->new->reject([
+ [$err->{code}, $err->{message}]
+ ]);
+ };
+ return $tx->result;
+};
+
+
+# Catch koral errors
+sub _catch_koral_errors {
+ my $res = shift;
+
+ my $json = $res->json;
+
+ unless ($json) {
+ return Mojo::Promise->new->reject([
+ [undef, 'JSON response is invalid']
+ ]);
+ };
+
+ # Get errors
+ my $err = $json->{errors};
+
+ # Create error message
+ if ($err) {
+ return Mojo::Promise->new->reject($err);
+ };
+
+ if ($json->{status}) {
+ return Mojo::Promise->new->reject([
+ [undef, 'Middleware error ' . $json->{'status'}]
+ ]);
+ };
+
+ return $json;
+};
+
+
+# Notify the user in case of warnings
+sub _notify_on_warnings {
+ my ($self, $warnings) = @_;
+
+ # TODO: Check for ref!
+ foreach my $w (@$warnings) {
+ $self->notify(
+ warn =>
+ ($w->[0] ? $w->[0] . ': ' : '') .
+ $w->[1]
+ );
+ };
+};
+
+sub _notify_on_errors {
+ my ($self, $errors) = @_;
+ foreach my $e (@$errors) {
+ $self->notify(
+ error =>
+ ($e->[0] ? $e->[0] . ': ' : '') .
+ ($e->[1] || 'Unknown')
+ );
+ };
+};
+
+
+# Notify the user in case of errors
+#sub _notify_on_warnings {
+# my ($self, $json) = @_;
+#
+# if ($json->{warnings}) {
+#
+# # TODO: Check for ref!
+# foreach (@{$json->{warnings}}) {
+# $self->notify(
+# warn =>
+# ($_->[0] ? $_->[0] . ': ' : '') .
+# $_->[1]
+# );
+# };
+# };
+#}
+
+
+# Process response and set stash values
+sub _process_matches {
+ my ($self, $json) = @_;
+
+ # Process meta
+ my $meta = $json->{meta};
+
+ # TODO:
+ # Set benchmark in case of development mode only.
+ # Use server timing API
+ #
+ # Reformat benchmark counter
+ # my $benchmark = $meta->{benchmark};
+ # if ($benchmark && $benchmark =~ s/\s+(m)?s$//) {
+ # $benchmark = sprintf("%.2f", $benchmark) . ($1 ? $1 : '') . 's';
+ # };
+ # Set benchmark
+ # $index->benchmark($benchmark);
+
+ # Set time exceeded
+ if ($meta->{timeExceeded} && $meta->{timeExceeded} eq Mojo::JSON::true) {
+ $self->stash(time_exceeded => 1);
+ };
+
+ # Set result values
+ $self->stash(items_per_page => $meta->{itemsPerPage});
+
+ # Set authorization
+ # $index->authorized($meta->{authorized}) if $meta->{authorized};
+
+ # Bouncing query
+ # if ($json->{query}) {
+ # $index->query_jsonld($json->{query});
+ # };
+
+ # Legacy
+ # elsif ($json->{request}->{query}) {
+ # $index->query_jsonld($json->{request}->{query});
+ # };
+
+
+ if ($meta->{totalResults}) {
+ $self->stash(total_results => $meta->{totalResults});
+ };
+
+ # Bouncing collection query
+ if ($json->{corpus} || $json->{collection}) {
+ $self->stash(corpus_jsonld => ($json->{corpus} || $json->{collection}));
+ };
+
+ # Set results to stash
+ $self->stash(
+ results => _map_matches($json->{matches})
+ );
+
+ return;
+};
+
+
+# Cleanup array of matches
+sub _map_matches {
+ return c() unless $_[0];
+ c(map { _map_match($_) } @{ shift() });
+};
+
+
+# Cleanup single match
+sub _map_match {
+ my $match = shift or return;
+
+ # Legacy match id
+ if ($match->{matchID}) {
+ $match->{matchID} =~ s/^match\-(?:[^!]+!|[^_]+_)[^\.]+?\.[^-]+?-// or
+ $match->{matchID} =~ s!^match\-(?:[^\/]+\/){2}[^-]+?-!!;
+ };
+
+ # Set IDs based on the sigle
+ (
+ $match->{corpusID},
+ $match->{docID},
+ $match->{textID}
+ ) = ($match->{textSigle} =~ /^([^_]+?)_+([^\.]+?)\.(.+?)$/);
+
+ return $match;
+};
+
+
+# Query endpoint
sub query {
my $c = shift;
@@ -48,11 +228,12 @@
# Create remote request URL
my $url = Mojo::URL->new($c->api);
+ $url->path('search');
$url->query(\%query);
# Check if total results is cached
my $total_results;
- if (!$c->no_cache) {
+ if (!$c->no_cache && 0) {
$total_results = $c->chi->get('total-' . $user . '-' . $url->to_string);
$c->stash(total_results => $total_results);
$c->app->log->debug('Get total result from cache');
@@ -61,8 +242,6 @@
$url->query({cutoff => 'true'});
};
-
-
# Establish 'search_results' taghelper
# This is based on Mojolicious::Plugin::Search
$c->app->helper(
@@ -76,34 +255,121 @@
return '';
};
+ my $coll = $c->stash('results');
+
# Iterate over results
- my $string = $c->stash('search.results')->map(
+ my $string = $coll->map(
sub {
# Call hit callback
- $c->stash('search.hit' => $_);
+ # $c->stash('search.hit' => $_);
local $_ = $_[0];
return $cb->($_);
})->join;
# Remove hit from stash
- delete $c->stash->{'search.hit'};
+ # delete $c->stash->{'search.hit'};
return b($string);
}
);
+ # Check if the request is cached
+ my $url_string = $url->to_string;
- return $c->render(
- template => 'search2',
- q => $query,
- ql => scalar $v->param('ql') // 'poliqarp',
- result_size => 0,
- start_page => 1,
- total_pages => 20,
- total_results => 40,
- time_exceeded => 0,
- benchmark => 'Long ...',
- api_response => ''
- );
+ # Set api request for debugging
+ # $c->stash('api_request' => $url_string);
+
+ # Debugging
+ $c->app->log->debug('Search for ' . $url_string);
+
+ # Check for cache
+ my $json = $c->chi->get('matches-' . $user . '-' . $url_string);
+
+ # Initialize promise object
+ my $promise;
+
+ # Result is cached
+ if ($json) {
+ $json->{cached} = 'true';
+
+ # The promise is already satisfied by the cache
+ $promise = Mojo::Promise->new->resolve($json);
+ }
+
+ # Retrieve from URL
+ else {
+
+ # Wrap a user agent method with a promise
+ $promise = $c->user->auth_request_p(get => $url)
+ ->then(\&_catch_http_errors)
+ ->then(\&_catch_koral_errors)
+ ;
+ };
+
+ $c->render_later;
+
+ # Prepare warnings
+ $promise->then(
+ sub {
+ my $json = shift;
+ $c->_notify_on_warnings($json->{warnings}) if $json->{warnings};
+ return $json
+ }
+
+ # Process response
+ )->then(
+ sub {
+ my $json = shift;
+
+ # Cache total results
+ unless ($c->stash('total_results') && $json->{meta}->{totalResults}) {
+
+ # Remove cutoff requirement again
+ $url->query([cutoff => 'true']);
+
+ # Set cache
+ $c->chi->set(
+ 'total-' . $user . '-' . $url->to_string => $json->{meta}->{totalResults}
+ )
+ };
+
+ # Cache result
+ $c->chi->set('matches-' . $user . '-' . $url_string => $json);
+
+ # Process match results
+ return $c->_process_matches($json);
+ }
+
+ # Deal with errors
+ )->catch(
+ sub {
+ $c->_notify_on_errors(shift);
+ }
+
+ # Render template
+ )->finally(
+ sub {
+ # Choose the snippet based on the parameter
+ my $template = scalar $v->param('snippet') ? 'snippet' : 'search2';
+
+ $c->app->log->debug('Render template ...');
+
+ return $c->render(
+ template => $template,
+ q => $query,
+ ql => scalar $v->param('ql') // 'poliqarp',
+ results => c(),
+ start_page => 1,
+ total_pages => 20,
+ # total_results => 40,
+ time_exceeded => 0,
+ benchmark => 'Long ...',
+ api_response => ''
+ );
+ }
+ )->wait;
+
+
+ return 1;
};
diff --git a/lib/Kalamar/Plugin/KalamarUser.pm b/lib/Kalamar/Plugin/KalamarUser.pm
index 211c87d..2f1a7a3 100644
--- a/lib/Kalamar/Plugin/KalamarUser.pm
+++ b/lib/Kalamar/Plugin/KalamarUser.pm
@@ -1,5 +1,6 @@
package Kalamar::Plugin::KalamarUser;
use Mojo::Base 'Mojolicious::Plugin';
+use Mojo::Promise;
use Mojo::ByteStream 'b';
has 'api';
@@ -114,6 +115,33 @@
}
);
+ # Request with authorization header
+ # return a promise
+ $mojo->helper(
+ 'user.auth_request_p' => sub {
+ my $c = shift;
+ my $method = shift;
+ my $path = shift;
+
+ my $ua = $plugin->ua;
+
+ my $tx;
+ if ($c->user_auth) {
+ $tx = $plugin->build_authorized_tx(
+ $c->user_auth, $c->client_ip, uc($method), $path, @_
+ );
+ }
+ else {
+ $tx = $ua->build_tx(
+ uc($method), $path, @_
+ );
+ };
+
+ # Create a promise object
+ return $ua->start_p($tx);
+ }
+ );
+
# Login
$mojo->helper(
diff --git a/t/fixture.t b/t/fixture.t
index 3fd0bd3..6fac855 100644
--- a/t/fixture.t
+++ b/t/fixture.t
@@ -32,6 +32,14 @@
->json_is('/errors/1/1','Could not parse query >>> [orth=das <<<.')
;
+$t->get_ok('/search?q=baum&ql=poliqarp')
+ ->status_is(200)
+ ->json_is('/meta/count', 25)
+ ->json_is('/meta/serialQuery', "tokens:s:Baum")
+ ->json_is('/matches/0/docSigle', "GOE/AGI")
+ ;
+
+
done_testing;
__END__
diff --git a/t/fixtures/fake_backend.pl b/t/fixtures/fake_backend.pl
index 73b4561..e7fc518 100644
--- a/t/fixtures/fake_backend.pl
+++ b/t/fixtures/fake_backend.pl
@@ -54,6 +54,7 @@
shift->render(text => 'Fake server available');
};
+
# Search fixtures
get '/search' => sub {
my $c = shift;
@@ -64,6 +65,8 @@
$v->optional('count');
$v->optional('context');
+ $c->app->log->debug('Receive request');
+
# Response q=x&ql=cosmas3
if ($v->param('ql') && $v->param('ql') eq 'cosmas3') {
return $c->render(
@@ -93,9 +96,12 @@
$response->{json}->{meta}->{startIndex} = $v->param("startIndex");
};
-
# Simple search fixture
- return $c->render(%$response);
+ $c->render(%$response);
+
+ $c->app->log->debug('Rendered result');
+
+ return 1;
};
diff --git a/t/query.t b/t/query.t
index de05a78..75ed9b8 100644
--- a/t/query.t
+++ b/t/query.t
@@ -1,16 +1,37 @@
use Mojo::Base -strict;
use Test::Mojo;
use Test::More;
+use Mojo::File qw/path/;
+
+
+#####################
+# Start Fake server #
+#####################
+my $mount_point = '/api/';
+$ENV{KALAMAR_API} = $mount_point;
my $t = Test::Mojo->new('Kalamar');
+# Mount fake backend
+# Get the fixture path
+my $fixtures_path = path(Mojo::File->new(__FILE__)->dirname, 'fixtures');
+my $fake_backend = $t->app->plugin(
+ Mount => {
+ $mount_point =>
+ $fixtures_path->child('fake_backend.pl')
+ }
+);
+# Configure fake backend
+$fake_backend->pattern->defaults->{app}->log($t->app->log);
+
# Query passed
-$t->get_ok('/q2?q=hui')
+$t->get_ok('/q2?q=baum')
->status_is(200)
->text_is('#error','')
- ->text_is('title', 'KorAP: Find »hui« with Poliqarp')
- ->element_exists('meta[name="DC.title"][content="KorAP: Find »hui« with Poliqarp"]')
+ ->text_is('title', 'KorAP: Find »baum« with Poliqarp')
+ ->element_exists('meta[name="DC.title"][content="KorAP: Find »baum« with Poliqarp"]')
->element_exists('body[itemscope][itemtype="http://schema.org/SearchResultsPage"]')
+ ->text_is('#total-results', 51)
;
diff --git a/t/remote_user.t b/t/remote_user.t
index 96897e5..ee0b872 100644
--- a/t/remote_user.t
+++ b/t/remote_user.t
@@ -4,6 +4,10 @@
use Mojo::File qw/path/;
use Data::Dumper;
+
+#####################
+# Start Fake server #
+#####################
my $mount_point = '/api/';
$ENV{KALAMAR_API} = $mount_point;
@@ -19,10 +23,10 @@
$fixtures_path->child('fake_backend.pl')
}
);
-
# Configure fake backend
$fake_backend->pattern->defaults->{app}->log($t->app->log);
+
$t->get_ok('/api')
->status_is(200)
->content_is('Fake server available');
@@ -58,6 +62,7 @@
->tx->res->dom->at('input[name=csrf_token]')->attr('value')
;
+
$t->post_ok('/user/login' => form => { handle_or_email => 'test', pwd => 'pass', csrf_token => $csrf })
->status_is(302)
->header_is('Location' => '/');
diff --git a/templates/search2.html.ep b/templates/search2.html.ep
index 6ed9c9e..200d693 100644
--- a/templates/search2.html.ep
+++ b/templates/search2.html.ep
@@ -1,6 +1,6 @@
% layout 'main', title => loc('searchtitle', q => stash('q'), ql => stash('ql')), schematype => 'SearchResultsPage';
-<div id="resultinfo" <% if (stash('results_size')) { %> class="found"<%} %>>
+<div id="resultinfo" <% if (stash('results')->size) { %> class="found"<%} %>>
<div id="pagination"><%= pagination(stash('start_page'), stash('total_pages'), url_with->query(['p' => '{page}'])) =%></div>
% my $found = stash('total_results');
<p class="found">\
@@ -11,7 +11,7 @@
% };
<span id="total-results"><%= $found_text %></span> <%= loc('matchCount', found => $found) %>\
%# <% if (stash('benchmark')) { %> (~ <%= stash('benchmark') %>)<% } %>
-% } elsif (stash('start_index') == 0 && stash('result_size') == 0) {
+% } elsif (stash('start_index') == 0 && stash('results')->size == 0) {
<span id="total-results">0</span> <%= loc('matchCount', found => $found) %>\
% };
</p>
@@ -20,13 +20,13 @@
%= include 'query2'
<div id="search">
-% if (stash('total_results') != 0 && stash('result_size')) {
+% if (stash('total_results') != 0 && stash('results')->size) {
<ol class="align-left">
%= search_results begin
%= include 'match', match => $_
% end
</ol>
-% } elsif (stash('result_size') == 0) {
+% } elsif (stash('results')->size == 0) {
<p id="no-results"><%= loc 'noMatches', q => stash('q'), ql => stash('ql') %></p>
% }
</div>