diff --git a/app/controllers/answers_controller.rb b/app/controllers/answers_controller.rb index 026242b..8ef2500 100644 --- a/app/controllers/answers_controller.rb +++ b/app/controllers/answers_controller.rb @@ -6,8 +6,6 @@ # PUT/PATCH /[:locale]/answer/[:id] def update # create a new answer based off the passed params - logger.debug("RAY: update params=") - logger.debug params.inspect ans_params = params[:answer] plan_id = ans_params[:plan_id] @@ -17,10 +15,8 @@ plan_id: plan_id, user_id: user_id, question_id: question_id) - logger.debug "RAY: found answer=#{@answer.inspect}" if @answer.nil? @answer = Answer.new(params[:answer]) - logger.debug "RAY: created answer=#{@answer.inspect}" end authorize @answer diff --git a/app/controllers/notes_controller.rb b/app/controllers/notes_controller.rb index d116da0..8a400f6 100644 --- a/app/controllers/notes_controller.rb +++ b/app/controllers/notes_controller.rb @@ -6,18 +6,30 @@ # POST /notes def create @note = Note.new - logger.debug "RAY: into save note" - @note.user_id = params[:new_note][:user_id] - @note.answer_id = params[:new_note][:answer_id] + user_id = params[:new_note][:user_id] + @note.user_id = user_id + + answer_id = params[:new_note][:answer_id] + question_id = params[:new_note][:question_id] + plan_id = params[:new_note][:plan_id] + if answer_id.present? + answer = Answer.find(@note.answer_id) + else + answer = Answer.new + answer.plan_id = plan_id + answer.question_id = question_id + answer.user_id = user_id + answer.save! + end + + @note.answer= answer @note.text = params["#{params[:new_note][:answer_id]}new_note_text"] authorize @note - answer = Answer.find(@note.answer_id) @plan = answer.plan @phase = answer.question.section.phase - logger.debug "RAY: saving " + @note.inspect if @note.save session[:question_id_notes] = answer.question_id redirect_to edit_plan_phase_path(@plan, @phase), status: :found, notice: I18n.t("helpers.comments.note_created") diff --git a/app/controllers/phases_controller.rb b/app/controllers/phases_controller.rb index 5c08f41..b65f33f 100644 --- a/app/controllers/phases_controller.rb +++ b/app/controllers/phases_controller.rb @@ -1,4 +1,5 @@ class PhasesController < ApplicationController + require 'pp' after_action :verify_authorized @@ -9,7 +10,7 @@ DROPDOWN = QuestionFormat.where(title: "Dropdown").first.id MULTI = QuestionFormat.where(title: "Multi select box").first.id - # GET /phases/1/edit + # GET /plans/PLANID/phases/PHASEID/edit def edit @textarea = TEXTAREA @@ -21,19 +22,12 @@ @plan = Plan.find(params[:plan_id]) authorize @plan - @phase = Phase.where(template_id: @plan.template_id, slug: params[:id]).first - @sections = @phase.sections - @section_answers = Hash.new - @phase.sections.each do |section| - nanswers = 0 - questions = section.questions - questions.each do |q| - answers = q.answers.where(plan_id: @plan) - nanswers += answers.count - end - @section_answers[section.id] = nanswers - end + @plan_data = @plan.to_hash + + phase_id = params[:id].to_i + @phase = Phase.find(phase_id) + @phase_data = @plan_data["template"]["phases"].select {|p| p["id"] == phase_id}.first if !user_signed_in? then respond_to do |format| @@ -42,6 +36,20 @@ end end + + + # GET /plans/PLANID/phases/PHASEID/status.json + def status + @plan = Plan.find(params[:plan_id]) + authorize @plan + if user_signed_in? && @plan.readable_by?(current_user.id) then + respond_to do |format| + format.json { render json: @plan.status } + end + else + render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false) + end + end end diff --git a/app/controllers/plans_controller.rb b/app/controllers/plans_controller.rb index 3e26fac..edae8f5 100644 --- a/app/controllers/plans_controller.rb +++ b/app/controllers/plans_controller.rb @@ -1,4 +1,5 @@ class PlansController < ApplicationController + require 'pp' #Uncomment the line below in order to add authentication to this page - users without permission will not be able to add new plans #load_and_authorize_resource # @@ -109,13 +110,15 @@ # GET /plans/show def show - @plan = Plan.includes(template: {phases: :sections} ).find(params[:id]) + @plan = Plan.find(params[:id]) authorize @plan + + @plan_data = @plan.to_hash + @editing = params[:editing] && @plan.administerable_by?(current_user.id) @selected_guidance_groups = [] - #@selected_guidance_groups = @plan.plan_guidance_groups.map{ |pgg| [pgg.guidance_group.name, pgg.guidance_group.id, :checked => pgg.selected] } - all_guidance_groups = @plan.plan_guidance_groups.includes(:guidance_group) - @selected_guidance_groups = all_guidance_groups.map{ |pgg| [pgg.guidance_group.name, pgg.guidance_group.id, :checked => pgg.selected] } + all_guidance_groups = @plan_data["plan_guidance_groups"] + @selected_guidance_groups = all_guidance_groups.map{ |pgg| [ pgg["guidance_group"]["name"], pgg["guidance_group"]["id"], :checked => pgg["selected"] ] } @selected_guidance_groups.sort! if user_signed_in? && @plan.readable_by?(current_user.id) then @@ -212,6 +215,20 @@ end end + def share + @plan = Plan.find(params[:id]) + authorize @plan + @plan_data = @plan.to_hash + if !user_signed_in? then + respond_to do |format| + format.html { redirect_to edit_user_registration_path } + end + elsif !@plan.editable_by?(current_user.id) then + respond_to do |format| + format.html { redirect_to plans_url, notice: I18n.t('helpers.settings.plans.errors.no_access_account') } + end + end + end def destroy @@ -348,7 +365,7 @@ @plan = Plan.find(params[:id]) authorize @plan - if user_signed_in? && @plan.readable_by(current_user.id) then + if user_signed_in? && @plan.readable_by?(current_user.id) then @exported_plan = ExportedPlan.new.tap do |ep| ep.plan = @plan ep.user = current_user @@ -356,7 +373,7 @@ ep.format = request.format.to_sym plan_settings = @plan.settings(:export) - Settings::Dmptemplate::DEFAULT_SETTINGS.each do |key, value| + Settings::Template::DEFAULT_SETTINGS.each do |key, value| ep.settings(:export).send("#{key}=", plan_settings.send(key)) end end @@ -415,4 +432,48 @@ groups.values end + + def fixup_hash(plan) + rollup(plan, "notes", "answer_id", "answers") + rollup(plan, "answers", "question_id", "questions") + rollup(plan, "questions", "section_id", "sections") + rollup(plan, "sections", "phase_id", "phases") + + plan["template"]["phases"] = plan.delete("phases") + + ghash = {} + plan["guidance_groups"].map{|g| ghash[g["id"]] = g} + plan["plan_guidance_groups"].each do |pgg| + pgg["guidance_group"] = ghash[ pgg["guidance_group_id"] ] + end + + plan["template"]["org"] = Org.find(plan["template"]["org_id"]).serializable_hash() + end + + + # find all object under src_plan_key + # merge them into the items under obj_plan_key using + # super_id = id + # so we have answers which each have a question_id + # rollup(plan, "answers", "quesiton_id", "questions") + # will put the answers into the right questions. + def rollup(plan, src_plan_key, super_id, obj_plan_key) + id_to_obj = Hash.new() + plan[src_plan_key].each do |o| + id = o[super_id] + if !id_to_obj.has_key?(id) + id_to_obj[id] = Array.new + end + id_to_obj[id] << o + end + + plan[obj_plan_key].each do |o| + id = o["id"] + if id_to_obj.has_key?(id) + o[src_plan_key] = id_to_obj[ id ] + end + end + plan.delete(src_plan_key) + end + end diff --git a/app/models/exported_plan.rb b/app/models/exported_plan.rb index 05fe0e2..4045298 100644 --- a/app/models/exported_plan.rb +++ b/app/models/exported_plan.rb @@ -25,7 +25,9 @@ # Getters to match Settings::Template::VALID_ADMIN_FIELDS def project_name - self.plan.title + name = self.plan.template.title + name += " - #{self.plan.title}" if self.plan.template.phases.count > 1 + name end def project_identifier diff --git a/app/models/phase.rb b/app/models/phase.rb index 31e3a57..88429fd 100644 --- a/app/models/phase.rb +++ b/app/models/phase.rb @@ -4,7 +4,7 @@ # [+Created:+] 03/09/2014 # [+Copyright:+] Digital Curation Centre and University of California Curation Center class Phase < ActiveRecord::Base - #extend FriendlyId +# extend FriendlyId ## # Associations diff --git a/app/models/plan.rb b/app/models/plan.rb index 3013e10..a32b171 100644 --- a/app/models/plan.rb +++ b/app/models/plan.rb @@ -5,6 +5,7 @@ has_many :phases, through: :template has_many :sections, through: :phases has_many :questions, through: :sections + has_many :themes, through: :questions has_many :answers, dependent: :destroy has_many :notes, through: :answers has_many :roles, dependent: :destroy @@ -35,7 +36,7 @@ #TODO: work out why this messes up plan creation : # briley: Removed reliance on :users, its really on :roles (shouldn't have a plan without at least a creator right?) It should be ok like this though now - validates :template, :title, presence: true +# validates :template, :title, presence: true ## # Constants @@ -113,7 +114,6 @@ def set_possible_guidance_groups # find all the themes in this plan # and get the guidance groups they belong to - logger.debug "RAY: set_possible_guidance_groups" ggroups = [] self.template.phases.each do |phase| phase.sections.each do |section| @@ -140,7 +140,6 @@ # @return array of hashes with orgname, themes and the guidance itself def guidance_for_question(question) guidances = [] - logger.debug "RAY: guidance_for_question" # add in the guidance for the template org unless self.template.org.nil? then @@ -599,23 +598,26 @@ end return self.template.org end +=end ## # returns the funder organisation for the project or nil if none is specified # # @return [Organisation, nil] the funder for project, or nil if none exists def funder - if self.template.nil? then + template = self.template + if template.nil? then return nil end - template_org = self.template.org - if template_org.funder? - return template_org + + if template.customization_of + return template.customization_of.org else - return nil + return template.org end end +=begin ## # returns the name of the funder for the project # @@ -824,9 +826,172 @@ end =end - + + + ## + # + # The following method is to help optimise data access. + # Even using includes or joins the data access gets performed lazily + # and the caching appears to be forgotten at times over view/partial boundaries + # To get round it we can pull everything out into a hash and use that + # which guarantees no further DB accesses. + # + # The serializable_hash method only pulls in one level but this includes + # attributes which are "through" other attributes. So we do a basic + # conversion to hash and then a "fixup" which knits the pieces together into + # the structure which we really want. + # + def to_hash + plan_data = self.serializable_hash( + include: [ :template, :phases, :sections, + :answers, :notes, :roles, :users, :questions, + :plan_guidance_groups, :guidance_groups] + ) + + question_hash = {} + + plan_data["questions"].each do |q| + question_hash[q["id"]] = q + end + + question_ids = question_hash.keys + + suggested_answers = SuggestedAnswer.where(question_id: question_ids).where.not(text: '') + suggested_answers.each do |sa| + question_hash[sa.question_id]["suggested_answer"] = sa.serializable_hash + end + + qf_hash = {} + QuestionFormat.all.each do |qf| + qf_hash[qf.id] = qf.serializable_hash + end + question_hash.values.each do |q| + q["question_format"] = qf_hash[q["question_format_id"]] + end + + gg_ids = plan_data["plan_guidance_groups"].select{|pgg| pgg["selected"]}.map{|pgg| pgg["guidance_group_id"]} + gg_hash = {} + + theme_guidance = {} + + ggs = GuidanceGroup.find(gg_ids).each do |gg| + gg_hash[gg.id] = gg.serializable_hash + end + + guidances = Guidance.joins(:themes).select('guidances.guidance_group_id, guidances.text, themes.title').where(guidance_group: gg_ids).to_a + guidances.each do |g| + title = g.title + if !theme_guidance.has_key?(title) + theme_guidance[title] = Array.new + end + theme_guidance[title] << { + "text" => g.text, + "org" => gg_hash[g.guidance_group_id]["name"] + } + end + + plan_data["questions"].each do |q| + qg = {} + if q.has_key?("themes") + q["themes"].each do |t| + title = t["title"] + qg[title] = theme_guidance[title] if theme_guidance.has_key?(title) + end + q["theme_guidance"] = qg + end + end + + fixup_hash(plan_data) + + return plan_data + end + + + private + # reconnect the various parts of the hash so that: + # plan = { + # template: + # phases: + # sections: + # questions: + # answers + # } + # + # becomes a nested structure like so + # + # plan = { template: { + # phases: [ { + # sections: [ { + # questions: [ { + # answers: [...] + # + def fixup_hash(plan) + # sort out guidance first so we can add it to the questions + # before rolling up + ghash = {} + plan["guidance_groups"].map{|g| ghash[g["id"]] = g} + plan["plan_guidance_groups"].each do |pgg| + pgg["guidance_group"] = ghash[ pgg["guidance_group_id"] ] + end + + rollup(plan, "notes", "answer_id", "answers") + rollup(plan, "answers", "question_id", "questions") + rollup(plan, "questions", "section_id", "sections", true) + rollup(plan, "sections", "phase_id", "phases", true) + + plan["template"]["phases"] = plan.delete("phases") + plan["template"]["phases"].sort! { |x,y| x["number"].to_i <=> y["number"].to_i } + + plan["template"]["org"] = Org.find(plan["template"]["org_id"]).serializable_hash() + + # when editing phases we want the number of questions answered + # so calculate that now + plan["template"]["phases"].each do |phase| + phase["sections"].each do |section| + nanswers = 0 + section["questions"].each do |question| + if question.has_key?("answers") && question["answers"].first["text"].present? + nanswers += 1 + end + end + section["nanswers"] = nanswers + end + end + + end + + + # find all object under src_plan_key + # merge them into the items under obj_plan_key using + # super_id = id + # so we have answers which each have a question_id + # rollup(plan, "answers", "quesiton_id", "questions") + # will put the answers into the right questions. + # sort determines whether the items being rolled up should be sorted by number field + def rollup(plan, src_plan_key, super_id, obj_plan_key, sort = false) + id_to_obj = Hash.new() + plan[src_plan_key].each do |o| + id = o[super_id] + if !id_to_obj.has_key?(id) + id_to_obj[id] = Array.new + end + id_to_obj[id] << o + end + + plan[obj_plan_key].each do |o| + id = o["id"] + if id_to_obj.has_key?(id) + if sort + id_to_obj[ id ].sort! { |x,y| x["number"].to_i <=> y["number"].to_i } + end + o[src_plan_key] = id_to_obj[ id ] + end + end + plan.delete(src_plan_key) + end + ## # adds a user to the project # if no flags are specified, the user is given read privleges diff --git a/app/policies/guidance_policy.rb b/app/policies/guidance_policy.rb index ee7be10..725326c 100644 --- a/app/policies/guidance_policy.rb +++ b/app/policies/guidance_policy.rb @@ -53,7 +53,7 @@ class Scope < Scope def resolve - scope = Guidance.includes(:guidance_group, :question, :themes).by_org(user.org_id) + scope = Guidance.includes(:guidance_group, :themes).by_org(user.org_id) end end end \ No newline at end of file diff --git a/app/policies/plan_policy.rb b/app/policies/plan_policy.rb index 3f5ba9a..cb43258 100644 --- a/app/policies/plan_policy.rb +++ b/app/policies/plan_policy.rb @@ -20,6 +20,10 @@ @plan.editable_by?(@user.id) end + def share? + @plan.readable_by?(@user.id) + end + def export? @plan.readable_by?(@user.id) end diff --git a/app/views/phases/_add_note.html.erb b/app/views/phases/_add_note.html.erb index 00584fd..41d72f5 100644 --- a/app/views/phases/_add_note.html.erb +++ b/app/views/phases/_add_note.html.erb @@ -1,13 +1,21 @@ -<% new_note = Note.new %> -<% answerid = answer.id %> + +<% new_note = Note.new + if !answer.nil? + answerid = answer["id"] + else + answerid = answer_obj.id + end +%> <%= form_for :new_note, :url => {:controller => :notes, :action => :create }, :html=>{:method=>:post, :id => "new_note_form_#{answerid}", :class => "add_note_form"} do |f| %> <%= f.hidden_field :user_id, :value => current_user.id %> <%= f.hidden_field :answer_id, :value => answerid %> + <%= f.hidden_field :question_id, :value => question["id"] %> + <%= f.hidden_field :plan_id, :value => plan_data["id"] %> <%= text_area_tag("#{answerid}new_note_text".to_sym, "" , class: "tinymce") %>
diff --git a/app/views/phases/_answer_form.html.erb b/app/views/phases/_answer_form.html.erb index 289ef9b..74ae43a 100644 --- a/app/views/phases/_answer_form.html.erb +++ b/app/views/phases/_answer_form.html.erb @@ -7,25 +7,36 @@
- <% q_format = question.question_format %> + <% + q_format = question["question_format"] + answer = nil + answer_obj = nil + if question.has_key?("answers") + answer = question["answers"].first + answer_obj = Answer.find(answer["id"]) + else + answer_obj = Answer.new + end + question_obj = Question.find(question["id"]) + %> -
- <%= semantic_form_for answer, :url => {:controller => :answers, :action => :update }, :html=>{:method=>:put}, :remote => true do |f| %> +
" class="question-form"> + <%= semantic_form_for answer_obj, :url => {:controller => :answers, :action => :update }, :html=>{:method=>:put}, :remote => true do |f| %> <%= f.inputs do %> - <%= f.input :plan_id, :as => :hidden %> + <%= f.input :plan_id, :as => :hidden, :input_html => { :value => @plan_data["id"] } %> <%= f.input :user_id, :as => :hidden, :input_html => { :value => current_user.id } %> - <%= f.input :question_id, :as => :hidden, :input_html => { :class => "question_id" } %> + <%= f.input :question_id, :as => :hidden, :input_html => { :value => question["id"], :class => "question_id" } %> - <%= raw question.text %> + <%= raw question["text"] %> - <% suggested_answer = question.suggested_answers.where(org_id: plan.template.org_id).first %> - <% if suggested_answer && suggested_answer.text.present? %> + <% if question.has_key?("suggested_answer") && question["suggested_answer"]["text"].present? %> + <% suggested_answer = question["suggested_answer"] %>
- <% if suggested_answer.is_example? then %> + <% if suggested_answer["is_example"] then %> <%= t("org_admin.questions.example_answer_label")%> <%else%> <%= t("org_admin.questions.suggested_answer_label")%> @@ -34,14 +45,14 @@

- <%= raw suggested_answer.text %> + <%= raw suggested_answer["text"] %>

<% end %> - <% if [ @checkbox, @multi, @radio, @dropdown ].include?( q_format.id ) %> + <% if [ @checkbox, @multi, @radio, @dropdown ].include?( q_format["id"] ) %> <% options = question.options.order("number") %> @@ -73,10 +84,10 @@ <%end%> <% end %> - <% if q_format.id == @textfield %> + <% if q_format["id"] == @textfield %> <%= text_field_tag("answer-text-#{question.id}".to_sym, strip_tags(answer.text), class: "question_text_field") %> - <% elsif q_format.id == @textarea %> - <%= text_area_tag("answer-text-#{question.id}".to_sym, answer.text, class: "tinymce") %> + <% elsif q_format["id"] == @textarea %> + <%= text_area_tag("answer-text-#{question["id"]}".to_sym, answer_obj.text, class: "tinymce") %> <% end %> <% end %> @@ -84,28 +95,28 @@ <%= f.actions do %> <%= f.action :submit, :label => t("helpers.save"), :button_html => { :class => "btn btn-primary"} %> - +
  • " class="saving-message" style="display:none;"><%= t("helpers.saving")%>
  • <% end %> <% end %>
    - <% if answer.created_at.nil? %> - <%= t("helpers.notanswered") %> + <% if answer.nil? || !answer.has_key?("created_at") || answer["created_at"].nil? %> + -status" class="label label-warning answer-status"><%= t("helpers.notanswered") %> <% else %> - <%= t("helpers.answered_by")%><%= answer.created_at %><%= t("helpers.answered_by_part2")%><%= answer.user.name %> + -status" class="label label-info answer-status"><%= t("helpers.answered_by")%><%= answer_obj.created_at %><%= t("helpers.answered_by_part2")%><%= answer_obj.user.name %> <% end %>
    - +-unsaved" class="label label-inverse answer-unsaved" style="display:none;"><%= t("helpers.unsaved") %>
    -
    - <% comments = answer.notes.all %> - <%= hidden_field_tag :question_id, question.id, :class => "question_id" %> +
    " class="question_right_column_nav"> + <% comments = answer_obj.notes.all %> + <%= hidden_field_tag :question_id, question["id"], :class => "question_id" %>