diff --git a/app/controllers/api/v2/plans_controller.rb b/app/controllers/api/v2/plans_controller.rb index 7714e9c892..df89d72124 100644 --- a/app/controllers/api/v2/plans_controller.rb +++ b/app/controllers/api/v2/plans_controller.rb @@ -4,12 +4,13 @@ module Api module V2 class PlansController < BaseApiController # rubocop:todo Style/Documentation respond_to :json + before_action :set_complete_param, only: %i[show index] # GET /api/v2/plans/:id def show raise Pundit::NotAuthorizedError unless @scopes.include?('read') - @plan = Plan.find_by(id: params[:id]) + @plan = Plan.includes(roles: :user).find_by(id: params[:id]) raise Pundit::NotAuthorizedError unless @plan.present? @@ -28,6 +29,10 @@ def index @items = paginate_response(results: @plans) render '/api/v2/plans/index', status: :ok end + + def set_complete_param + @complete = params[:complete].to_s.downcase == 'true' + end end end end diff --git a/app/policies/api/v2/plans_policy.rb b/app/policies/api/v2/plans_policy.rb index 98a22a5025..26c5d1346c 100644 --- a/app/policies/api/v2/plans_policy.rb +++ b/app/policies/api/v2/plans_policy.rb @@ -21,7 +21,17 @@ def initialize(resource_owner) # rubocop:todo Lint/MissingSuper end def resolve - Plan.joins(:roles) + Plan.joins( + :roles + ).includes( + :identifiers, + :research_outputs, + :template, + funder: :identifiers, + contributors: [:identifiers, { org: :identifiers }], + org: %i[region identifiers], + roles: [user: [:identifiers, { org: :identifiers }]] + ) .where(roles: { user_id: @resource_owner.id, active: true }) .distinct end diff --git a/app/policies/api/v2/templates_policy.rb b/app/policies/api/v2/templates_policy.rb index dacdfe48a2..6f3576e7e6 100644 --- a/app/policies/api/v2/templates_policy.rb +++ b/app/policies/api/v2/templates_policy.rb @@ -9,23 +9,22 @@ def initialize(resource_owner) # rubocop:todo Lint/MissingSuper end def resolve - # create the sql where clause - where_clause = <<-SQL - (visibility = 0 AND org_id = ?) OR - (visibility = 1 AND customization_of IS NULL) - SQL - # get the templates Template .includes(org: :identifiers) .joins(:org) .published - .where( - where_clause, - @resource_owner.org&.id - ) + .merge(accessible_templates) .order(:title) end + + private + + def accessible_templates + org_templates = Template.organisationally_visible.where(org_id: @resource_owner.org&.id) + public_templates = Template.publicly_visible.where(customization_of: nil) + org_templates.or(public_templates) + end end end end diff --git a/app/presenters/api/v2/plan_presenter.rb b/app/presenters/api/v2/plan_presenter.rb index 204cd68011..ec494558f7 100644 --- a/app/presenters/api/v2/plan_presenter.rb +++ b/app/presenters/api/v2/plan_presenter.rb @@ -4,9 +4,9 @@ module Api module V2 # Helper class for the API V2 project / DMP class PlanPresenter - attr_reader :data_contact, :contributors, :costs + attr_reader :data_contact, :contributors, :costs, :complete_plan_data - def initialize(plan:) + def initialize(plan:, complete: false) @contributors = [] return unless plan.present? @@ -22,6 +22,8 @@ def initialize(plan:) end @costs = plan_costs(plan: @plan) + + @complete_plan_data = fetch_all_q_and_a if complete end # Extract the ARK or DOI for the DMP OR use its URL if none exists @@ -44,9 +46,10 @@ def plan_costs(plan:) # TODO: define a new 'Currency' question type that includes a float field # any currency type selector (e.g GBP or USD) - answers = plan.answers.includes(question: :themes).select do |answer| - answer.question.themes.include?(theme) - end + answers = plan.answers + .joins(question: :themes) + .where(themes: { id: theme.id }) + .includes(:question) answers.map do |answer| # TODO: Investigate whether question level guidance should be the description @@ -54,6 +57,23 @@ def plan_costs(plan:) currency_code: 'usd', value: answer.text } end end + + # Fetch all questions and answers from a plan, regardless of theme + def fetch_all_q_and_a + answers = @plan.answers.includes(:question) + return [] unless answers.present? + + answers.filter_map do |answer| + q = answer.question + next unless q.present? + + { + title: "Question #{q.number || q.id}", + question: q.text.to_s, + answer: answer.text.to_s + } + end + end end end end diff --git a/app/presenters/api/v2/research_output_presenter.rb b/app/presenters/api/v2/research_output_presenter.rb index 0f6cb27ec9..33308bd9ae 100644 --- a/app/presenters/api/v2/research_output_presenter.rb +++ b/app/presenters/api/v2/research_output_presenter.rb @@ -55,25 +55,37 @@ def fetch_q_and_a_as_single_statement(themes:) fetch_q_and_a(themes: themes).collect { |item| item[:description] }.join('
') end - def fetch_q_and_a(themes:) # rubocop:disable Metrics/CyclomaticComplexity + def fetch_q_and_a(themes:) return [] unless themes.is_a?(Array) && themes.any? - themes.filter_map do |theme| - qs = questions_for_theme(theme) - descr = qs.filter_map do |q| - a = @plan.answers.find { |ans| ans.question_id == q.id } - next unless a.present? + answers = answers_for_themes(themes) - format_q_and_a(q, a) - end - next if descr.blank? + descs_by_theme = build_descriptions_by_theme_hash(answers, themes) - { title: theme, description: descr } + descs_by_theme.map do |theme, descs| + { title: theme, description: descs } end end - def questions_for_theme(theme) - @plan.questions.select { |q| q.themes.collect(&:title).include?(theme) } + def answers_for_themes(themes) + @plan.answers + .joins(question: :themes) + .where(themes: { title: themes }) + .includes(question: :themes) + .distinct + end + + def build_descriptions_by_theme_hash(answers, themes) + descs_by_theme = Hash.new { |h, k| h[k] = [] } + + answers.each do |answer| + answer.question.themes.each do |theme| + next unless themes.include?(theme.title) + + descs_by_theme[theme.title] << format_q_and_a(answer.question, answer) + end + end + descs_by_theme end def format_q_and_a(question, answer) diff --git a/app/views/api/v2/plans/_show.json.jbuilder b/app/views/api/v2/plans/_show.json.jbuilder index 0de52775a3..3cd5dfe8c3 100644 --- a/app/views/api/v2/plans/_show.json.jbuilder +++ b/app/views/api/v2/plans/_show.json.jbuilder @@ -4,7 +4,7 @@ json.schema 'https://github.com/RDA-DMP-Common/RDA-DMP-Common-Standard/tree/master/examples/JSON/JSON-schema/1.0' -presenter = Api::V2::PlanPresenter.new(plan: plan) +presenter = Api::V2::PlanPresenter.new(plan: plan, complete: @complete) # Note the symbol of the dmproadmap json object # nested in extensions which is the container for the json template object, etc. @@ -68,5 +68,18 @@ unless @minimal json.title template.title end end + + if @complete + json.complete_plan do + q_and_a = presenter.complete_plan_data + next if q_and_a.blank? + + json.array! q_and_a do |item| + json.title item[:title] + json.question item[:question] + json.answer item[:answer] + end + end + end end end