sufia 6.3.0 → 6.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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">