sufia 6.3.0 → 6.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -2
  3. data/.rubocop.yml +10 -0
  4. data/Gemfile +16 -7
  5. data/History.md +43 -0
  6. data/README.md +26 -19
  7. data/SUFIA_VERSION +1 -1
  8. data/app/assets/javascripts/notifications_check.js.erb +46 -0
  9. data/app/assets/javascripts/sufia.js +1 -2
  10. data/app/assets/javascripts/sufia/uploader.js +3 -3
  11. data/app/assets/stylesheets/sufia/_collections.scss +5 -0
  12. data/app/assets/stylesheets/sufia/_dashboard.scss +6 -1
  13. data/app/assets/stylesheets/sufia/_file-listing.scss +44 -6
  14. data/app/assets/stylesheets/sufia/_file-show.scss +4 -0
  15. data/app/assets/stylesheets/sufia/_settings.scss +3 -0
  16. data/app/controllers/api/items_controller.rb +7 -3
  17. data/app/controllers/concerns/sufia/admin/depositor_stats.rb +1 -1
  18. data/app/controllers/concerns/sufia/admin/stats_behavior.rb +6 -76
  19. data/app/controllers/concerns/sufia/batch_controller_behavior.rb +10 -2
  20. data/app/controllers/concerns/sufia/contact_form_controller_behavior.rb +1 -0
  21. data/app/controllers/concerns/sufia/files_controller_behavior.rb +11 -1
  22. data/app/controllers/concerns/sufia/homepage_controller.rb +1 -1
  23. data/app/controllers/concerns/sufia/my_controller_behavior.rb +2 -0
  24. data/app/controllers/concerns/sufia/users_controller_behavior.rb +2 -2
  25. data/app/helpers/generic_file_helper.rb +8 -5
  26. data/app/jobs/content_delete_event_job.rb +16 -11
  27. data/app/jobs/content_deposit_event_job.rb +4 -16
  28. data/app/jobs/content_depositor_change_event_job.rb +32 -20
  29. data/app/jobs/content_event_job.rb +39 -0
  30. data/app/jobs/content_new_version_event_job.rb +4 -16
  31. data/app/jobs/content_restored_version_event_job.rb +6 -19
  32. data/app/jobs/content_update_event_job.rb +4 -16
  33. data/app/jobs/event_job.rb +48 -4
  34. data/app/jobs/user_edit_profile_event_job.rb +4 -17
  35. data/app/jobs/user_follow_event_job.rb +10 -12
  36. data/app/jobs/user_unfollow_event_job.rb +10 -15
  37. data/app/models/concerns/sufia/solr_document_behavior.rb +11 -1
  38. data/app/models/system_stats.rb +108 -0
  39. data/app/presenters/sufia/admin_stats_presenter.rb +49 -0
  40. data/app/views/_controls.html.erb +1 -1
  41. data/app/views/_footer.html.erb +1 -1
  42. data/app/views/_logo.html.erb +1 -3
  43. data/app/views/admin/stats/_date_form.html.erb +8 -0
  44. data/app/views/admin/stats/_deposits.html.erb +2 -10
  45. data/app/views/admin/stats/_files.html.erb +6 -14
  46. data/app/views/admin/stats/_new_users.html.erb +7 -14
  47. data/app/views/admin/stats/_stats_by_date.html.erb +8 -0
  48. data/app/views/admin/stats/_top_data.html.erb +24 -0
  49. data/app/views/admin/stats/index.html.erb +5 -31
  50. data/app/views/collections/_form_for_select_collection.html.erb +5 -4
  51. data/app/views/collections/_show_actions.html.erb +7 -2
  52. data/app/views/collections/_show_document_list_row.html.erb +1 -9
  53. data/app/views/generic_files/_browse_everything.html.erb +3 -0
  54. data/app/views/generic_files/_descriptions.html.erb +1 -1
  55. data/app/views/generic_files/_generic_file.html.erb +1 -1
  56. data/app/views/generic_files/_local_file_import.html.erb +3 -0
  57. data/app/views/generic_files/_show_actions.html.erb +4 -0
  58. data/app/views/generic_files/upload/_form.html.erb +3 -0
  59. data/app/views/generic_files/upload/_to_collection.html.erb +5 -0
  60. data/app/views/homepage/_recent_document.html.erb +1 -7
  61. data/app/views/my/_index_partials/_default_group.html.erb +1 -1
  62. data/app/views/my/_index_partials/_list_collections.html.erb +3 -10
  63. data/app/views/my/_index_partials/_list_files.html.erb +13 -22
  64. data/app/views/my/_sort_and_per_page.html.erb +3 -3
  65. data/app/views/records/edit_fields/_rights.html.erb +2 -1
  66. data/app/views/static/terms.html.erb +1 -1
  67. data/config/locales/sufia.en.yml +13 -0
  68. data/lib/generators/sufia/templates/catalog_controller.rb +2 -2
  69. data/lib/sufia/version.rb +1 -1
  70. data/spec/actors/generic_file/actor_spec.rb +35 -0
  71. data/spec/controllers/admin_stats_controller_spec.rb +53 -23
  72. data/spec/controllers/api/items_controller_spec.rb +47 -41
  73. data/spec/controllers/batch_controller_spec.rb +1 -0
  74. data/spec/controllers/generic_files_controller_spec.rb +35 -1
  75. data/spec/controllers/my/files_controller_spec.rb +5 -0
  76. data/spec/factories/generic_files.rb +3 -0
  77. data/spec/features/collection_spec.rb +91 -0
  78. data/spec/features/contact_form_spec.rb +1 -0
  79. data/spec/forms/collection_edit_form_spec.rb +3 -3
  80. data/spec/forms/generic_file_edit_form_spec.rb +1 -1
  81. data/spec/jobs/create_derivatives_job_spec.rb +6 -0
  82. data/spec/models/file_content_datastream_spec.rb +1 -1
  83. data/spec/models/file_download_stat_spec.rb +4 -4
  84. data/spec/models/file_usage_spec.rb +2 -2
  85. data/spec/models/file_view_stat_spec.rb +4 -4
  86. data/spec/models/generic_file_spec.rb +15 -3
  87. data/spec/models/geo_names_resource_spec.rb +10 -0
  88. data/spec/models/solr_document_spec.rb +28 -0
  89. data/spec/models/system_stats_spec.rb +184 -0
  90. data/spec/models/user_spec.rb +1 -1
  91. data/spec/models/user_usage_stats_spec.rb +1 -1
  92. data/spec/services/generic_file_csv_service_spec.rb +66 -0
  93. data/spec/services/generic_file_indexing_service_spec.rb +35 -0
  94. data/spec/services/lock_manager_spec.rb +12 -0
  95. data/spec/spec_helper.rb +2 -1
  96. data/spec/views/admin/stats/index.html.erb_spec.rb +11 -10
  97. data/spec/views/catalog/sort_and_per_page.html.erb_spec.rb +1 -1
  98. data/spec/views/collections/_form_for_select_collection.html.erb_spec.rb +51 -0
  99. data/spec/views/generic_file/_browse_everything.html.erb_spec.rb +4 -0
  100. data/spec/views/generic_file/edit.html.erb_spec.rb +31 -24
  101. data/spec/views/generic_file/new.html.erb_spec.rb +70 -0
  102. data/spec/views/generic_file/show.html.erb_spec.rb +23 -0
  103. data/sufia.gemspec +3 -2
  104. data/tasks/sufia-dev.rake +2 -0
  105. metadata +42 -9
  106. data/lib/sufia/role_mapper.rb +0 -7
@@ -1,18 +1,6 @@
1
- class ContentNewVersionEventJob < EventJob
2
- def run
3
- gf = GenericFile.find(generic_file_id)
4
- action = "User #{link_to_profile depositor_id} has added a new version of #{link_to gf.title.first, Sufia::Engine.routes.url_helpers.generic_file_path(gf)}"
5
- timestamp = Time.now.to_i
6
- depositor = User.find_by_user_key(depositor_id)
7
- # Create the event
8
- event = depositor.create_event(action, timestamp)
9
- # Log the event to the depositor's profile stream
10
- depositor.log_profile_event(event)
11
- # Log the event to the GF's stream
12
- gf.log_event(event)
13
- # Fan out the event to all followers who have access
14
- depositor.followers.select { |user| user.can? :read, gf }.each do |follower|
15
- follower.log_event(event)
16
- end
1
+ # A specific job to log a file new version to a user's activity stream
2
+ class ContentNewVersionEventJob < ContentEventJob
3
+ def action
4
+ @action ||= "User #{link_to_profile depositor_id} has added a new version of #{link_to generic_file.title.first, Sufia::Engine.routes.url_helpers.generic_file_path(generic_file)}"
17
5
  end
18
6
  end
@@ -1,26 +1,13 @@
1
- class ContentRestoredVersionEventJob < EventJob
1
+ # A specific job to log a file restored version to a user's activity stream
2
+ class ContentRestoredVersionEventJob < ContentEventJob
2
3
  attr_accessor :revision_id
3
4
 
4
5
  def initialize(generic_file_id, depositor_id, revision_id)
5
- self.generic_file_id = generic_file_id
6
- self.depositor_id = depositor_id
7
- self.revision_id = revision_id
6
+ super(generic_file_id, depositor_id)
7
+ @revision_id = revision_id
8
8
  end
9
9
 
10
- def run
11
- gf = GenericFile.find(generic_file_id)
12
- action = "User #{link_to_profile depositor_id} has restored a version '#{revision_id}' of #{link_to gf.title.first, Sufia::Engine.routes.url_helpers.generic_file_path(gf)}"
13
- timestamp = Time.now.to_i
14
- depositor = User.find_by_user_key(depositor_id)
15
- # Create the event
16
- event = depositor.create_event(action, timestamp)
17
- # Log the event to the depositor's profile stream
18
- depositor.log_profile_event(event)
19
- # Log the event to the GF's stream
20
- gf.log_event(event)
21
- # Fan out the event to all followers who have access
22
- depositor.followers.select { |user| user.can? :read, gf }.each do |follower|
23
- follower.log_event(event)
24
- end
10
+ def action
11
+ @action ||= "User #{link_to_profile depositor_id} has restored a version '#{revision_id}' of #{link_to generic_file.title.first, Sufia::Engine.routes.url_helpers.generic_file_path(generic_file)}"
25
12
  end
26
13
  end
@@ -1,18 +1,6 @@
1
- class ContentUpdateEventJob < EventJob
2
- def run
3
- gf = GenericFile.find(generic_file_id)
4
- action = "User #{link_to_profile depositor_id} has updated #{link_to gf.title.first, Sufia::Engine.routes.url_helpers.generic_file_path(gf)}"
5
- timestamp = Time.now.to_i
6
- depositor = User.find_by_user_key(depositor_id)
7
- # Create the event
8
- event = depositor.create_event(action, timestamp)
9
- # Log the event to the depositor's profile stream
10
- depositor.log_profile_event(event)
11
- # Log the event to the GF's stream
12
- gf.log_event(event)
13
- # Fan out the event to all followers who have access
14
- depositor.followers.select { |user| user.can? :read, gf }.each do |follower|
15
- follower.log_event(event)
16
- end
1
+ # A specific job to log a file content update to a user's activity stream
2
+ class ContentUpdateEventJob < ContentEventJob
3
+ def action
4
+ @action ||= "User #{link_to_profile depositor_id} has updated #{link_to generic_file.title.first, Sufia::Engine.routes.url_helpers.generic_file_path(generic_file)}"
17
5
  end
18
6
  end
@@ -1,3 +1,9 @@
1
+ # A generic job for sending events to a user and their followers.
2
+ #
3
+ # This class does not implement a usable action, so it must be implemented in a child class
4
+ #
5
+ # @attr [String] depositor_id the user the event is specified for
6
+ #
1
7
  class EventJob
2
8
  include Rails.application.routes.url_helpers
3
9
  include ActionView::Helpers
@@ -5,14 +11,52 @@ class EventJob
5
11
  include Hydra::AccessControlsEnforcement
6
12
  include SufiaHelper
7
13
 
14
+ # queue to run the job on
8
15
  def queue_name
9
16
  :event
10
17
  end
11
18
 
12
- attr_accessor :generic_file_id, :depositor_id
19
+ attr_accessor :depositor_id
13
20
 
14
- def initialize(generic_file_id, depositor_id)
15
- self.generic_file_id = generic_file_id
16
- self.depositor_id = depositor_id
21
+ # @param the id of the user to create the event for
22
+ def initialize(depositor_id)
23
+ @depositor_id = depositor_id
24
+ end
25
+
26
+ # Method used to cause the event to be created
27
+ def run
28
+ # Log the event to the depositor's profile stream
29
+ log_user_event
30
+
31
+ # Fan out the event to all followers who have access
32
+ log_to_followers
33
+ end
34
+
35
+ # override to provide your specific action for the event you are logging
36
+ # @abstract
37
+ def action
38
+ raise(NotImplementedError, "#action should be implemented by an child class of EventJob")
39
+ end
40
+
41
+ # create an event with an action and a timestamp for the user
42
+ def event
43
+ @event ||= depositor.create_event(action, Time.now.to_i)
44
+ end
45
+
46
+ # the user that will be the subject of the event
47
+ def depositor
48
+ @depositor ||= User.find_by_user_key(depositor_id)
49
+ end
50
+
51
+ # log the event to the users event stream
52
+ def log_user_event
53
+ depositor.log_event(event)
54
+ end
55
+
56
+ # log the event to the users followers
57
+ def log_to_followers
58
+ depositor.followers.each do |follower|
59
+ follower.log_event(event)
60
+ end
17
61
  end
18
62
  end
@@ -1,21 +1,8 @@
1
+ # A specific job to log a user profile edit to a user's activity stream
1
2
  class UserEditProfileEventJob < EventJob
2
- attr_accessor :editor_id
3
+ alias_attribute :editor_id, :depositor_id
3
4
 
4
- def initialize(editor_id)
5
- self.editor_id = editor_id
6
- end
7
-
8
- def run
9
- action = "User #{link_to_profile editor_id} has edited his or her profile"
10
- timestamp = Time.now.to_i
11
- editor = User.find_by_user_key(editor_id)
12
- # Create the event
13
- event = editor.create_event(action, timestamp)
14
- # Log the event to the editor's stream
15
- editor.log_event(event)
16
- # Fan out the event to all followers
17
- editor.followers.each do |user|
18
- user.log_event(event)
19
- end
5
+ def action
6
+ @action ||= "User #{link_to_profile editor_id} has edited his or her profile"
20
7
  end
21
8
  end
@@ -1,23 +1,21 @@
1
+ # A specific job to log a user following another user to a user's activity stream
1
2
  class UserFollowEventJob < EventJob
2
- attr_accessor :follower_id, :followee_id
3
+ attr_accessor :followee_id
4
+ alias_attribute :follower_id, :depositor_id
3
5
 
4
6
  def initialize(follower_id, followee_id)
5
- self.follower_id = follower_id
6
- self.followee_id = followee_id
7
+ super(follower_id)
8
+ @followee_id = followee_id
7
9
  end
8
10
 
9
11
  def run
10
- # Create the event
11
- follower = User.find_by_user_key(follower_id)
12
- event = follower.create_event("User #{link_to_profile follower_id} is now following #{link_to_profile followee_id}", Time.now.to_i)
13
- # Log the event to the follower's stream
14
- follower.log_event(event)
12
+ super
15
13
  # Fan out the event to followee
16
14
  followee = User.find_by_user_key(followee_id)
17
15
  followee.log_event(event)
18
- # Fan out the event to all followers
19
- follower.followers.each do |user|
20
- user.log_event(event)
21
- end
16
+ end
17
+
18
+ def action
19
+ @action ||= "User #{link_to_profile follower_id} is now following #{link_to_profile followee_id}"
22
20
  end
23
21
  end
@@ -1,25 +1,20 @@
1
+ # A specific job to log a user unfollowing another user to a user's activity stream
1
2
  class UserUnfollowEventJob < EventJob
2
- attr_accessor :unfollower_id, :unfollowee_id
3
+ attr_accessor :unfollowee_id
4
+ alias_attribute :unfollower_id, :depositor_id
3
5
 
4
6
  def initialize(unfollower_id, unfollowee_id)
5
- self.unfollower_id = unfollower_id
6
- self.unfollowee_id = unfollowee_id
7
+ super(unfollower_id)
8
+ @unfollowee_id = unfollowee_id
7
9
  end
8
10
 
9
11
  def run
10
- action = "User #{link_to_profile unfollower_id} has unfollowed #{link_to_profile unfollowee_id}"
11
- timestamp = Time.now.to_i
12
- unfollower = User.find_by_user_key(unfollower_id)
13
- # Create the event
14
- event = unfollower.create_event(action, timestamp)
15
- # Log the event to the unfollower's stream
16
- unfollower.log_event(event)
17
- # Fan out the event to unfollowee
12
+ super
18
13
  unfollowee = User.find_by_user_key(unfollowee_id)
19
14
  unfollowee.log_event(event)
20
- # Fan out the event to all followers
21
- unfollower.followers.each do |user|
22
- user.log_event(event)
23
- end
15
+ end
16
+
17
+ def action
18
+ @action ||= "User #{link_to_profile unfollower_id} has unfollowed #{link_to_profile unfollowee_id}"
24
19
  end
25
20
  end
@@ -45,7 +45,17 @@ module Sufia
45
45
  return unless field.present?
46
46
  begin
47
47
  Date.parse(field).to_formatted_s(:standard)
48
- rescue
48
+ rescue ArgumentError
49
+ ActiveFedora::Base.logger.info "Unable to parse date: #{field.first.inspect} for #{self['id']}"
50
+ end
51
+ end
52
+
53
+ def create_date
54
+ field = self[CatalogController.uploaded_field]
55
+ return unless field.present?
56
+ begin
57
+ Date.parse(field).to_formatted_s(:standard)
58
+ rescue ArgumentError
49
59
  ActiveFedora::Base.logger.info "Unable to parse date: #{field.first.inspect} for #{self['id']}"
50
60
  end
51
61
  end
@@ -0,0 +1,108 @@
1
+ # A class that retrieves system level statistics about the system
2
+ #
3
+ # @attr_reader int limit limits the results returned from top_depositors and top_formats
4
+ # Default is 5, maximum is 20, minimum is 5
5
+ # @attr_reader int start_date Filters the statistics returned by the class to after the start date
6
+ # blank means no filter
7
+ # @attr_reader int end_date Filters the statistics returned by the class to before end date (end of day)
8
+ # blank means today
9
+ class SystemStats
10
+ attr_reader :limit, :start_date, :end_date
11
+
12
+ # initialize the stats class setting the limitations
13
+ #
14
+ # @attr_reader int limit limits the results returned from top_depositors and top_formats
15
+ # Default is 5, maximum is 20, minimum is 5
16
+ # @attr_reader int start_date Filters the statistics returned by the class to after the start date
17
+ # blank means no filter
18
+ # @attr_reader int end_date Filters the statistics returned by the class to before end date (end of day)
19
+ # blank means today
20
+ def initialize(limit_records = 5, start_date_str = nil, end_date_str = nil)
21
+ @limit = validate_limit(limit_records)
22
+ @start_date = DateTime.parse(start_date_str) unless start_date_str.blank?
23
+ @end_date = DateTime.parse(end_date_str).end_of_day unless end_date_str.blank?
24
+ end
25
+
26
+ # returns the total files in the system filtered by the start_date and end_date if present
27
+ #
28
+ # @return [Hash] A hash with the total files by permission for the system
29
+ # @option [Number] :total Total number of files without regard to permissions
30
+ # @option [Number] :public Total number of files that have public permissions
31
+ # @option [Number] :registered Total number of files that have registered (logged in) permissions
32
+ # @option [Number] :private Total number of files that have private permissions
33
+ def document_by_permission
34
+ return document_by_date_by_permission if start_date
35
+
36
+ files_count = {}
37
+ files_count[:total] = ::GenericFile.count
38
+ files_count[:public] = ::GenericFile.where_public.count
39
+ files_count[:registered] = ::GenericFile.where_registered.count
40
+ files_count[:private] = files_count[:total] - (files_count[:registered] + files_count[:public])
41
+ files_count
42
+ end
43
+
44
+ # returns a list (of size limit) of system users (depositors) that have the most deposits in the system
45
+ # @return [Hash] a hash with the user name as the key and the number of deposits as the value
46
+ # { 'cam156' => 25, 'hjc14' => 24 ... }
47
+ def top_depositors
48
+ depositor_key = Solrizer.solr_name('depositor', :stored_searchable, type: :string)
49
+ top_data(depositor_key, limit)
50
+ end
51
+
52
+ # returns a list (of size limit) of file formats (mime_types) that have the most files in the system
53
+ # @return [Hash] a hash with the file format as the key and the number of files as the value
54
+ # { 'png' => 25, 'pdf' => 24 ... }
55
+ def top_formats
56
+ format_key = Solrizer.solr_name('file_format', Solrizer::Descriptor.new(:string, :indexed, :multivalued))
57
+ top_data(format_key, limit)
58
+ end
59
+
60
+ # returns [Array<user>] a list (of size limit) of users most recently registered with the system
61
+ #
62
+ def recent_users
63
+ # no dates return the top few based on limit
64
+ return ::User.order('created_at DESC').limit(limit) if start_date.blank?
65
+
66
+ ::User.recent_users start_date, end_date
67
+ end
68
+
69
+ # returns [Number] the number of currently registered users
70
+ #
71
+ def users_count
72
+ ::User.count
73
+ end
74
+
75
+ private
76
+
77
+ def validate_limit(count)
78
+ if count.blank? || count < 5
79
+ 5
80
+ elsif count > 20
81
+ 20
82
+ else
83
+ count
84
+ end
85
+ end
86
+
87
+ def document_by_date_by_permission
88
+ files_count = {}
89
+ files_count[:total] = ::GenericFile.find_by_date_created(start_date, end_date).count
90
+ files_count[:public] = ::GenericFile.find_by_date_created(start_date, end_date).merge(::GenericFile.where_public).count
91
+ files_count[:registered] = ::GenericFile.find_by_date_created(start_date, end_date).merge(::GenericFile.where_registered).count
92
+ files_count[:private] = files_count[:total] - (files_count[:registered] + files_count[:public])
93
+ files_count
94
+ end
95
+
96
+ def top_data(key, limit)
97
+ query_url = "#{ActiveFedora.solr_config[:url]}/terms?terms.fl=#{key}&terms.sort=count&terms.limit=#{limit}&wt=json&omitHeader=true"
98
+ # Parse JSON response (looks like {"terms":{"depositor_tesim":["mjg36",3]}} for depositor)
99
+ json = open(query_url).read
100
+ begin
101
+ tuples = JSON.parse(json)['terms'][key]
102
+ rescue
103
+ []
104
+ end
105
+ # Change to hash where keys = logins and values = counts
106
+ Hash[*tuples]
107
+ end
108
+ end
@@ -0,0 +1,49 @@
1
+ module Sufia
2
+ class AdminStatsPresenter
3
+ attr_accessor :depositors, :deposit_stats
4
+ attr_reader :limit, :start_date, :end_date, :stats_filters
5
+
6
+ def initialize(stats_filters, limit)
7
+ @stats_filters = stats_filters
8
+ @start_date = stats_filters[:start_date]
9
+ @end_date = stats_filters[:end_date]
10
+ @limit = limit
11
+ end
12
+
13
+ def recent_users
14
+ @recent_users ||= stats.recent_users
15
+ end
16
+
17
+ def active_users
18
+ @active_users ||= stats.top_depositors
19
+ end
20
+
21
+ def top_formats
22
+ @top_formats ||= stats.top_formats
23
+ end
24
+
25
+ def files_count
26
+ @files_count ||= stats.document_by_permission
27
+ end
28
+
29
+ def users_count
30
+ @users_count ||= stats.users_count
31
+ end
32
+
33
+ def date_filter_string
34
+ if start_date.blank?
35
+ "unfiltered"
36
+ elsif end_date.blank?
37
+ "#{start_date} to #{Date.current}"
38
+ else
39
+ "#{start_date} to #{end_date}"
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def stats
46
+ @stats ||= ::SystemStats.new(limit, start_date, end_date)
47
+ end
48
+ end
49
+ end
@@ -3,7 +3,7 @@
3
3
  <div class="col-xs-12 col-sm-5 col-md-6">
4
4
  <nav class="navbar navbar-default" role="navigation">
5
5
  <ul class="nav navbar-nav">
6
- <li <%= 'class=active' if current_page?(root_path) %>><a href="<%= root_path %>">Home</a></li>
6
+ <li <%= 'class=active' if current_page?(sufia.root_path) %>><a href="<%= sufia.root_path %>">Home</a></li>
7
7
  <li <%= 'class=active' if current_page?(sufia.about_path) %>><a href="<%= sufia.about_path %>">About</a></li>
8
8
  <li <%= 'class=active' if action_name == "help" %>><a href="<%= sufia.static_path('help') %>">Help</a></li>
9
9
  <li <%= 'class=active' if current_page?(sufia.contact_path) %>><a href="<%= sufia.contact_path %>">Contact</a></li>
@@ -1,7 +1,7 @@
1
1
  <div id="footer" class="container-fluid">
2
2
  <div class="row">
3
3
  <div class="col-xs-12 col-sm-5">
4
- <p class="pull-left">A service of Project Hydra.</p>
4
+ <p class="pull-left">A service of <a href="http://projecthydra.org/" target="_blank">Project Hydra</a>.</p>
5
5
  <p>v.<%= Sufia::VERSION %></p>
6
6
  </div>
7
7
  <div class="col-xs-12 col-sm-7">