diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 27731cc..2c30573 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,10 +17,10 @@ On your local machine, add a remote that points to the original DMPRoadmap codebase. This will allow you to pull down the latest changes and sync up your forked repository -Run the following from your local clone of the repository to setup a remote that will allow you to pull down the latest changes from DMPRoadmap. Then pull down the contributions branch: +Run the following from your local clone of the repository to setup a remote that will allow you to pull down the latest changes from DMPRoadmap. Then pull down the development branch: ```bash git remote add upstream https://github.com/DMPRoadmap/roadmap.git -git fetch contributions +git fetch development ``` ### Pulling down the latest changes from DMPRoadmap into your fork @@ -31,12 +31,12 @@ ### Create a new feature/bug fix/translations branch -You should always base your new branch off of the contributions branch. We keep this branch up to date with the latest release. Checkout the contributions branch, sync it with DMPRoadmap and then push the latest up to your own fork: +You should always base your new branch off of the development branch. We keep this branch up to date with the latest release. Checkout the development branch, sync it with DMPRoadmap and then push the latest up to your own fork: ```bash -git checkout contributions -git pull upstream contributions -git push origin contributions +git checkout development +git pull upstream development +git push origin development git checkout -b [my-branch] ``` @@ -50,7 +50,7 @@ When you are finished making changes, we ask that all contributors squash their commits into a single git commit. This helps us keep the git history clean and makes it easier to revert any changes if necessary. -_Note that if this is your first time rebasing a branch we recommend making a buckup of the branch first since a rebase creates the potential for you to lose your changes if its done incorrectly: `git checkout -b [feature branch]-bak && git checkout [feature branch]`_ +_Note that if this is your first time rebasing a branch we recommend making a backup of the branch first since a rebase creates the potential for you to lose your changes if its done incorrectly: `git checkout -b [feature branch]-bak && git checkout [feature branch]`_ To rebase your feature branch you should follow this example: @@ -130,7 +130,7 @@ Once your changes are complete, push your branch up to your fork, `git push origin [my-branch]` -Then login to Github and go to your fork. Select your branch from the list and click 'New Pull Request'. On the page that opens, select the 'contributions' branch on the DMPRoadmap section. +Then login to Github and go to your fork. Select your branch from the list and click 'New Pull Request'. On the page that opens, select the 'development' branch on the DMPRoadmap section. Then review your code and provide us with detailed comments about what the changes are doing (e.g. adding a new feature, fixing a recorded bug, etc.). If you are working off of one of our Github issues, then please note that in the PR message with a `Fixes #1234`. @@ -142,11 +142,11 @@ ### Acceptence of your PR -Once your code has been approved a member of the core development team will merge it into the contributions branch and then into development when we are ready to include it in a sprint. +Once your code has been approved a member of the core development team will merge it into the development branchand include it in an upcoming release. At this point its a good idea to delete the branch from your fork in Github and also delete it from your local machine via: ```bash -git checkout contributions +git checkout development git branch -D [my-branch] ``` diff --git a/app/controllers/org_admin/questions_controller.rb b/app/controllers/org_admin/questions_controller.rb index 555b105..69d008b 100644 --- a/app/controllers/org_admin/questions_controller.rb +++ b/app/controllers/org_admin/questions_controller.rb @@ -1,7 +1,7 @@ module OrgAdmin class QuestionsController < ApplicationController include Versionable - + respond_to :html after_action :verify_authorized @@ -9,32 +9,32 @@ def show question = Question.includes(:annotations, :question_options, section: { phase: :template }).find(params[:id]) authorize question - render partial: 'show', locals: { - template: question.section.phase.template, + render partial: 'show', locals: { + template: question.section.phase.template, section: question.section, - question: question + question: question } end - + # GET /org_admin/templates/[:template_id]/phases/[:phase_id]/sections/[:section_id]/question/[:id]/edit def edit question = Question.includes(:annotations, :question_options, section: { phase: :template }).find(params[:id]) authorize question - render partial: 'edit', locals: { - template: question.section.phase.template, + render partial: 'edit', locals: { + template: question.section.phase.template, section: question.section, - question: question + question: question } end - + # GET /org_admin/templates/[:template_id]/phases/[:phase_id]/sections/[:section_id]/questions/new def new section = Section.includes(:questions, phase: :template).find(params[:section_id]) nbr = section.questions.maximum(:number) question = Question.new({ section_id: section.id, question_format: QuestionFormat.find_by(title: 'Text area'), number: nbr.present? ? nbr + 1 : 1 }) authorize question - render partial: 'form', locals: { - template: section.phase.template, + render partial: 'form', locals: { + template: section.phase.template, section: section, question: question, method: 'post', @@ -66,12 +66,15 @@ authorize question begin question = get_modifiable(question) - # Need to reattach the incoming annotation's and question_options to the + # Need to reattach the incoming annotation's and question_options to the # modifiable (versioned) question attrs = question_params attrs = transfer_associations(question) if question.id != params[:id] # If the user unchecked all of the themes set the association to an empty array - attrs[:theme_ids] = [] unless attrs[:theme_ids].present? + # add check for number present to ensure this is not just an annotation + if attrs[:theme_ids].blank? && attrs[:number].present? + attrs[:theme_ids] = [] + end if question.update!(attrs) flash[:notice] = success_message(_('question'), _('updated')) else @@ -83,14 +86,14 @@ end if question.section.phase.template.customization_of.present? redirect_to org_admin_template_phase_path({ - template_id: question.section.phase.template.id, - id: question.section.phase.id, + template_id: question.section.phase.template.id, + id: question.section.phase.id, section: question.section.id }) else redirect_to edit_org_admin_template_phase_path({ - template_id: question.section.phase.template.id, - id: question.section.phase.id, + template_id: question.section.phase.template.id, + id: question.section.phase.id, section: question.section.id }) end @@ -112,8 +115,8 @@ flash[:alert] = _('Unable to create a new version of this template.') end redirect_to edit_org_admin_template_phase_path({ - template_id: section.phase.template.id, - id: section.phase.id, + template_id: section.phase.template.id, + id: section.phase.id, section: section.id }) end @@ -122,23 +125,23 @@ def question_params params.require(:question).permit(:number, :text, :question_format_id, :option_comment_display, :default_value, question_options_attributes: [:id, :number, :text, :is_default, :_destroy], annotations_attributes: [:id, :text, :org_id, :org, :type], theme_ids: []) end - - # When a template gets versioned by changes to one of its questions we need to loop + + # When a template gets versioned by changes to one of its questions we need to loop # through the incoming params and ensure that the annotations and question_options # get attached to the new question def transfer_associations(question) attrs = question_params if attrs[:annotations_attributes].present? attrs[:annotations_attributes].each_key do |key| - old_annotation = question.annotations.select do |a| - a.org_id.to_s == attrs[:annotations_attributes][key][:org_id] && a.type.to_s == attrs[:annotations_attributes][key][:type] + old_annotation = question.annotations.select do |a| + a.org_id.to_s == attrs[:annotations_attributes][key][:org_id] && a.type.to_s == attrs[:annotations_attributes][key][:type] end attrs[:annotations_attributes][key][:id] = old_annotation.first.id unless old_annotation.empty? end end # TODO: This question_options id swap feel fragile. We cannot really match on any of the # data elements because the user may have changed them so we rely on its position - # within the array/query since they should be equivalent. + # within the array/query since they should be equivalent. if attrs[:question_options_attributes].present? attrs[:question_options_attributes].each_key do |key| attrs[:question_options_attributes][key][:id] = question.question_options[key.to_i].id.to_s if question.question_options[key.to_i].present? @@ -147,4 +150,4 @@ attrs end end -end \ No newline at end of file +end diff --git a/app/controllers/orgs_controller.rb b/app/controllers/orgs_controller.rb index ccc0c8d..135fc5c 100644 --- a/app/controllers/orgs_controller.rb +++ b/app/controllers/orgs_controller.rb @@ -9,7 +9,7 @@ authorize org languages = Language.all.order("name") org.links = {"org": []} unless org.links.present? - render 'admin_edit', locals: {org: org, languages: languages, method: 'PUT', + render 'admin_edit', locals: {org: org, languages: languages, method: 'PUT', url: admin_update_org_path(org) } end @@ -22,23 +22,23 @@ @org.logo = attrs[:logo] if attrs[:logo] tab = (attrs[:feedback_enabled].present? ? 'feedback' : 'profile') if params[:org_links].present? - @org.links = JSON.parse(params[:org_links]) + @org.links = JSON.parse(params[:org_links]) end - + begin # Only allow super admins to change the org types and shib info - if current_user.can_super_admin? + if current_user.can_super_admin? # Handle Shibboleth identifiers if that is enabled - if Rails.application.config.shibboleth_use_filtered_discovery_service + if Rails.application.config.shibboleth_use_filtered_discovery_service && params[:shib_id].present? shib = IdentifierScheme.find_by(name: 'shibboleth') shib_settings = @org.org_identifiers.select{ |ids| ids.identifier_scheme == shib}.first - if params[:shib_id].present? || params[:shib_domain].present? + if !params[:shib_id].blank? shib_settings = OrgIdentifier.new(org: @org, identifier_scheme: shib) unless shib_settings.present? shib_settings.identifier = params[:shib_id] shib_settings.attrs = {domain: params[:shib_domain]} shib_settings.save - else + else if shib_settings.present? # The user cleared the shib values so delete the object shib_settings.destroy @@ -46,7 +46,7 @@ end end end - + if @org.update_attributes(attrs) flash[:notice] = success_message(_('organisation'), _('saved')) redirect_to "#{admin_edit_org_path(@org)}\##{tab}" @@ -63,14 +63,14 @@ # ---------------------------------------------------------------- def shibboleth_ds redirect_to root_path unless current_user.nil? - + @user = User.new - # Display the custom Shibboleth discovery service page. + # Display the custom Shibboleth discovery service page. @orgs = Org.joins(:identifier_schemes).where('identifier_schemes.name = ?', 'shibboleth').sort{|x,y| x.name <=> y.name } - + if @orgs.empty? flash[:alert] = _('No organisations are currently registered.') - redirect_to user_shibboleth_omniauth_authorize_path + redirect_to user_shibboleth_omniauth_authorize_path end end @@ -82,12 +82,12 @@ scheme = IdentifierScheme.find_by(name: 'shibboleth') shib_entity = OrgIdentifier.where(org_id: params['shib-ds'][:org_id], identifier_scheme: scheme) - + if !shib_entity.empty? # Force SSL url = "#{request.base_url.gsub('http:', 'https:')}#{Rails.application.config.shibboleth_login}" target = "#{user_shibboleth_omniauth_callback_url.gsub('http:', 'https:')}" - + #initiate shibboleth login sequence redirect_to "#{url}?target=#{target}&entityID=#{shib_entity.first.identifier}" else @@ -104,6 +104,6 @@ private def org_params params.require(:org).permit(:name, :abbreviation, :logo, :contact_email, :contact_name, :remove_logo, :org_type, - :feedback_enabled, :feedback_email_subject, :feedback_email_msg) + :feedback_enabled, :feedback_email_msg) end end \ No newline at end of file diff --git a/app/controllers/plans_controller.rb b/app/controllers/plans_controller.rb index 8c5c943..080f0d3 100644 --- a/app/controllers/plans_controller.rb +++ b/app/controllers/plans_controller.rb @@ -1,8 +1,10 @@ -require 'pp' class PlansController < ApplicationController include ConditionalUserMailer + require 'pp' helper PaginableHelper helper SettingsTemplateHelper + include FeedbacksHelper + after_action :verify_authorized, except: [:overview] def index @@ -153,25 +155,27 @@ def edit plan = Plan.find(params[:id]) authorize plan - + plan, phase = Plan.load_for_phase(params[:id], params[:phase_id]) - + readonly = !plan.editable_by?(current_user.id) - guidance_groups = GuidanceGroup.where(published: true, id: plan.guidance_group_ids) + guidance_groups_ids = plan.guidance_groups.collect(&:id) + + guidance_groups = GuidanceGroup.where(published: true, id: guidance_groups_ids) + # Since the answers have been pre-fetched through plan (see Plan.load_for_phase) # we create a hash whose keys are question id and value is the answer associated answers = plan.answers.reduce({}){ |m, a| m[a.question_id] = a; m } - + render('/phases/edit', locals: { base_template_org: phase.template.base_org, plan: plan, phase: phase, readonly: readonly, question_guidance: plan.guidance_by_question_as_hash, guidance_groups: guidance_groups, - answers: answers, - guidance_service: GuidanceService.new(plan) }) + answers: answers }) end - + # PUT /plans/1 # PUT /plans/1.json def update @@ -185,7 +189,7 @@ guidance_group_ids = params[:guidance_group_ids].blank? ? [] : params[:guidance_group_ids].map(&:to_i).uniq @plan.guidance_groups = GuidanceGroup.where(id: guidance_group_ids) @plan.save - + if @plan.update_attributes(attrs) format.html { redirect_to overview_plan_path(@plan), notice: success_message(_('plan'), _('saved')) } format.json {render json: {code: 1, msg: success_message(_('plan'), _('saved'))}} @@ -194,7 +198,7 @@ format.html { render action: "edit" } format.json {render json: {code: 0, msg: flash[:alert]}} end - + rescue Exception flash[:alert] = failed_update_error(@plan, _('plan')) format.html { render action: "edit" } @@ -348,18 +352,19 @@ end def request_feedback - plan = Plan.find(params[:id]) - authorize plan + @plan = Plan.find(params[:id]) + authorize @plan alert = _('Unable to submit your request for feedback at this time.') begin - if plan.request_feedback(current_user) - redirect_to share_plan_path(plan), notice: _('Your request for feedback has been submitted.') + if @plan.request_feedback(current_user) + redirect_to share_plan_path(@plan), + notice: _(request_feedback_flash_notice) else - redirect_to share_plan_path(plan), alert: alert + redirect_to share_plan_path(@plan), alert: alert end rescue Exception - redirect_to share_plan_path(plan), alert: alert + redirect_to share_plan_path(@plan), alert: alert end end @@ -445,4 +450,15 @@ end plan.delete(src_plan_key) end + + # Flash notice for successful feedback requests + # + # @return [String] + def request_feedback_flash_notice + # Use the generic feedback confirmation message unless the Org has + # specified one + text = current_user.org.feedback_email_msg || + feedback_confirmation_default_message + feedback_constant_to_text(text, current_user, @plan, current_user.org) + end end diff --git a/app/controllers/roles_controller.rb b/app/controllers/roles_controller.rb index 2937035..913bc2a 100644 --- a/app/controllers/roles_controller.rb +++ b/app/controllers/roles_controller.rb @@ -7,7 +7,7 @@ registered = true @role = Role.new(role_params) authorize @role - + access_level = params[:role][:access_level].to_i @role.set_access_level(access_level) message = '' @@ -30,7 +30,8 @@ if @role.save if registered deliver_if(recipients: user, key: 'users.added_as_coowner') do |r| - UserMailer.sharing_notification(@role, r).deliver_now + UserMailer.sharing_notification(@role, r, inviter: current_user) + .deliver_now end end flash[:notice] = message @@ -73,7 +74,7 @@ end redirect_to controller: 'plans', action: 'share', id: @role.plan.id end - + # This function makes user's role on a plan inactive - i.e. "removes" this from their plans def deactivate role = Role.find(params[:id]) diff --git a/app/helpers/feedbacks_helper.rb b/app/helpers/feedbacks_helper.rb new file mode 100644 index 0000000..15fca1b --- /dev/null +++ b/app/helpers/feedbacks_helper.rb @@ -0,0 +1,18 @@ +module FeedbacksHelper + def feedback_confirmation_default_subject + _('%{application_name}: Your plan has been submitted for feedback') + end + + def feedback_confirmation_default_message + _('

Hello %{user_name}.

'\ + '

Your plan "%{plan_name}" has been submitted for feedback from an administrator at your organisation. '\ + 'If you have questions pertaining to this action, please contact us at %{organisation_email}.

') + end + + def feedback_constant_to_text(text, user, plan, org) + _("#{text}") % {application_name: Rails.configuration.branding[:application][:name], + user_name: user.name, + plan_name: plan.title, + organisation_email: org.contact_email} + end +end diff --git a/app/helpers/mailer_helper.rb b/app/helpers/mailer_helper.rb index a7ee2df..d837693 100644 --- a/app/helpers/mailer_helper.rb +++ b/app/helpers/mailer_helper.rb @@ -1,21 +1,5 @@ module MailerHelper include PermsHelper - def feedback_confirmation_default_subject - _('%{application_name}: Your plan has been submitted for feedback') - end - - def feedback_confirmation_default_message - _('

Hello %{user_name}.

'\ - '

Your plan "%{plan_name}" has been submitted for feedback from an administrator at your organisation. '\ - 'If you have questions pertaining to this action, please contact us at %{organisation_email}.

') - end - - def feedback_constant_to_text(text, user, plan, org) - _("#{text}") % {application_name: Rails.configuration.branding[:application][:name], - user_name: user.name, - plan_name: plan.title, - organisation_email: org.contact_email} - end # Returns an unordered HTML list with the permissions associated to the user passed def privileges_list(user) @@ -23,7 +7,7 @@ names = name_and_text r= "" end diff --git a/app/helpers/orgs_helper.rb b/app/helpers/orgs_helper.rb new file mode 100644 index 0000000..f106fa1 --- /dev/null +++ b/app/helpers/orgs_helper.rb @@ -0,0 +1,19 @@ +module OrgsHelper + # frozen_string_literal: true + + DEFAULT_EMAIL = '%{organisation_email}' + + # Tooltip string for Org feedback form. + # + # @param org [Org] The current Org we're updating feedback form for. + # @return [String] The tooltip message + def tooltip_for_org_feedback_form(org) + email = org.contact_email.presence || DEFAULT_EMAIL + _("SAMPLE MESSAGE: A data librarian from %{org_name} will respond to your request within 48 + hours. If you have questions pertaining to this action please contact us + at %{organisation_email}.") % { + organisation_email: email, + org_name: org.name + } + end +end diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index 6fa76b0..d459b3e 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -1,91 +1,97 @@ class UserMailer < ActionMailer::Base include MailerHelper helper MailerHelper + helper FeedbacksHelper + default from: Rails.configuration.branding[:organisation][:email] - + def welcome_notification(user) @user = user FastGettext.with_locale FastGettext.default_locale do - mail(to: @user.email, + mail(to: @user.email, subject: _('Welcome to %{tool_name}') %{ :tool_name => Rails.configuration.branding[:application][:name] }) end end - - def sharing_notification(role, user) - @role = role - @user = user + + def sharing_notification(role, user, inviter:) + @role = role + @user = user + @inviter = inviter + subject = _("A Data Management Plan in %{tool_name} has been shared "\ + "with you") % { + tool_name: Rails.configuration.branding[:application][:name] + } FastGettext.with_locale FastGettext.default_locale do - mail(to: @role.user.email, - subject: _('A Data Management Plan in %{tool_name} has been shared with you') %{ :tool_name => Rails.configuration.branding[:application][:name] }) + mail(to: @role.user.email, subject: subject) end end - + def permissions_change_notification(role, user) @role = role @user = user if user.active? FastGettext.with_locale FastGettext.default_locale do - mail(to: @role.user.email, + mail(to: @role.user.email, subject: _('Changed permissions on a Data Management Plan in %{tool_name}') %{ :tool_name => Rails.configuration.branding[:application][:name] }) end end end - + def plan_access_removed(user, plan, current_user) @user = user @plan = plan @current_user = current_user if user.active? FastGettext.with_locale FastGettext.default_locale do - mail(to: @user.email, + mail(to: @user.email, subject: "#{_('Permissions removed on a DMP in %{tool_name}') %{ :tool_name => Rails.configuration.branding[:application][:name] }}") end end end - + def feedback_notification(recipient, plan, requestor) @user = requestor - + if @user.org.present? && recipient.active? @org = @user.org @plan = plan @recipient = recipient - + FastGettext.with_locale FastGettext.default_locale do - mail(to: recipient.email, + mail(to: recipient.email, subject: _("%{application_name}: %{user_name} requested feedback on a plan") % {application_name: Rails.configuration.branding[:application][:name], user_name: @user.name(false)}) end end end - + def feedback_complete(recipient, plan, requestor) @requestor = requestor - @user = recipient - @plan = plan - + @user = recipient + @plan = plan + @phase = plan.phases.first if recipient.active? FastGettext.with_locale FastGettext.default_locale do - mail(to: recipient.email, + mail(to: recipient.email, subject: _("%{application_name}: Expert feedback has been provided for %{plan_title}") % {application_name: Rails.configuration.branding[:application][:name], plan_title: @plan.title}) end end end - + def feedback_confirmation(recipient, plan, requestor) user = requestor if user.org.present? && recipient.active? org = user.org plan = plan - + # Use the generic feedback confirmation message unless the Org has specified one subject = (org.feedback_email_subject.present? ? org.feedback_email_subject : feedback_confirmation_default_subject) message = (org.feedback_email_msg.present? ? org.feedback_email_msg : feedback_confirmation_default_message) @body = feedback_constant_to_text(message, user, plan, org) - + FastGettext.with_locale FastGettext.default_locale do - mail(to: recipient.email, + mail(to: recipient.email, subject: feedback_constant_to_text(subject, user, plan, org)) end end @@ -101,7 +107,7 @@ end end end - + # @param commenter - User who wrote the comment # @param plan - Plan for which the comment is associated to def new_comment(commenter, plan) @@ -119,7 +125,7 @@ end def admin_privileges(user) - @user = user + @user = user if user.active? FastGettext.with_locale FastGettext.default_locale do mail(to: user.email, subject: diff --git a/app/models/plan.rb b/app/models/plan.rb index 016dba6..a837d31 100644 --- a/app/models/plan.rb +++ b/app/models/plan.rb @@ -194,11 +194,6 @@ end if self.save! - # Send an email confirmation to the owners and co-owners - owners = User.joins(:roles).where('roles.plan_id =? AND roles.access IN (?)', self.id, Role.access_values_for(:administrator)) - deliver_if(recipients: owners, key: 'users.feedback_requested') do |r| - UserMailer.feedback_confirmation(r, self, user).deliver_now - end # Send an email to the org-admin contact if user.org.contact_email.present? contact = User.new(email: user.org.contact_email, firstname: user.org.contact_name) diff --git a/app/services/guidance_service.rb b/app/services/guidance_service.rb deleted file mode 100644 index da115f0..0000000 --- a/app/services/guidance_service.rb +++ /dev/null @@ -1,139 +0,0 @@ -class GuidanceService - attr_accessor :plan - attr_accessor :guidance_groups - - def initialize(plan) - @plan = plan - @guidance_groups = plan.guidance_groups.where(published: true) - end - # Returns an Array of orgs according to the guidance related to plan - # Note the Array is sorted in the following order: - # First funder org (if the template from the plan is a customization of another) - # Second template owner's org - # The orgs from every guidance group selected for this plan - def orgs - if !defined?(@orgs) - @orgs = [] - org_found = lambda{ |orgs, org| return orgs.find{ |lookup_org| lookup_org.id == org.id }.present? } - orgs_from_annotations.each{ |org| @orgs << org unless org_found.call(@orgs, org) } - orgs_from_guidance_groups.each{ |org| @orgs << org unless org_found.call(@orgs, org) } - end - return @orgs - end - def any?(org:nil, question:nil) - if org.nil? && question.nil? - return hashified_annotations? || hashified_guidance_groups? - end - return guidance_annotations?(org: org, question: question) || - guidance_groups_by_theme?(org: org, question: question) - end - # Returns true if exists any guidance_group applicable to the org and question passed - def guidance_groups_by_theme?(org: nil, question: nil) - return false unless question.respond_to?(:themes) - return false unless hashified_guidance_groups.has_key?(org) - result = guidance_groups_by_theme(org: org, question: question).detect do |gg, theme_hash| - if theme_hash.present? - theme_hash.detect{ |theme, guidances| guidances.present? } - else - false - end - end - return result.present? - end - # Returns true if exists any annotation applicable to the org and question passed - def guidance_annotations?(org: nil, question: nil) - return false unless question.respond_to?(:id) - return false unless hashified_annotations.has_key?(org) - return hashified_annotations[org].find{ |annotation| (annotation.question_id == question.id) && (annotation.type == "guidance")}.present? - end - # Returns a hash of guidance groups for an org and question passed with the following structure: - # { guidance_group: { theme: [guidance, ...], ... }, ... } - def guidance_groups_by_theme(org: nil, question: nil) - raise ArgumentError unless question.respond_to?(:themes) - return {} unless hashified_guidance_groups.has_key?(org) - return hashified_guidance_groups[org].each_key.reduce({}) do |acc, gg| - filtered_gg = hashified_guidance_groups[org][gg].each_key.reduce({}) do |acc, theme| - acc[theme] = hashified_guidance_groups[org][gg][theme] if question.themes.include?(theme) - acc - end - acc[gg] = filtered_gg if filtered_gg.present? - acc - end - end - # Returns a collection of annotations (type guidance) for an org and question passed - def guidance_annotations(org: nil, question: nil) - raise ArgumentError unless question.respond_to?(:id) - return [] unless hashified_annotations.has_key?(org) - return hashified_annotations[org].select{ |annotation| (annotation.question_id == question.id) && (annotation.type == "guidance")} - end - - private - def orgs_from_guidance_groups - if !defined?(@orgs_from_guidance_groups) - @orgs_from_guidance_groups = Org.joins(:guidance_groups).where("guidance_groups.id": guidance_groups.map(&:id)).distinct("orgs.id") - end - return @orgs_from_guidance_groups - end - def orgs_from_annotations - if !defined?(@orgs_from_annotations) - @orgs_from_annotations = [] - @orgs_from_annotations << Template.find_by(family_id: plan.template.customization_of).org if plan.template.customization_of.present? - @orgs_from_annotations << plan.template.org - end - return @orgs_from_annotations - end - def hashified_guidance_groups - @hashified_guidance_groups ||= hashify_guidance_groups - end - def hashified_guidance_groups? - result = hashified_guidance_groups.detect do |org, gg_hash| - if gg_hash.present? - gg_hash.detect do |gg, theme_hash| - if theme_hash.present? - theme_hash.detect{ |theme, guidances| guidances.present? } - else - false - end - end - else - false - end - end - return result.present? - end - def hashified_annotations - @hashified_annotations ||= hashify_annotations - end - def hashified_annotations? - return hashified_annotations.detect{ |org, annotations| annotations.present? }.present? - end - # Hashifies guidance groups for a plan according to the distinct orgs into the following structure: - # { org: { guidance_group: { theme: [guidance, ...], ... }, ... }, ... } - def hashify_guidance_groups - hashified_guidances = hashify_guidances - return orgs_from_guidance_groups.reduce({}) do |acc, org| - org_guidance_groups = hashified_guidances.each_key.select{ |gg| gg.org_id == org.id } - acc[org] = org_guidance_groups.reduce({}){ |acc, gg| acc[gg] = hashified_guidances[gg]; acc } - acc - end - end - # Hashifies guidances from a collection of guidance_groups passed into the following structure: - # { guidance_group: { theme: [guidance, ...], ... }, ... } - def hashify_guidances - return guidance_groups.reduce({}) do |acc, gg| - themes = Theme.includes(:guidances).joins(:guidances).merge(Guidance.where(guidance_group_id: gg.id, published: true)) - acc[gg] = themes.reduce({}) do |acc, theme| - acc[theme] = theme.guidances - acc - end - acc - end - end - def hashify_annotations - return orgs_from_annotations.reduce({}) do |acc, org| - annotations = Annotation.where(org_id: org.id, question_id: plan.template.question_ids) - acc[org] = annotations.select{ |annotation| annotation.org_id = org.id } - acc - end - end -end diff --git a/app/views/guidance_groups/_index_by_theme.html.erb b/app/views/guidance_groups/_index_by_theme.html.erb deleted file mode 100644 index 3c39eff..0000000 --- a/app/views/guidance_groups/_index_by_theme.html.erb +++ /dev/null @@ -1,42 +0,0 @@ -<%# locals{ guidance_groups_by_theme } %> -<% parent_id = guidance_groups_by_theme.object_id %> -
-
- -
- <% guidance_groups_by_theme.each_pair do |guidance_group, theme_hash| %> - <% theme_hash.each_pair do |theme, guidances| %> -
-
" - aria-expanded="false" - aria-controls="<%= "##{guidances.object_id}" %>"> - -
-
" - class="panel-collapse collapse" - role="tabpanel" - aria-labelledby="<%= "panel-heading-#{guidances.object_id}" %>"> -
- <% guidances.each do |guidance| %> - <%= raw(guidance.text) %> - <% end %> -
-
-
- <% end %> - <% end %> -
diff --git a/app/views/guidances/new_edit.html.erb b/app/views/guidances/new_edit.html.erb index 66e3e2c..28e3cf8 100644 --- a/app/views/guidances/new_edit.html.erb +++ b/app/views/guidances/new_edit.html.erb @@ -14,8 +14,8 @@ <%= text_area_tag("guidance-text", guidance.text, class: "form-control", 'aria-required': true, rows: 10) %> <%= render partial: 'org_admin/shared/theme_selector', - locals: { f: f, all_themes: themes, as_radio: true, required: true, - popover_message: _('Select one theme that is relevant to this guidance. This will display your generic organisation-level guidance, or any Schools/Departments for which you create guidance groups, across all templates that have questions with the corresponding theme tags.') } %> + locals: { f: f, all_themes: themes, as_radio: false, required: true, + popover_message: _('Select one or more themes that are relevant to this guidance. This will display your generic organisation-level guidance, or any Schools/Departments for which you create guidance groups, across all templates that have questions with the corresponding theme tags.') } %>
<%= f.label _('Guidance group'), for: :guidance_group_id, class: 'control-label' %> <%= f.collection_select(:guidance_group_id, guidance_groups, diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 2e50441..9f43109 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -88,6 +88,7 @@ VALIDATION_MESSAGE_PASSWORDS_MATCH: _('The passwords must match.'), VALIDATION_MESSAGE_RADIO: _('Please choose one of the options.'), VALIDATION_MESSAGE_CHECKBOX: _('Please check the box to continue.'), + VALIDATION_MESSAGE_MULTI_CHECKBOX: _('Please select at least one of the options to continue.'), VALIDATION_MESSAGE_SELECT: _('Please select a value from the list.'), VALIDATION_MESSAGE_TEXT: _('This field is required.'), diff --git a/app/views/org_admin/annotations/_show.html.erb b/app/views/org_admin/annotations/_show.html.erb index d5392d9..8d52d14 100644 --- a/app/views/org_admin/annotations/_show.html.erb +++ b/app/views/org_admin/annotations/_show.html.erb @@ -1,12 +1,14 @@ <% if example_answer.present? || guidance.present? %> > <% if example_answer.present? %> -
<%= _('%{org} Example Answer') % { org: example_answer.org.short_name } %>
+ <% label = (for_plan && template.customization_of.present?) ? _('%{org} Example Answer') % { org: example_answer.org.short_name } : _('Example Answer') %> +
<%= label %>
<%= raw example_answer.text %>
<% end %> <% if guidance.present? %> -
<%= _('Question Specific Guidance') %>
+ <% label = (for_plan && template.customization_of.present?) ? _('%{org} Guidance') % { org: guidance.org.short_name } : _('Guidance') %> +
<%= label %>
<%= raw guidance.text %>
<% end %> -<% end %> +<% end %> \ No newline at end of file diff --git a/app/views/org_admin/phases/preview.html.erb b/app/views/org_admin/phases/preview.html.erb index 19acbfd..051e4b4 100644 --- a/app/views/org_admin/phases/preview.html.erb +++ b/app/views/org_admin/phases/preview.html.erb @@ -31,20 +31,19 @@
- <%= render partial: '/phases/edit_plan_answers', + <%= render partial: '/phases/edit_plan_answers', locals: { - plan: nil, - phase: phase, - readonly: true, - question_guidance: {}, - edit: false, - guidance_groups: [], - base_template_org: template.base_org, - guidance_service: GuidanceService.new(Plan.new(template: phase.template)) } %> + plan: nil, + phase: phase, + readonly: true, + question_guidance: {}, + edit: false, + guidance_groups: [], + base_template_org: template.base_org } %>
- + - + \ No newline at end of file diff --git a/app/views/org_admin/question_options/_option_fields.html.erb b/app/views/org_admin/question_options/_option_fields.html.erb index 63cd834..1bb60f2 100644 --- a/app/views/org_admin/question_options/_option_fields.html.erb +++ b/app/views/org_admin/question_options/_option_fields.html.erb @@ -21,7 +21,7 @@ <% options_q.number = i %>
- <%= op.number_field :number, in: 1..20, class: 'form-control' %> + <%= op.number_field :number, min: 1, class: 'form-control' %>
<%= op.text_field :text, as: :string, class: 'form-control' %> diff --git a/app/views/org_admin/shared/_theme_selector.html.erb b/app/views/org_admin/shared/_theme_selector.html.erb index 11fe8e7..13dd640 100644 --- a/app/views/org_admin/shared/_theme_selector.html.erb +++ b/app/views/org_admin/shared/_theme_selector.html.erb @@ -3,43 +3,41 @@ <% required ||= false %>
- <% if required %> - * - <% end %> - <%= f.label _('Themes'), for: :theme_ids, class: 'control-label' %> - <%= render partial: 'shared/popover', - locals: { message: popover_message, placement: 'right' }%> -
- <% if all_themes.length > 0 %> <% cntr = 0 nbr_of_cols = (all_themes.length.to_f / MAX_NUMBER_THEMES_PER_COLUMN.to_f).ceil col_size = (12 / (nbr_of_cols > 4 ? 3 : nbr_of_cols)).round %> -
- <% all_themes.each do |theme| %> - <% if cntr >= MAX_NUMBER_THEMES_PER_COLUMN %> +
+ + <%= _('Themes') %> + <%= render partial: 'shared/popover', + locals: { message: popover_message, placement: 'right' }%> + + +
+ <% all_themes.each do |theme| %> + <% if cntr >= MAX_NUMBER_THEMES_PER_COLUMN %> +
+
+ <% cntr = 0 %> + <% end %> +
+ <% namespace = f.object.class.name.downcase %> + <% id = f.object.id.present? ? f.object.id : 'new' %> + + value="<%= theme.id %>"<%= f.object.themes.include?(theme) ? ' checked="checked"' : '' %>> + <%= theme.title %>
-
- <% cntr = 0 %> + <% cntr += 1 %> <% end %> -
-
- <% namespace = f.object.class.name.downcase %> - <% id = f.object.id.present? ? f.object.id : 'new' %> - - value="<%= theme.id %>"<%= f.object.themes.include?(theme) ? ' checked="checked"' : '' %>> - <%= theme.title %> -
-
- <% cntr += 1 %> - <% end %> -
+
+
<% else %>

<%= _('No themes have been defined. Please contact your administrator for assistance.') %>

<% end %> diff --git a/app/views/orgs/_feedback_form.html.erb b/app/views/orgs/_feedback_form.html.erb index 30df386..c16752e 100644 --- a/app/views/orgs/_feedback_form.html.erb +++ b/app/views/orgs/_feedback_form.html.erb @@ -8,19 +8,14 @@
-

<%= _('Request Expert Feedback - Automated Email:') %>

+

+ <%= _('Request Expert Feedback - Message Displayed on Share Plan Tab:') %> +

-
- <%= f.label :feedback_email_subject, _('Subject'), class: "control-label" %> - - <%= f.text_field :feedback_email_subject, class: "form-control", placeholder: feedback_confirmation_default_subject.gsub('%{application_name}', Rails.configuration.branding[:application][:name]) %> -
-
-
- <% tip = feedback_confirmation_default_message.gsub('%{organisation_email}', org.contact_email.present? ? org.contact_email : '%{organisation_email}') %> -
+
<%= f.label :feedback_email_msg, _('Message'), class: "control-label" %> - <%= f.text_area :feedback_email_msg, class: "form-control" %> + <%= f.text_area :feedback_email_msg, class: "form-control", + "aria-required" => true %>
diff --git a/app/views/phases/_edit_plan_answers.html.erb b/app/views/phases/_edit_plan_answers.html.erb index e01758b..41cc613 100644 --- a/app/views/phases/_edit_plan_answers.html.erb +++ b/app/views/phases/_edit_plan_answers.html.erb @@ -72,12 +72,7 @@
- <%= render partial: '/phases/guidances_notes', locals: { - plan: plan, - template: phase.template, - question: question, - answer: answer, - guidance_service: guidance_service } %> + <%= render partial: '/phases/guidances_notes', locals: { plan: plan, template: phase.template, question: question, answer: answer, question_guidance: question_guidance, guidance_groups: guidance_groups, base_template_org: base_template_org } %>
<%= raw('
') if i != section.questions.length - 1 %> diff --git a/app/views/phases/_guidances_notes.html.erb b/app/views/phases/_guidances_notes.html.erb index b080f71..9ad6e2a 100644 --- a/app/views/phases/_guidances_notes.html.erb +++ b/app/views/phases/_guidances_notes.html.erb @@ -1,6 +1,7 @@ -<%# locals: { plan, template, question, answer, guidance_service } %> -<% guidances_active = guidance_service.any? %> -<% active_nav = nil %> +<%# locals: { plan, template, question, answer, question_guidance, guidance_groups } %> +<% annotations = question.annotations.where(type: Annotation.types[:guidance]) %> +<% guidance_set = question_guidance[question.id] || {} %> +<% guidances_active = (annotations.present? || guidance_set.length > 0) %>