umlaut 3.2.0 → 3.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +1 -1
- data/app/assets/stylesheets/umlaut/_resolve.scss +1 -1
- data/app/controllers/feedback_controller.rb +46 -0
- data/app/controllers/resource_controller.rb +19 -1
- data/app/controllers/search_methods/sfx4.rb +15 -0
- data/app/controllers/umlaut_controller.rb +6 -0
- data/app/helpers/emailer_helper.rb +17 -3
- data/app/mailers/feedback_mailer.rb +25 -0
- data/app/mixin_logic/metadata_helper.rb +37 -0
- data/app/models/referent.rb +22 -19
- data/app/models/request.rb +3 -0
- data/app/referent_filters/dissertation_catch.rb +1 -1
- data/app/service_adaptors/all_books_dot_com.rb +17 -0
- data/app/service_adaptors/illiad.rb +161 -0
- data/app/service_adaptors/isbn_db.rb +6 -1
- data/app/service_adaptors/isbn_link.rb +57 -0
- data/app/service_adaptors/scopus.rb +4 -0
- data/app/service_adaptors/scopus2.rb +330 -0
- data/app/views/feedback/_resolve_section.html.erb +16 -0
- data/app/views/feedback/new.html.erb +33 -0
- data/app/views/feedback_mailer/feedback.text.erb +23 -0
- data/app/views/layouts/umlaut.html.erb +2 -0
- data/app/views/umlaut/_alerts.html.erb +14 -0
- data/lib/generators/templates/umlaut_services.yml +8 -8
- data/lib/umlaut/routes.rb +8 -1
- data/lib/umlaut/version.rb +1 -1
- data/test/dummy/tmp/cache/assets/BFF/760/sprockets%2Fe00969069e468419c393709f042b4527 +0 -0
- data/test/dummy/tmp/cache/assets/C9D/060/sprockets%2F5c8956a1666824a1d214531abd22e2a2 +0 -0
- data/test/dummy/tmp/cache/assets/CD8/370/sprockets%2F357970feca3ac29060c1e3861e2c0953 +0 -0
- data/test/dummy/tmp/cache/assets/D15/FC0/sprockets%2F8cbf3a8b7acb7fc27a42168846226385 +0 -0
- data/test/dummy/tmp/cache/assets/D32/A10/sprockets%2F13fe41fee1fe35b49d145bcc06610705 +0 -0
- data/test/dummy/tmp/cache/assets/D4E/1B0/sprockets%2Ff7cbd26ba1d28d48de824f0e94586655 +0 -0
- data/test/dummy/tmp/cache/assets/D5A/EA0/sprockets%2Fd771ace226fc8215a3572e0aa35bb0d6 +0 -0
- data/test/dummy/tmp/cache/assets/D98/990/sprockets%2F710ede3e7f5ebab14e9772fa88c00c02 +0 -0
- data/test/dummy/tmp/cache/assets/DAD/BA0/sprockets%2F193f81f7e4eae26eaaa7d909c0c8e956 +0 -0
- data/test/dummy/tmp/cache/assets/DCB/620/sprockets%2F2332a294ceeab3ab9b5ee643989dc0eb +0 -0
- data/test/dummy/tmp/cache/assets/DDC/400/sprockets%2Fcffd775d018f68ce5dba1ee0d951a994 +0 -0
- data/test/dummy/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
- data/test/functional/feedback_controller_test.rb +59 -0
- data/test/test_helper.rb +13 -0
- data/test/unit/feedback_mailer_test.rb +57 -0
- data/test/unit/illiad_test.rb +146 -0
- data/test/unit/metadata_helper_test.rb +49 -0
- data/test/unit/referent_to_citation_test.rb +45 -0
- data/test/unit/scopus2_test.rb +147 -0
- data/test/vcr_cassettes/scopus/live_test_with_no_hits.yml +52 -0
- data/test/vcr_cassettes/scopus/live_test_with_result.yml +62 -0
- data/test/vcr_cassettes/scopus/live_trigger_scopus_error.yml +50 -0
- metadata +34 -120
- data/test/dummy/tmp/cache/sass/b43409235ed55124ccf6a0235156711682d0fe98/umlaut.css.scssc +0 -0
- data/test/dummy/tmp/cache/sass/d2b87393a9fcb33d01e765e1c09b76db7f14f647/bootstrap-responsive.scssc +0 -0
- data/test/dummy/tmp/cache/sass/d2b87393a9fcb33d01e765e1c09b76db7f14f647/bootstrap.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_accordion.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_alerts.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_breadcrumbs.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_button-groups.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_buttons.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_carousel.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_close.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_code.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_component-animations.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_dropdowns.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_forms.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_grid.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_hero-unit.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_labels-badges.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_layouts.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_media.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_mixins.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_modals.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_navbar.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_navs.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_pager.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_pagination.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_popovers.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_progress-bars.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_reset.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_responsive-1200px-min.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_responsive-767px-max.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_responsive-768px-979px.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_responsive-navbar.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_responsive-utilities.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_scaffolding.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_sprites.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_tables.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_thumbnails.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_tooltip.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_type.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_utilities.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_variables.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/_wells.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/bootstrap.scssc +0 -0
- data/test/dummy/tmp/cache/sass/db21ae7b0d8da2224e8e1d588985ee0507dc926a/responsive.scssc +0 -0
- data/test/dummy/tmp/cache/sass/fc140da5c32e19b5c3f84a23bb2383ebc83fcc20/_admin.scssc +0 -0
- data/test/dummy/tmp/cache/sass/fc140da5c32e19b5c3f84a23bb2383ebc83fcc20/_az.scssc +0 -0
- data/test/dummy/tmp/cache/sass/fc140da5c32e19b5c3f84a23bb2383ebc83fcc20/_forms.scssc +0 -0
- data/test/dummy/tmp/cache/sass/fc140da5c32e19b5c3f84a23bb2383ebc83fcc20/_icons.scssc +0 -0
- data/test/dummy/tmp/cache/sass/fc140da5c32e19b5c3f84a23bb2383ebc83fcc20/_layout.scssc +0 -0
- data/test/dummy/tmp/cache/sass/fc140da5c32e19b5c3f84a23bb2383ebc83fcc20/_misc.scssc +0 -0
- data/test/dummy/tmp/cache/sass/fc140da5c32e19b5c3f84a23bb2383ebc83fcc20/_mixins.scssc +0 -0
- data/test/dummy/tmp/cache/sass/fc140da5c32e19b5c3f84a23bb2383ebc83fcc20/_modal.scssc +0 -0
- data/test/dummy/tmp/cache/sass/fc140da5c32e19b5c3f84a23bb2383ebc83fcc20/_resolve.scssc +0 -0
- data/test/dummy/tmp/cache/sass/fc140da5c32e19b5c3f84a23bb2383ebc83fcc20/_results.scssc +0 -0
- data/test/dummy/tmp/cache/sass/fc140da5c32e19b5c3f84a23bb2383ebc83fcc20/_search.scssc +0 -0
- data/test/dummy/tmp/cache/sass/fc140da5c32e19b5c3f84a23bb2383ebc83fcc20/_spinner.scssc +0 -0
- data/test/dummy/tmp/cache/sass/fc140da5c32e19b5c3f84a23bb2383ebc83fcc20/_variables.scssc +0 -0
@@ -0,0 +1,330 @@
|
|
1
|
+
# Service adapter plug-in.
|
2
|
+
#
|
3
|
+
#
|
4
|
+
# PURPOSE: Includes "cited by", "similar articles" and "more by these authors"
|
5
|
+
# links from scopus.
|
6
|
+
#
|
7
|
+
# LIMTATIONS: You must be a Scopus customer for these links generated to work
|
8
|
+
# for your users at all! Off-campus users should be probably going through ezproxy, see
|
9
|
+
# the EZProxy plug-in.
|
10
|
+
# Must find a match in scopus, naturally. "cited by" will only
|
11
|
+
# be included if Scopus has non-0 "cited by" links. But there's no good way
|
12
|
+
# to precheck similar/more-by for this, so they are provided blind and may
|
13
|
+
# result in 0 hits. You can turn them off if you like, with @include_similar,
|
14
|
+
# and @include_more_by_authors.
|
15
|
+
# Abstracts are not used because it seems to violate Scopus terms of service
|
16
|
+
# to use them.
|
17
|
+
#
|
18
|
+
# REGISTERING: Register for a Scopus API key at:
|
19
|
+
# http://www.developers.elsevier.com/action/devprojects?pageOrigin=cmsPage&zone=topNavBar
|
20
|
+
# Look for "Register a new site" button at the bottom right of the page.
|
21
|
+
#
|
22
|
+
# For the second Scopus API, you theoretically need a Scopus "PartnerID" and
|
23
|
+
# corresponding "release number", in @partner_id and @scopus_release
|
24
|
+
# There's no real easy way to get one. Scopus says:
|
25
|
+
# "To obtain a partner ID or release number, contact your nearest regional
|
26
|
+
# Scopus office. A list of Scopus contacts is available at
|
27
|
+
# http://www.info.scopus.com/contactus/index.shtml"
|
28
|
+
# Bah! But fortunately, using the "partnerID" assigned to the Scopus Json
|
29
|
+
# API, 65, _seems_ to work, and is coded here as the default. You could try
|
30
|
+
# going with that. When you register a partnerID, you also get a 'salt key',
|
31
|
+
# which is currently not used by this code, but @link_salt_key is reserved
|
32
|
+
# for it in case added functionality does later.
|
33
|
+
#
|
34
|
+
# SCOPUS USEFUL URLS:
|
35
|
+
#
|
36
|
+
# api key register: http://www.developers.elsevier.com/action/devprojects?pageOrigin=cmsPage&zone=topNavBar
|
37
|
+
#
|
38
|
+
# 'content policies' terms of use: http://www.developers.elsevier.com/cms/content-apis
|
39
|
+
#
|
40
|
+
# API overview docs: http://www.developers.elsevier.com/cms/content-apis
|
41
|
+
#
|
42
|
+
# Various other api docs? Confused myself as to organization here.
|
43
|
+
#
|
44
|
+
# * http://www.developers.elsevier.com/devcms/content-api-search-request
|
45
|
+
# * http://www.developers.elsevier.com/devcms/content/search-fields-overview
|
46
|
+
# * http://api.elsevier.com/content/search/#d0n14606
|
47
|
+
#
|
48
|
+
# Some API recommendations for federated search: http://www.developers.elsevier.com/cms/restful-api-federated-search
|
49
|
+
#
|
50
|
+
class Scopus2 < Service
|
51
|
+
require 'umlaut_http'
|
52
|
+
require 'nokogiri'
|
53
|
+
|
54
|
+
include ActionView::Helpers::SanitizeHelper
|
55
|
+
|
56
|
+
include MetadataHelper
|
57
|
+
include UmlautHttp
|
58
|
+
|
59
|
+
required_config_params :api_key
|
60
|
+
|
61
|
+
attr_accessor :scopus_search_base
|
62
|
+
|
63
|
+
def service_types_generated
|
64
|
+
types = []
|
65
|
+
types.push( ServiceTypeValue[:cited_by] ) if @include_cited_by
|
66
|
+
types.push( ServiceTypeValue[:abstract] ) if @include_abstract
|
67
|
+
types.push( ServiceTypeValue[:similar] ) if @include_similar
|
68
|
+
types.push( ServiceTypeValue[@more_by_authors_type] ) if @include_more_by_authors
|
69
|
+
|
70
|
+
return types
|
71
|
+
end
|
72
|
+
|
73
|
+
def initialize(config)
|
74
|
+
#defaults
|
75
|
+
@display_name = "Scopus"
|
76
|
+
@registered_referer
|
77
|
+
@scopus_search_base = 'http://api.elsevier.com/content/search/index:SCOPUS'
|
78
|
+
|
79
|
+
@include_cited_by = true
|
80
|
+
@include_similar = true
|
81
|
+
@include_more_by_authors = true
|
82
|
+
@more_by_authors_type = "similar"
|
83
|
+
|
84
|
+
@inward_cited_by_url = "http://www.scopus.com/scopus/inward/citedby.url"
|
85
|
+
#@partner_id = "E5wmcMWC"
|
86
|
+
@partner_id = 65
|
87
|
+
@link_salt_key = nil
|
88
|
+
@scopus_release = "R6.0.0"
|
89
|
+
|
90
|
+
# Scopus offers two algorithms for finding similar items.
|
91
|
+
# This variable can be:
|
92
|
+
# "key" => keyword based similarity
|
93
|
+
# "ref" => reference based similiarity (cites similar refs?) Seems to offer 0 hits quite often, so we use keyword instead.
|
94
|
+
# "aut" => author. More docs by same authors. Incorporated as seperate link usually.
|
95
|
+
@more_like_this_type = "key"
|
96
|
+
@inward_more_like_url = "http://www.scopus.com/scopus/inward/mlt.url"
|
97
|
+
|
98
|
+
@credits = {
|
99
|
+
@display_name => "http://www.scopus.com/home.url"
|
100
|
+
}
|
101
|
+
|
102
|
+
super(config)
|
103
|
+
end
|
104
|
+
|
105
|
+
def xml_namespaces
|
106
|
+
@xml_namespaces ||=
|
107
|
+
{ "atom" => "http://www.w3.org/2005/Atom",
|
108
|
+
"dc" => "http://purl.org/dc/elements/1.1/",
|
109
|
+
"opensearch" => "http://a9.com/-/spec/opensearch/1.1/",
|
110
|
+
"prism" => "http://prismstandard.org/namespaces/basic/2.0/"
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
def handle(request)
|
115
|
+
scopus_query = scopus_query(request)
|
116
|
+
|
117
|
+
# we can't make a good query, nevermind.
|
118
|
+
return request.dispatched(self, true) if scopus_query.blank?
|
119
|
+
|
120
|
+
url = scopus_url(scopus_query)
|
121
|
+
|
122
|
+
|
123
|
+
# Make the call.
|
124
|
+
headers = {"Accept" => "application/xml"}
|
125
|
+
headers["Referer"] = @registered_referer if @registered_referer
|
126
|
+
|
127
|
+
response = http_fetch(url, :headers => headers, :raise_on_http_error_code => false)
|
128
|
+
|
129
|
+
unless response.kind_of? Net::HTTPSuccess
|
130
|
+
# error, sometimes we have info in XML <service-error>
|
131
|
+
xml = begin
|
132
|
+
Nokogiri::XML(response.body)
|
133
|
+
rescue Exception
|
134
|
+
nil
|
135
|
+
end
|
136
|
+
|
137
|
+
code, message = nil, nil
|
138
|
+
if xml && error = xml.at_xpath("./service-error")
|
139
|
+
code = error.at_xpath("./status/statusCode")
|
140
|
+
message = error.at_xpath("./status/statusText")
|
141
|
+
end
|
142
|
+
e = StandardError.new("Scopus returned error: #{code}: #{message}: scopus query: #{url}")
|
143
|
+
return request.dispatched(self, DispatchedService::FailedFatal, e)
|
144
|
+
end
|
145
|
+
|
146
|
+
xml = Nokogiri::XML(response.body)
|
147
|
+
|
148
|
+
# Take the first hit from scopus's results, hope they relevancy ranked it
|
149
|
+
# well. For DOI/pmid search, there should ordinarly be only one hit!
|
150
|
+
first_hit = xml.at_xpath("//atom:entry[1]", xml_namespaces)
|
151
|
+
|
152
|
+
# Weirdly, a zero-hit result has one <atom:entry> containing an
|
153
|
+
# <atom:error> (Sic). Could other kinds of errors be reported that
|
154
|
+
# way too? Maybe. Better check just in case, ugh.
|
155
|
+
if first_hit && (error = first_hit.at_xpath("./atom:error", xml_namespaces))
|
156
|
+
scopus_message = error.text
|
157
|
+
|
158
|
+
if scopus_message == "Result set was empty"
|
159
|
+
# Just zero hits, no big deal, but nothing to do.
|
160
|
+
return request.dispatched(self, true)
|
161
|
+
else
|
162
|
+
# real error, log it.
|
163
|
+
e = StandardError.new("Scopus returned error: #{error.text}: scopus query: #{url}")
|
164
|
+
return request.dispatched(self, DispatchedService::FailedFatal, e)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
if first_hit
|
169
|
+
if first_hit && (error = first_hit.at_xpath("./atom:error", xml_namespaces))
|
170
|
+
e = StandardError.new("Scopus returned error: #{error.text}")
|
171
|
+
return request.dispatched(self, DispatchedService::FailedFatal, e)
|
172
|
+
end
|
173
|
+
|
174
|
+
if (@include_cited_by)
|
175
|
+
try_add_cited_by_response(first_hit, request)
|
176
|
+
end
|
177
|
+
|
178
|
+
if (@include_similar)
|
179
|
+
url = more_like_this_url(first_hit)
|
180
|
+
# Pre-checking for actual hits not currently working, disabled.
|
181
|
+
if (true || ( hits = check_for_hits(url) ) > 0 )
|
182
|
+
request.add_service_response(
|
183
|
+
:service=>self,
|
184
|
+
:display_text => "#{hits} #{ServiceTypeValue[:similar].display_name_pluralize.downcase.capitalize}",
|
185
|
+
:url => url,
|
186
|
+
:service_type_value => :similar)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
if ( @include_more_by_authors)
|
191
|
+
url = more_like_this_url(first_hit, :type => "aut")
|
192
|
+
# Pre-checking for actual hits not currently working, disabled.
|
193
|
+
if (true || ( hits = check_for_hits(url) ) > 0 )
|
194
|
+
request.add_service_response(
|
195
|
+
:service=>self,
|
196
|
+
:display_text => "#{hits} More from these authors",
|
197
|
+
:url => url,
|
198
|
+
:service_type_value => :similar)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
203
|
+
|
204
|
+
return request.dispatched(self, true)
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
# Returns a scopus advanced search query intended to find the exact
|
209
|
+
# known item identified by this citation.
|
210
|
+
#
|
211
|
+
# NOT uri-escaped yet, make sure to uri-escape before putting it in a uri
|
212
|
+
# param!
|
213
|
+
#
|
214
|
+
# Will try to use DOI or PMID if available. Otherwise
|
215
|
+
# will use issn/year/vol/iss/start page if available.
|
216
|
+
# In some cases may resort to author/title.
|
217
|
+
def scopus_query(request)
|
218
|
+
|
219
|
+
if (doi = get_doi(request.referent))
|
220
|
+
return "DOI(#{phrase(doi)})"
|
221
|
+
elsif (pmid = get_pmid(request.referent))
|
222
|
+
return "PMID(#{phrase(pmid)})"
|
223
|
+
elsif (isbn = get_isbn(request.referent))
|
224
|
+
# I don't think scopus has a lot of ISBN-holding citations, but
|
225
|
+
# it allows search so we might as well try.
|
226
|
+
return "ISBN(#{phrase(isbn)})"
|
227
|
+
else
|
228
|
+
# Okay, we're going to try to do it on issn/vol/issue/page.
|
229
|
+
# If we don't have issn, we'll reluctantly use journal title
|
230
|
+
# (damn you google scholar).
|
231
|
+
metadata = request.referent.metadata
|
232
|
+
issn = request.referent.issn
|
233
|
+
if ( (issn || ! metadata['jtitle'].blank? ) &&
|
234
|
+
! metadata['volume'].blank? &&
|
235
|
+
! metadata['issue'].blank? &&
|
236
|
+
! metadata['spage'].blank? )
|
237
|
+
query = "VOLUME(#{phrase(metadata['volume'])}) AND ISSUE(#{phrase(metadata['issue'])}) AND PAGEFIRST(#{phrase(metadata['spage'])}) "
|
238
|
+
if ( issn )
|
239
|
+
query += " AND (ISSN(#{phrase(issn)}) OR EISSN(#{phrase(issn)}))"
|
240
|
+
else
|
241
|
+
query += " AND EXACTSRCTITLE(#{phrase(metadata['jtitle'])})"
|
242
|
+
end
|
243
|
+
return query
|
244
|
+
end
|
245
|
+
end
|
246
|
+
return nil
|
247
|
+
end
|
248
|
+
|
249
|
+
def scopus_url(query)
|
250
|
+
"#{@scopus_search_base}?apiKey=#{CGI.escape @api_key}&query=#{CGI.escape query}"
|
251
|
+
end
|
252
|
+
|
253
|
+
# backslash escapes any double quotes, and embeds string in scopus
|
254
|
+
# phrase search double quotes. Does NOT uri-escape.
|
255
|
+
def phrase(str)
|
256
|
+
'"' + str.gsub('"', '\\"') + '"'
|
257
|
+
end
|
258
|
+
|
259
|
+
# Input is a ruby hash that came from the scopus JSON, representing
|
260
|
+
# a single hit. We're going to add this as a result.
|
261
|
+
def try_add_cited_by_response(result, request)
|
262
|
+
# While scopus provides an "inwardurl" in the results, this just takes
|
263
|
+
# us to the record detail page. We actually want to go RIGHT to the
|
264
|
+
# list of cited-by items. So we create our own, based on Scopus's
|
265
|
+
# reversed engineered predictable URLs.
|
266
|
+
|
267
|
+
count_str = result.at_xpath("atom:citedby-count/text()", xml_namespaces).to_s
|
268
|
+
count_i = count_str.to_i
|
269
|
+
|
270
|
+
return if count_i < 1
|
271
|
+
|
272
|
+
label = ServiceTypeValue[:cited_by].display_name_pluralize.downcase.capitalize
|
273
|
+
if count_i == 1
|
274
|
+
label = ServiceTypeValue[:cited_by].display_name.downcase.capitalize
|
275
|
+
end
|
276
|
+
cited_by_url = cited_by_url( result )
|
277
|
+
|
278
|
+
request.add_service_response(:service=>self,
|
279
|
+
:display_text => "#{count_str} #{label}",
|
280
|
+
:count=> count_i,
|
281
|
+
:url => cited_by_url,
|
282
|
+
:service_type_value => :cited_by)
|
283
|
+
end
|
284
|
+
|
285
|
+
def eid_from_hit(result)
|
286
|
+
result.at_xpath("atom:eid/text()", xml_namespaces).to_s
|
287
|
+
end
|
288
|
+
|
289
|
+
def cited_by_url(result)
|
290
|
+
eid = CGI.escape( eid_from_hit(result) )
|
291
|
+
#return "#{@scopus_cited_by_base}?eid=#{eid}&src=s&origin=recordpage"
|
292
|
+
# Use the new scopus direct link format!
|
293
|
+
return "#{@inward_cited_by_url}?partnerID=#{@partner_id}&rel=#{@scopus_release}&eid=#{eid}"
|
294
|
+
return
|
295
|
+
end
|
296
|
+
|
297
|
+
def more_like_this_url(result, options = {})
|
298
|
+
options[:type] ||= @more_like_this_type
|
299
|
+
eid = CGI.escape eid_from_hit(result)
|
300
|
+
|
301
|
+
return "#{@inward_more_like_url}?partnerID=#{@partner_id}&rel=#{@scopus_release}&eid=#{eid}&mltType=#{options[:type]}"
|
302
|
+
end
|
303
|
+
|
304
|
+
# NOT currently working. Scopus doesn't make this easy.
|
305
|
+
# Takes a scopus direct url for which we're not sure if there will be results
|
306
|
+
# or not, and requests it and html screen-scrapes to get hit count. (We
|
307
|
+
# can conveniently find this just in the html <title> at least).
|
308
|
+
# Works for cited_by and more_like_this searches at present.
|
309
|
+
# May break if Scopus changes their html title!
|
310
|
+
def check_for_hits(url)
|
311
|
+
|
312
|
+
response = http_fetch(url).body
|
313
|
+
|
314
|
+
response_html = Nokogiri::HTML(response)
|
315
|
+
|
316
|
+
title = response_xml.at('title').inner_text
|
317
|
+
# title is "X documents" (or 'Documents') if there are hits.
|
318
|
+
# It's annoyingly "Search Error" if there are either 0 hits, or
|
319
|
+
# if there was an actual error. So we can't easily log actual
|
320
|
+
# errors, sorry.
|
321
|
+
title.downcase =~ /^\s*(\d+)?\s+document/
|
322
|
+
if ( hits = $1)
|
323
|
+
return hits.to_i
|
324
|
+
else
|
325
|
+
return 0
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
|
330
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<div class="help-text-icon">?</div>
|
2
|
+
<%= render 'section_heading', :presenter => renderer, :force_render => true %>
|
3
|
+
|
4
|
+
<ul class="response_list umlaut-help-list">
|
5
|
+
<% umlaut_config.feedback.contacts.each_pair do |key, config| %>
|
6
|
+
<li>
|
7
|
+
<% if config[:link_out] %>
|
8
|
+
<%# simply link out to somewhere outside of umlaut %>
|
9
|
+
<%= link_to config[:label], config[:link_out] %>
|
10
|
+
<% else %>
|
11
|
+
<%# link to our feedback form %>
|
12
|
+
<%= link_to config[:label], feedback_path(umlaut_request.id, :contact_id => key) %>
|
13
|
+
<% end %>
|
14
|
+
</li>
|
15
|
+
<% end %>
|
16
|
+
</ul>
|
@@ -0,0 +1,33 @@
|
|
1
|
+
<h1>Contact Us <small>Send us questions, problems, or comments</small></h1>
|
2
|
+
|
3
|
+
<%= form_tag(feedback_path(params[:request_id], :contact_id => params[:contact_id]) , :method => :post, :class => "feedback-form form-horizontal") do %>
|
4
|
+
|
5
|
+
<div class="control-group">
|
6
|
+
<label class="control-label" for="name">Your Name</label>
|
7
|
+
<div class="controls">
|
8
|
+
<input type="text" id="name" name="name">
|
9
|
+
</div>
|
10
|
+
</div>
|
11
|
+
|
12
|
+
<div class="control-group">
|
13
|
+
<label class="control-label" for="email">Your Email</label>
|
14
|
+
<div class="controls">
|
15
|
+
<input type="text" id="email" name="email">
|
16
|
+
</div>
|
17
|
+
</div>
|
18
|
+
|
19
|
+
<div class="control-group">
|
20
|
+
<label class="control-label" for="feedback">Question or Comment</label>
|
21
|
+
<div class="controls">
|
22
|
+
<textarea id="feedback" name="feedback" class="input-xxlarge" rows=6></textarea>
|
23
|
+
</div>
|
24
|
+
</div>
|
25
|
+
|
26
|
+
|
27
|
+
<div class="control-group">
|
28
|
+
<div class="controls">
|
29
|
+
<button type="submit" class="btn">Send</button>
|
30
|
+
</div>
|
31
|
+
</div>
|
32
|
+
|
33
|
+
<% end %>
|
@@ -0,0 +1,23 @@
|
|
1
|
+
** User **
|
2
|
+
|
3
|
+
<%= @name %> <<%= @email %>>
|
4
|
+
|
5
|
+
** User Comments **
|
6
|
+
|
7
|
+
<%= @feedback %>
|
8
|
+
|
9
|
+
** Citation **
|
10
|
+
|
11
|
+
<%- if @umlaut_request -%>
|
12
|
+
<%= brief_citation(@umlaut_request, :include_labels => true) %>
|
13
|
+
<%= citation_identifiers(@umlaut_request) %>
|
14
|
+
<%- if @umlaut_request.referent.permalinks.present? -%>
|
15
|
+
Permalink: <%= url_for(:controller=>"store",
|
16
|
+
:id=> @umlaut_request.referent.permalinks.first.id,
|
17
|
+
:only_path => false, :host => @host) %>
|
18
|
+
|
19
|
+
Original Source: <%= @umlaut_request.referrer_id %>
|
20
|
+
<%- end -%>
|
21
|
+
<%- else -%>
|
22
|
+
No citation supplied.
|
23
|
+
<%- end -%>
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<%-
|
2
|
+
text, css_class = nil, nil
|
3
|
+
[:alert_success, :alert_info, :alert_error].each do |key|
|
4
|
+
if flash[key]
|
5
|
+
text = flash[key]
|
6
|
+
css_class = key.to_s.parameterize
|
7
|
+
end
|
8
|
+
end
|
9
|
+
-%>
|
10
|
+
<% if text %>
|
11
|
+
<div class="alert <%= css_class %>">
|
12
|
+
<%= text %>
|
13
|
+
</div>
|
14
|
+
<% end %>
|
@@ -29,7 +29,14 @@ default:
|
|
29
29
|
disabled: true
|
30
30
|
display_name: Find It
|
31
31
|
base_url: YOUR_SFX_BASE_URL
|
32
|
-
priority: 3
|
32
|
+
priority: 3
|
33
|
+
|
34
|
+
# blind link to price comparison site AllBooks.com.
|
35
|
+
# just a blind link with no API pre-check, will be near
|
36
|
+
# instantaneous to calculate.
|
37
|
+
AllBooksDotCom:
|
38
|
+
type: AllBooksDotCom
|
39
|
+
priority: 3
|
33
40
|
|
34
41
|
|
35
42
|
# First half of Amazon, run in foreground, get metadata and cover images.
|
@@ -134,13 +141,6 @@ default:
|
|
134
141
|
type: UlrichsLink
|
135
142
|
priority: d
|
136
143
|
|
137
|
-
IsbnDb:
|
138
|
-
display_name: isbndb.com
|
139
|
-
disabled: true
|
140
|
-
type: IsbnDb
|
141
|
-
priority: d
|
142
|
-
access_key: YOUR_ISBNDB_ACCESS_KEY
|
143
|
-
|
144
144
|
EmailExport:
|
145
145
|
type: EmailExport
|
146
146
|
priority: d
|
data/lib/umlaut/routes.rb
CHANGED
@@ -31,7 +31,7 @@ module Umlaut
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def default_route_sets
|
34
|
-
[:root, :permalinks, :a_z, :resolve, :open_search, :link_router, :export_email, :resources, :search, :javascript]
|
34
|
+
[:root, :permalinks, :a_z, :resolve, :open_search, :link_router, :export_email, :resources, :search, :javascript, :feedback]
|
35
35
|
end
|
36
36
|
|
37
37
|
module RouteSets
|
@@ -151,6 +151,13 @@ module Umlaut
|
|
151
151
|
match 'images/spinner.gif' => redirect("/assets/spinner.gif")
|
152
152
|
end
|
153
153
|
end
|
154
|
+
|
155
|
+
def feedback
|
156
|
+
add_routes do |options|
|
157
|
+
get 'feedback(/:request_id)', :to => "feedback#new", :as => "feedback"
|
158
|
+
post 'feedback(/:request_id)' => 'feedback#create'
|
159
|
+
end
|
160
|
+
end
|
154
161
|
|
155
162
|
def admin
|
156
163
|
add_routes do |options|
|
data/lib/umlaut/version.rb
CHANGED
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class FeedbackControllerTest < ActionController::TestCase
|
4
|
+
setup do
|
5
|
+
@umlaut_request = fake_umlaut_request("/resolve?sid=google&auinit=S&aulast=Madsbad&atitle=Mechanisms+of+changes+in+glucose+metabolism+and+bodyweight+after+bariatric+surgery&id=doi:10.1016/S2213-8587(13)70218-3&title=The+Lancet+Diabetes+%26+Endocrinology&volume=2&issue=2&date=2014&spage=152&issn=2213-8587")
|
6
|
+
@umlaut_request.save!
|
7
|
+
|
8
|
+
FeedbackController.umlaut_config.feedback ||= Confstruct::Configuration.new
|
9
|
+
FeedbackController.umlaut_config.feedback.contacts ||= Confstruct::Configuration.new
|
10
|
+
FeedbackController.umlaut_config.feedback.contacts[:example] = Confstruct::Configuration.new(
|
11
|
+
:email_address => "librarian@example.org",
|
12
|
+
:label => "Librarian"
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
test "display feedback form" do
|
17
|
+
get :new, :contact_id => "example"
|
18
|
+
assert_response :success
|
19
|
+
assert_template "feedback/new"
|
20
|
+
end
|
21
|
+
|
22
|
+
test "send feedback" do
|
23
|
+
assert_difference 'ActionMailer::Base.deliveries.size', +1 do
|
24
|
+
post :create, :contact_id => 'example', :name => "Joe Example", :email => "joe@example.org", :feedback => "Some feedback", :request_id => @umlaut_request.id
|
25
|
+
end
|
26
|
+
email = ActionMailer::Base.deliveries.last
|
27
|
+
|
28
|
+
assert_equal ["librarian@example.org"], email.to
|
29
|
+
assert_equal ["joe@example.org"], email.reply_to
|
30
|
+
assert_include email.body, "Some feedback"
|
31
|
+
|
32
|
+
assert_present flash[:alert_success]
|
33
|
+
|
34
|
+
assert_redirected_to(:controller => "resolve", :action => "index", :'umlaut.request_id' => @umlaut_request.id)
|
35
|
+
end
|
36
|
+
|
37
|
+
test "#create raises error on missing contact_id email" do
|
38
|
+
assert_raise(FeedbackController::NoFeedbackEmailFoundException) do
|
39
|
+
post :create, :name => "Joe Example", :email => "joe@example.org", :feedback => "Some feedback", :request_id => @umlaut_request.id
|
40
|
+
end
|
41
|
+
|
42
|
+
assert_raise(FeedbackController::NoFeedbackEmailFoundException) do
|
43
|
+
post :create, :contact_id => "NO_SUCH_ID", :name => "Joe Example", :email => "joe@example.org", :feedback => "Some feedback", :request_id => @umlaut_request.id
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
test "#new raises error on missing contact_id email" do
|
48
|
+
assert_raise(FeedbackController::NoFeedbackEmailFoundException) do
|
49
|
+
get :new
|
50
|
+
end
|
51
|
+
|
52
|
+
assert_raise(FeedbackController::NoFeedbackEmailFoundException) do
|
53
|
+
get :new, :contact_id => "NO_SUCH_ID"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
|
59
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -95,3 +95,16 @@ end
|
|
95
95
|
def assert_length(size, list)
|
96
96
|
assert_equal size, list.length, "Expected size of #{size} for #{list}"
|
97
97
|
end
|
98
|
+
|
99
|
+
|
100
|
+
# Methods you can use to make a mocked up Rails Request and corersponding Umlaut Request
|
101
|
+
# Pass in a URL, absolute or partial, eg "/resolve?isbn=X"
|
102
|
+
def fake_rails_request(umlaut_url)
|
103
|
+
# hard to figure out how to mock a request, this seems to work
|
104
|
+
ActionController::TestRequest.new(Rack::MockRequest.env_for(umlaut_url))
|
105
|
+
end
|
106
|
+
|
107
|
+
def fake_umlaut_request(umlaut_url)
|
108
|
+
rails_request = fake_rails_request(umlaut_url)
|
109
|
+
Request.find_or_create(rails_request.params, {}, rails_request)
|
110
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class FeedbackMailerTest < ActionMailer::TestCase
|
6
|
+
setup do
|
7
|
+
ActionMailer::Base.deliveries.clear
|
8
|
+
|
9
|
+
@host = "umlaut.example.org"
|
10
|
+
@to_email = "librarian@example.org"
|
11
|
+
end
|
12
|
+
|
13
|
+
test "feedback" do
|
14
|
+
umlaut_request = fake_umlaut_request("/resolve?sid=google&auinit=S&aulast=Madsbad&atitle=Mechanisms+of+changes+in+glucose+metabolism+and+bodyweight+after+bariatric+surgery&id=doi:10.1016/S2213-8587(13)70218-3&title=The+Lancet+Diabetes+%26+Endocrinology&volume=2&issue=2&date=2014&spage=152&issn=2213-8587")
|
15
|
+
|
16
|
+
feedback_name = "Joe Example"
|
17
|
+
feedback_email = "joe@example.org"
|
18
|
+
feedback_text = "This is my feedback, yes it is"
|
19
|
+
|
20
|
+
email = FeedbackMailer.feedback(@host, @to_email, :umlaut_request => umlaut_request, :name => feedback_name, :email => feedback_email, :feedback => feedback_text).deliver
|
21
|
+
|
22
|
+
assert ActionMailer::Base.deliveries.present?
|
23
|
+
assert_equal [UmlautController.umlaut_config.from_email_addr], email.from
|
24
|
+
assert_equal [feedback_email], email.reply_to
|
25
|
+
assert_equal [@to_email], email.to
|
26
|
+
assert_equal "#{UmlautController.umlaut_config.app_name} Feedback: #{feedback_name}", email.subject
|
27
|
+
|
28
|
+
assert_includes email.body, feedback_text
|
29
|
+
|
30
|
+
assert_includes email.body, "#{feedback_name} <#{feedback_email}>"
|
31
|
+
# citation
|
32
|
+
assert_includes email.body, "Mechanisms of changes in glucose metabolism and bodyweight"
|
33
|
+
assert_includes email.body, "Madsbad"
|
34
|
+
assert_includes email.body, "The Lancet Diabetes & Endocrinology"
|
35
|
+
assert_includes email.body, "Published: 2014 Vol: 2 Iss: 2 p. 152"
|
36
|
+
assert_includes email.body, "ISSN: 22138587"
|
37
|
+
assert_includes email.body, "10.1016/S2213-8587(13)70218-3" # doi
|
38
|
+
|
39
|
+
assert_includes email.body, "Original Source: #{umlaut_request.referrer_id}"
|
40
|
+
|
41
|
+
# Permalink properly generated and included
|
42
|
+
assert_present umlaut_request.referent.permalinks
|
43
|
+
assert_includes email.body, "Permalink: http"
|
44
|
+
end
|
45
|
+
|
46
|
+
test "feedback with no umlaut_request" do
|
47
|
+
feedback_name = "Joe Example"
|
48
|
+
feedback_email = "joe@example.org"
|
49
|
+
feedback_text = "This is my feedback, yes it is"
|
50
|
+
|
51
|
+
email = FeedbackMailer.feedback(@host, @to_email, :name => feedback_name, :email => feedback_email, :feedback => feedback_text).deliver
|
52
|
+
# just no raise is good enough for this test for now, mostly
|
53
|
+
|
54
|
+
assert_includes email.body, "No citation supplied"
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|