diff --git a/app/controllers/concerns/paginable.rb b/app/controllers/concerns/paginable.rb new file mode 100644 index 0000000..b915e81 --- /dev/null +++ b/app/controllers/concerns/paginable.rb @@ -0,0 +1,24 @@ +module Paginable + extend ActiveSupport::Concern + + #included do + # helper :paginable_renderise + #end + + # Renders paginable layout with the partial view passed + # partial {String} - Represents a path to where the partial view is stored + # controller {String} - Represents the name of the controller to handles the pagination + # action {String} - Represents the method name within the controller + # scope {ActiveRecord::Relation} - Represents scope variable + # locals {Hash} - A hash objects with any additional local variables to be passed to the partial view + def paginable_renderise(partial: nil, controller: params[:controller], action: params[:action], scope: nil, locals: {}) + raise ArgumentError, 'scope should be an instance of ActiveRecord::Relation class' unless scope.is_a?(ActiveRecord::Relation) + raise ArgumentError, 'locals should be an instance of Hash' unless locals.is_a?(Hash) + render(layout: '/layouts/paginable', partial: partial, locals: { + controller: controller, + action: action, + # The scope is paginable if it has been chained with page method from kaminari which contains methods such as total_pages + paginable: scope.respond_to?(:total_pages), + scope: scope }.merge(locals)) + end +end \ No newline at end of file diff --git a/app/controllers/paginable/plans_controller.rb b/app/controllers/paginable/plans_controller.rb new file mode 100644 index 0000000..e9e4311 --- /dev/null +++ b/app/controllers/paginable/plans_controller.rb @@ -0,0 +1,23 @@ +class Paginable::PlansController < ApplicationController + include Paginable + # /paginable/plans/privately_visible/:page + def privately_visible + raise Pundit::NotAuthorizedError unless Paginable::PlanPolicy.new(current_user).privately_visible? + if params[:page] == 'ALL' + plans = current_user.active_plans + else + plans = current_user.active_plans.page(params[:page]) + end + paginable_renderise(partial: 'privately_visible', scope: plans) + end + # GET /paginable/plans/organisationally_or_publicly_visible/:page + def organisationally_or_publicly_visible + raise Pundit::NotAuthorizedError unless Paginable::PlanPolicy.new(current_user).organisationally_or_publicly_visible? + if params[:page] == 'ALL' + plans = Plan.organisationally_or_publicly_visible(current_user) + else + plans = Plan.organisationally_or_publicly_visible(current_user).page(params[:page]) + end + paginable_renderise(partial: 'organisationally_or_publicly_visible', scope: plans) + end +end \ No newline at end of file diff --git a/app/controllers/paginable/users_controller.rb b/app/controllers/paginable/users_controller.rb new file mode 100644 index 0000000..7637573 --- /dev/null +++ b/app/controllers/paginable/users_controller.rb @@ -0,0 +1,11 @@ +class Paginable::UsersController < ApplicationController + include Paginable + # /paginable/users/index/:page + def index + authorize User + users = params[:page] == 'ALL' ? + current_user.org.users.includes(:roles) : + current_user.org.users.includes(:roles).page(params[:page]) + paginable_renderise(partial: 'index', scope: users) + end +end \ No newline at end of file diff --git a/app/controllers/plans_controller.rb b/app/controllers/plans_controller.rb index a3d74e0..a38cd5d 100644 --- a/app/controllers/plans_controller.rb +++ b/app/controllers/plans_controller.rb @@ -1,17 +1,13 @@ class PlansController < ApplicationController require 'pp' + helper PaginableHelper helper SettingsTemplateHelper - after_action :verify_authorized def index authorize Plan - @plans = current_user.active_plans - # Exclude any plans where the user is a reviewer. They access those plans via Admin -> Plans menu - @plans.delete_if{ |p| p.reviewable_by?(current_user) } - @paginable = params[:page] - @organisationally_or_publicly_visible = @paginable.nil? ? Plan.organisationally_or_publicly_visible(current_user) : - Plan.organisationally_or_publicly_visible(current_user).page(params[:page]) + @plans = current_user.active_plans.page(1) + @organisationally_or_publicly_visible = Plan.organisationally_or_publicly_visible(current_user).page(1) end # GET /plans/new diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 6c5f88f..9b1e2f1 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,4 +1,5 @@ class UsersController < ApplicationController + helper PaginableHelper after_action :verify_authorized respond_to :html @@ -7,10 +8,7 @@ # Displays number of roles[was project_group], name, email, and last sign in def admin_index authorize User - # Sets the user to the currently logged in user if it is undefined -# @user = current_user if @user.nil? -# @users = @user.org.users.includes(:roles) - @users = current_user.org.users.includes(:roles) + @users = current_user.org.users.includes(:roles).page(1) end ## diff --git a/app/helpers/paginable_helper.rb b/app/helpers/paginable_helper.rb new file mode 100644 index 0000000..cb653ea --- /dev/null +++ b/app/helpers/paginable_helper.rb @@ -0,0 +1,3 @@ +module PaginableHelper + include Paginable +end \ No newline at end of file diff --git a/app/models/user.rb b/app/models/user.rb index 4ff9d87..ea34e83 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -90,11 +90,7 @@ # # @return [Plans] def active_plans - plans = [] - self.roles.includes(:plan).where(active: true).each do |r| - plans << r.plan - end - return plans + self.plans.includes(:template).where("roles.active": true).where(Role.not_reviewer_condition) end diff --git a/app/policies/paginable/plan_policy.rb b/app/policies/paginable/plan_policy.rb new file mode 100644 index 0000000..01c259a --- /dev/null +++ b/app/policies/paginable/plan_policy.rb @@ -0,0 +1,14 @@ +module Paginable + class PlanPolicy < ApplicationPolicy + def initialize(user) + @user = user + end + def privately_visible? + @user.is_a?(User) + end + + def organisationally_or_publicly_visible? + @user.is_a?(User) + end + end +end \ No newline at end of file diff --git a/app/views/layouts/_paginable.html.erb b/app/views/layouts/_paginable.html.erb new file mode 100644 index 0000000..e899eb1 --- /dev/null +++ b/app/views/layouts/_paginable.html.erb @@ -0,0 +1,33 @@ +<% + # Custom layout to be included on any view that needs pagination + # locals: { controller, action, paginable, scope } +%> +
+
+
+ <%= yield %> +
+
+
+
+
+
+ <% total = paginable ? scope.total_count : scope.length %> + <% if total > Kaminari.config.default_per_page %> + <% if paginable %> + <%= link_to(_('View all'), url_for(controller: controller, action: action, page: 'ALL'), { 'data-remote': true }) %> + <% else %> + <%= link_to(_('View less'), url_for(controller: controller, action: action, page: 1), { 'data-remote': true }) %> + <% end %> + <% end %> +
+
+ <% if paginable %> + <%= paginate(scope, params: { controller: controller, action: action }, remote: true) %> + <% end %> +
+
+
+
+
+
\ No newline at end of file diff --git a/app/views/paginable/plans/_organisationally_or_publicly_visible.html.erb b/app/views/paginable/plans/_organisationally_or_publicly_visible.html.erb new file mode 100644 index 0000000..55f27ba --- /dev/null +++ b/app/views/paginable/plans/_organisationally_or_publicly_visible.html.erb @@ -0,0 +1,46 @@ +<% if current_user.org_id.present? %> +
+
+

<%= _('%{org_title} Plans') %{ :org_title => current_user.org.name } %>

+
+
+
+
+
+ + + <% if scope.length > TABLE_FILTER_MIN_ROWS %> + + + + <% end %> + + + + + + + + + + <% scope.each do |plan| %> + + + + + + + + <% end %> + +
+ <%= render(partial: "shared/table_filter", + locals: { placeholder: _('Filter plans')}) %> +
<%= _('Project Title') %><%= _('Template') %><%= _('Owner') %><%= _('Updated') %><%= _('Download') %>
<%= link_to "#{plan.title.length > 40 ? "#{plan.title[0..39]} ..." : plan.title}", plan_path(plan) %><%= plan.template.title %><%= plan.owner.present? ? plan.owner.name : _('Unknown') %><%= l(plan.latest_update.to_date, formats: :short) %> + <%= link_to _('PDF'), plan_export_path(plan, format: :pdf), target: '_blank' %> +
+
+
+
+<% end %> + diff --git a/app/views/paginable/plans/_privately_visible.html.erb b/app/views/paginable/plans/_privately_visible.html.erb new file mode 100644 index 0000000..6884e94 --- /dev/null +++ b/app/views/paginable/plans/_privately_visible.html.erb @@ -0,0 +1,81 @@ +
+ + + <% if scope.length > TABLE_FILTER_MIN_ROWS %> + + + + <% end %> + + + + + + + + + + + + <% scope.each do |plan| %> + + + + + + + + + + <% end %> + +
+ <%= render(partial: "shared/table_filter", locals: { placeholder: _('Filter plans')}) %> +
<%= _('Project Title') %><%= _('Template') %><%= _('Edited') %><%= _('Role') %><%= _('Test') %><%= _('Visibility') %> + <%= _('Actions') %> +
+ <%= link_to "#{plan.title.length > 60 ? "#{plan.title[0..59]} ..." : plan.title}", + plan_path(plan) %> + <%= plan.template.title %><%= l(plan.latest_update.to_date, formats: :short) %><%= display_role(plan.roles.find_by(user: current_user)) %> + <% if plan.administerable_by?(current_user.id) then %> + <%= form_for plan, url: set_test_plan_path(plan), html: {method: :post, id: 'update-test-plan'} do |f| %> + <%= check_box_tag(:is_test, "1", (plan.visibility === 'is_test')) %> + <% end %> + <% else %> + <%= plan.visibility === 'is_test' ? _('Yes') : _('No') %> + <% end %> + + <%= plan.visibility === 'is_test' ? _('N/A') : raw(display_visibility(plan.visibility)) %> + + +
+
\ No newline at end of file diff --git a/app/views/paginable/users/_index.html.erb b/app/views/paginable/users/_index.html.erb new file mode 100644 index 0000000..6b0caa3 --- /dev/null +++ b/app/views/paginable/users/_index.html.erb @@ -0,0 +1,67 @@ +
+
+

<%= _('List of users') %>

+

+ <%= _('Below is a list of users registered for your organisation. You can sort the data by each field.')%> +

+
+
+ +
+
+
+ + + <% if scope.count > TABLE_FILTER_MIN_ROWS %> + + + + <% end %> + + + + + + + + + + <% scope.each do |user| %> + <% if !user.nil? then%> + + + + + + + + <% end %> + <% end %> + +
+ <%= render(partial: "shared/table_filter", + locals: {path: admin_index_users_path, + placeholder: _('Filter users')}) %> +
<%= _('Name') %><%= _('Email address') %><%= _('Last logged in') %><%= _('How many plans?') %><%= _('Privileges') %>
+ <% if !user.name.nil? %> + <%= user.name(false) %> + <% end %> + + <%= user.email %> + + <% if !user.last_sign_in_at.nil? %> + <%= l user.last_sign_in_at.to_date, :formats => :short %> + <% end %> + + <% if !user.roles.nil? %> + <%= user.roles.length %> + <% end %> + + <% unless current_user == user %> + <% b_label = _('Edit')%> + <%= link_to b_label, admin_grant_permissions_user_path(user)%> + <% end %> +
+
+
+
\ No newline at end of file diff --git a/app/views/plans/_publicly_visible_org.html.erb b/app/views/plans/_publicly_visible_org.html.erb deleted file mode 100644 index 5104832..0000000 --- a/app/views/plans/_publicly_visible_org.html.erb +++ /dev/null @@ -1,63 +0,0 @@ -<% if current_user.org_id.present? %> -
-
-

<%= _('%{org_title} Plans') %{ :org_title => current_user.org.name } %>

-
-
-
-
-
- - - <% if plans.length > TABLE_FILTER_MIN_ROWS %> - - - - <% end %> - - - - - - - - - - <% plans.each do |plan| %> - - - - - - - - <% end %> - -
- <%= render(partial: "shared/table_filter", - locals: { placeholder: _('Filter plans')}) %> -
<%= _('Project Title') %><%= _('Template') %><%= _('Owner') %><%= _('Updated') %><%= _('Download') %>
<%= link_to "#{plan.title.length > 40 ? "#{plan.title[0..39]} ..." : plan.title}", plan_path(plan) %><%= plan.template.title %><%= plan.owner.present? ? plan.owner.name : _('Unknown') %><%= l(plan.latest_update.to_date, formats: :short) %> - <%= link_to _('PDF'), plan_export_path(plan, format: :pdf), target: '_blank' %> -
-
-
-
-
-
-
- <% if paginable %> - <%= link_to(_('View all'), plans_path) %> - <% else %> - <%= link_to(_('View less'), plans_path(page: 1)) %> - <% end %> -
-
- <% if paginable %> - <%= paginate(plans) %> - <% end %> -
-
-
-
-<% end %> - diff --git a/app/views/plans/index.html.erb b/app/views/plans/index.html.erb index a1294a7..bd64ba1 100644 --- a/app/views/plans/index.html.erb +++ b/app/views/plans/index.html.erb @@ -1,7 +1,6 @@

<%= _('My Dashboard') %>

-

<% if @plans.count > 0 %> @@ -15,100 +14,28 @@

-
- <% if @plans.length > 0 %> -
- - - <% if @plans.length > TABLE_FILTER_MIN_ROWS %> - - - - <% end %> - - - - - - - - - - - - <% @plans.each do |plan| %> - - - - - - - - - - <% end %> - -
- <%= render(partial: "shared/table_filter", - locals: { placeholder: _('Filter plans')}) %> -
<%= _('Project Title') %><%= _('Template') %><%= _('Edited') %><%= _('Role') %><%= _('Test') %><%= _('Visibility') %> - <%= _('Actions') %> -
- <%= link_to "#{plan.title.length > 60 ? "#{plan.title[0..59]} ..." : plan.title}", - plan_path(plan) %> - <%= plan.template.title %><%= l(plan.latest_update.to_date, formats: :short) %><%= display_role(plan.roles.find_by(user: current_user)) %> - <% if plan.administerable_by?(current_user.id) then %> - <%= form_for plan, url: set_test_plan_path(plan), html: {method: :post, id: 'update-test-plan'} do |f| %> - <%= check_box_tag(:is_test, "1", (plan.visibility === 'is_test')) %> - <% end %> - <% else %> - <%= plan.visibility === 'is_test' ? _('Yes') : _('No') %> - <% end %> - - <%= plan.visibility === 'is_test' ? _('N/A') : raw(display_visibility(plan.visibility)) %> - - -
-
- <% end %> + <%= paginable_renderise( + partial: '/paginable/plans/privately_visible', + controller: 'paginable/plans', + action: 'privately_visible', + scope: @plans) %> +
+
+
+
<%= link_to _('Create plan'), new_plan_path, class: "btn btn-primary" %>
<% if @organisationally_or_publicly_visible.length > 0 %> - <%= render partial: 'publicly_visible_org', locals: { plans: @organisationally_or_publicly_visible, paginable: @paginable } %> + <%= paginable_renderise( + partial: '/paginable/plans/organisationally_or_publicly_visible', + controller: 'paginable/plans', + action: 'organisationally_or_publicly_visible', + scope: @organisationally_or_publicly_visible) %> <% end %>
diff --git a/app/views/users/admin_index.html.erb b/app/views/users/admin_index.html.erb index 177f8d8..21ca2da 100644 --- a/app/views/users/admin_index.html.erb +++ b/app/views/users/admin_index.html.erb @@ -1,67 +1,5 @@ -
-
-

<%= _('List of users') %>

-

- <%= _('Below is a list of users registered for your organisation. You can sort the data by each field.')%> -

-
-
- -
-
-
- - - <% if @users.count > TABLE_FILTER_MIN_ROWS %> - - - - <% end %> - - - - - - - - - - <% @users.each do |user| %> - <% if !user.nil? then%> - - - - - - - - <% end %> - <% end %> - -
- <%= render(partial: "shared/table_filter", - locals: {path: admin_index_users_path, - placeholder: _('Filter users')}) %> -
<%= _('Name') %><%= _('Email address') %><%= _('Last logged in') %><%= _('How many plans?') %><%= _('Privileges') %>
- <% if !user.name.nil? then%> - <%= user.name(false) %> - <% end %> - - <%= user.email %> - - <% if !user.last_sign_in_at.nil? then%> - <%= l user.last_sign_in_at.to_date, :formats => :short %> - <% end %> - - <% if !user.roles.nil? then%> - <%= user.roles.length %> - <% end %> - - <% unless current_user == user %> - <% b_label = _('Edit')%> - <%= link_to b_label, admin_grant_permissions_user_path(user)%> - <% end %> -
-
-
-
\ No newline at end of file +<%= paginable_renderise( + partial: '/paginable/users/index', + controller: 'paginable/users', + action: 'index', + scope: @users) %> \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 155ae89..9324e8b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,4 @@ Rails.application.routes.draw do - namespace :admin do resources :users, only: [:new, :create, :edit, :update, :index, :show] resources :orgs, only: [:new, :create, :edit, :update, :index, :show] @@ -256,4 +255,16 @@ end end end + + namespace :paginable do + # Paginable actions for plans + resources :plans, only: [] do + get 'privately_visible/:page', action: :privately_visible, on: :collection, as: :privately_visible + get 'organisationally_or_publicly_visible/:page', action: :organisationally_or_publicly_visible, on: :collection, as: :organisationally_or_publicly_visible + end + # Paginable actions for users + resources :users, only: [] do + get 'index/:page', action: :index, on: :collection, as: :index + end + end end diff --git a/lib/assets/javascripts/application.js b/lib/assets/javascripts/application.js index 673b5fe..d5868f6 100644 --- a/lib/assets/javascripts/application.js +++ b/lib/assets/javascripts/application.js @@ -1,4 +1,5 @@ // Generic JS that is applicable across multiple pages +import './utils/paginable'; import './utils/linkHelper'; import './utils/tabHelper'; import './utils/tableHelper'; diff --git a/lib/assets/javascripts/utils/paginable.js b/lib/assets/javascripts/utils/paginable.js new file mode 100644 index 0000000..9abef4a --- /dev/null +++ b/lib/assets/javascripts/utils/paginable.js @@ -0,0 +1,7 @@ +$(() => { + // Event delegation for paginable class so that any children a[data-remote="true"] + // now or in future will be handled through this eventListener + $('.paginable').on('ajax:success', 'a[data-remote="true"]', (e, data) => { + $(e.target).closest('.paginable').html(data); + }); +}); diff --git a/lib/assets/webpack.config.js b/lib/assets/webpack.config.js index 9113b1c..2a9e7bc 100644 --- a/lib/assets/webpack.config.js +++ b/lib/assets/webpack.config.js @@ -15,7 +15,7 @@ context: __dirname, entry: { - vendor: ['jquery', 'timeago.js', 'jquery-accessible-autocomplete-list-aria/jquery-accessible-autocomplete-list-aria'], + vendor: ['jquery', 'timeago.js', 'jquery-accessible-autocomplete-list-aria/jquery-accessible-autocomplete-list-aria', 'jquery-ujs'], application: ['./javascripts/application.js', './stylesheets/application.scss'], }, diff --git a/test/functional/paginable/plans_controller_test.rb b/test/functional/paginable/plans_controller_test.rb new file mode 100644 index 0000000..e811322 --- /dev/null +++ b/test/functional/paginable/plans_controller_test.rb @@ -0,0 +1,37 @@ +require 'test_helper' + +class PlansControllerTest < ActionDispatch::IntegrationTest + include Devise::Test::IntegrationHelpers + setup do + @user = User.find_by(email: 'super_admin@example.com') + #@plans_total = Kaminari.config.default_per_page + #(1..@plans_total+1).each do + # Plan.create(title: 'Test Plan', template: @user.org.templates.first, grant_number: 'Plan12345', + # identifier: '000912', description: 'This is a test plan', + # principal_investigator: 'Foo Bar', principal_investigator_identifier: 'ABC', + # data_contact: 'foo.bar@example.com', visibility: :privately_visible).assign_creator(@user.id) + #end + end + test 'privately_visible action renders layout view for page param ALL' do + sign_in @user + get privately_visible_paginable_plans_path('ALL') + assert_response :success + # Checks the existence of a link with href equals to privately_visible_paginable_plans_path(1) + # assert_select('.paginable-layout .pull-left a[href=?]', privately_visible_paginable_plans_path(1)) + # Checks the existence of a link (e.g. View Less) with data-remote attribute as true (for AJAX requests) + # assert_select('.paginable-layout .pull-left a[data-remote=?]', 'true') + # Checks that does not exist any nav with class pagination in the view rendered (e.g. no pagination) + # assert_select('nav.pagination', { count: 0 }) + end + test 'privately_visible action renders layout view for page param 1' do + sign_in @user + get privately_visible_paginable_plans_path(1) + assert_response :success + #assert_select('.paginable-layout .pull-left a[href=?]', privately_visible_paginable_plans_path('ALL')) + #assert_select('.paginable-layout .pull-left a[data-remote=?]', 'true') + #assert_select('nav.pagination', { count: 1 }) + #assert_select('nav.pagination .page.current', { count: 1, text: '1' }) + end +end + +# assert_select reference at http://www.rubydoc.info/github/rails/rails-dom-testing/Rails/Dom/Testing/Assertions/SelectorAssertions \ No newline at end of file