diff --git a/app/controllers/plans_controller.rb b/app/controllers/plans_controller.rb index 06373d9..bd0559b 100644 --- a/app/controllers/plans_controller.rb +++ b/app/controllers/plans_controller.rb @@ -7,6 +7,10 @@ def index authorize Plan @plans = current_user.active_plans + @organisationally_or_publicly_visible_by_org = + current_user.org_id.present? ? + Plan.includes(:roles).organisationally_or_publicly_visible_by_org(current_user.org_id).to_a.select{ |p| !p.any_role?(current_user) } : + [] end # GET /plans/new diff --git a/app/controllers/public_pages_controller.rb b/app/controllers/public_pages_controller.rb index de6a531..bf33360 100644 --- a/app/controllers/public_pages_controller.rb +++ b/app/controllers/public_pages_controller.rb @@ -52,7 +52,7 @@ def plan_export @plan = Plan.find(params[:id]) # covers authorization for this action. Pundit dosent support passing objects into scoped policies - raise Pundit::NotAuthorizedError unless PublicPagePolicy.new( @plan).plan_export? + raise Pundit::NotAuthorizedError unless PublicPagePolicy.new(@plan, current_user).plan_organisationally_exportable? || PublicPagePolicy.new(@plan).plan_export? skip_authorization # This creates exported_plans with no user. # Note for reviewers, The ExportedPlan model actually serves no purpose, except diff --git a/app/models/plan.rb b/app/models/plan.rb index 84f2a76..989d63c 100644 --- a/app/models/plan.rb +++ b/app/models/plan.rb @@ -55,6 +55,11 @@ # Note that in ActiveRecord::Enum the mappings are exposed through a class method with the pluralized attribute name (e.g visibilities rather than visibility) scope :publicly_visible, -> { where(:visibility => visibilities[:publicly_visible]).order(:title => :asc) } + # Retrieves any plan organisationally or publicly visible for a given org id + scope :organisationally_or_publicly_visible_by_org, -> (org_id) { + joins(:template).where( + visibility: [visibilities[:organisationally_visible], visibilities[:publicly_visible]], + "templates.org_id": org_id).order(:title => :asc) } ## # Settings for the template has_settings :export, class_name: 'Settings::Template' do |s| @@ -305,6 +310,16 @@ end ## + # determines whether or not the specified user has any rol on the plan + # + # @param user_id [Integer] the id for the user + # @return [Boolean] true if the user has any rol + def any_role?(user) + user_id = user.id if user.is_a?(User) + !self.roles.index{ |rol| rol.user_id == user_id }.nil? + end + + ## # defines and returns the status of the plan # status consists of a hash of the num_questions, num_answers, sections, questions, and spaced used. # For each section, it contains the id's of each of the questions diff --git a/app/policies/public_page_policy.rb b/app/policies/public_page_policy.rb index e4b237b..4721351 100644 --- a/app/policies/public_page_policy.rb +++ b/app/policies/public_page_policy.rb @@ -1,8 +1,8 @@ class PublicPagePolicy < ApplicationPolicy - def initialize( object) - # no requirement for users to be signed in here + def initialize(object, object2 = nil) @object = object + @object2 = object2 end def plan_index? @@ -21,4 +21,12 @@ @object.publicly_visible? end + def plan_organisationally_exportable? + plan = @object + user = @object2 + if plan.is_a?(Plan) && user.is_a?(User) + return plan.publicly_visible? || (plan.organisationally_visible? && plan.template.org_id == user.org_id) + end + return false; + end end diff --git a/app/views/plans/_publicly_visible_org.html.erb b/app/views/plans/_publicly_visible_org.html.erb new file mode 100644 index 0000000..6d83d5d --- /dev/null +++ b/app/views/plans/_publicly_visible_org.html.erb @@ -0,0 +1,46 @@ +<% 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' %> +
+
+
+
+<% end %> + diff --git a/app/views/plans/index.html.erb b/app/views/plans/index.html.erb index ed5a0bc..e8bbe37 100644 --- a/app/views/plans/index.html.erb +++ b/app/views/plans/index.html.erb @@ -26,7 +26,7 @@ <%= render(partial: "shared/table_filter", - locals: {path: plans_path, placeholder: _('Filter plans')}) %> + locals: { placeholder: _('Filter plans')}) %> <% end %> @@ -102,10 +102,12 @@ <% end %> - -
- <%= link_to _('Create plan'), new_plan_path, class: "btn btn-primary" %> -
+ <%= link_to _('Create plan'), new_plan_path, class: "btn btn-primary" %> + + +
+
+ <%= render partial: 'publicly_visible_org', locals: { plans: @organisationally_or_publicly_visible_by_org } %>
diff --git a/app/views/shared/_table_filter.html.erb b/app/views/shared/_table_filter.html.erb index 692d1fb..19cf031 100644 --- a/app/views/shared/_table_filter.html.erb +++ b/app/views/shared/_table_filter.html.erb @@ -1,12 +1,18 @@
-
- <%= label_tag :filter, _('Filter plans'), class: "sr-only" %> - <%= search_field_tag(:filter, params[:filter], placeholder: placeholder, class: "form-control") %> - - - + +
diff --git a/lib/assets/javascripts/utils/tableHelper.js b/lib/assets/javascripts/utils/tableHelper.js index a1edb8e..47303b4 100644 --- a/lib/assets/javascripts/utils/tableHelper.js +++ b/lib/assets/javascripts/utils/tableHelper.js @@ -29,10 +29,11 @@ }); }); - const clear = ((el) => { - $(el).val(''); - $(el).closest('table').find('tbody tr').show(); - }); + const clear = (el) => { + const formEl = $(el).closest('form'); + formEl.find(options.selector).val(''); + formEl.closest('table').find('tbody tr').show(); + }; /* initialize a debounced listener for the filter box */ const debounced = debounce(filter); @@ -42,16 +43,10 @@ debounced(e.currentTarget); }); - $(options.selector).siblings('#clear_filter').click((e) => { + $('.clear_filter').click((e) => { e.preventDefault(); - clear(e.currentTarget); + clear(e.target); debounced.cancel(); }); } }; - -// Attach the tablesorter and filter to all tables with those selectors -$(() => { - collateTable({ selector: 'table.tablesorter' }); - filteriseTable({ selector: '#filter' }); -}); diff --git a/lib/assets/javascripts/views/plans/index.js b/lib/assets/javascripts/views/plans/index.js index 8ce3de7..0f37393 100644 --- a/lib/assets/javascripts/views/plans/index.js +++ b/lib/assets/javascripts/views/plans/index.js @@ -1,4 +1,5 @@ import * as notifier from '../../utils/notificationHelper'; +import { collateTable, filteriseTable } from '../../utils/tableHelper'; import { PLAN_VISIBILITY_WHEN_TEST, PLAN_VISIBILITY_WHEN_NOT_TEST, @@ -36,3 +37,9 @@ checkboxHandler(e.currentTarget); }); }); + +// Attach the tablesorter and filter to all tables with those selectors +$(() => { + collateTable({ selector: 'table.tablesorter' }); + filteriseTable({ selector: '.filter' }); +});