Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Changelog

- Redesigned plan-creation page to enforce template access rules and simplify UI [#3534](https://github.com/DMPRoadmap/roadmap/issues/3534)

## v5.0.2
- Bump Ruby to v3.1.4 and use `.ruby-version` in CI
- [#3566](https://github.com/DMPRoadmap/roadmap/pull/3566)
Expand Down
52 changes: 43 additions & 9 deletions app/assets/stylesheets/blocks/_forms.scss
Original file line number Diff line number Diff line change
@@ -1,21 +1,55 @@
@use '../variables/colours' as *;

.form-control {
border: 0px;
}

.form-control input, .form-control textarea, .form-control select{
border: 2px solid $color-border-light;

.form-control input,
.form-control textarea,
.form-control select {
border: 2px solid $color-border-light;
}

.form-check {
padding-left: 0rem;
padding-left: 0rem;
}

.form-inline{
margin-bottom: 5px;
.form-inline {
margin-bottom: 5px;
}

.form-check-label {
padding-left: 5px;
padding-left: 5px;
}

// Added for new plan create page app/views/plans/new.html.erb
.roadmap-form {
margin-bottom: 1rem;

legend {
padding-inline: 2px;
float: none;
width: auto;
padding: 0.5rem;
font-size: 1rem;
background-color: $color-primary-background;
color: $color-primary-text;
}

fieldset {
border: 2px solid #555555;
padding: 1rem;
margin-bottom: 1rem;
}

.form-row {
margin-bottom: 1rem;
}

.form-row:last-child {
margin-bottom: 0;
}

.form-check .form-check-input {
float: left;
margin-left: 0.5rem;
}
}
67 changes: 49 additions & 18 deletions app/controllers/plans_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,31 +29,62 @@ def index
# rubocop:enable Metrics/AbcSize

# GET /plans/new
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
def new
@plan = Plan.new
authorize @plan

# Get all of the available funders and non-funder orgs
@funders = Org.funder
.includes(identifiers: :identifier_scheme)
.joins(:templates)
.where(templates: { published: true }).uniq.sort_by(&:name)
@orgs = (Org.includes(identifiers: :identifier_scheme).organisation +
Org.includes(identifiers: :identifier_scheme).institution +
Org.includes(identifiers: :identifier_scheme).default_orgs)
@orgs = @orgs.flatten.uniq.sort_by(&:name)

@plan.org_id = current_user.org&.id
# looks into the list of families of templates
customizations = Template.latest_customized_version_per_org(@plan.org_id)
customization_ids = customizations.select(&:published?).collect(&:customization_of)

# get templates of user's own org
user_org_own_templates = Template.organisationally_visible
.where(org_id: @plan.org_id, customization_of: nil)
.published
.uniq.sort_by(&:title)

# get templates of user's customised org
user_org_custom_templates = Template.latest_customized_version_per_org(@plan.org_id)
.published
.uniq.sort_by(&:title)

# get funder templates no customised templates
funder_non_customised_templates = Template.published
.joins(:org)
.where(orgs: { org_type: Org.org_type_values_for(:funder) })
# The next line removes templates that belong to a family that
# has customised templates
.where.not(family_id: customization_ids)
.uniq.sort_by(&:title)

# get global templates
global_templates = Template.published
.where(is_default: true)
# The next line removes templates that belong to a family that
# has customised templates
.where.not(family_id: customization_ids)
.uniq.sort_by(&:title)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The newly added commits appear to address some of my earlier concerns regarding duplicate templates, so thank you for that.

That said, I still notice that the upgrade_customization? handling from TemplateOptionsController isn’t being used here, and it’s unclear what other behavioural differences now exist compared to that controller.

I am wondering if, instead of a complete rework of the template selection logic, we could instead pass user.org to TemplateOptionsController. And if there is in fact any required behavioural changes (e.g. always return default/global templates, grouping the templates into the three categories, etc.) then the code could be changed there. Re-using the existing logic could also help mitigate any further regressions or follow-up fixes, and keep template-selection behaviour consistent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will look at this.

# create templates-grouped hash
@templates_grouped = {
_("Your Organisation's Templates:") => user_org_own_templates.map do |t|
[t.title, t.id]
end,
_("Your Organisation's Customised Templates:") => user_org_custom_templates.map do |t|
[t.title, t.id]
end,
_('Global Templates:') => global_templates.map do |t|
[t.title, t.id]
end,
_('Funder Templates:') => funder_non_customised_templates.map do |t|
[t.title, t.id]
end
}.reject { |_, val| val.empty? }

# TODO: is this still used? We cannot switch this to use the :plan_params
# strong params because any calls that do not include `plan` in the
# query string will fail
flash[:notice] = "#{_('This is a')} <strong>#{_('test plan')}</strong>" if params.key?(:test)
@is_test = params[:test] ||= false
respond_to :html
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

# POST /plans
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
Expand Down
201 changes: 83 additions & 118 deletions app/views/plans/new.html.erb
Original file line number Diff line number Diff line change
@@ -1,125 +1,90 @@
<% title _('Create a new plan') %>
<% required_project_title_tooltip = _('This field is required.') %>
<% project_title_tooltip = _('If applying for funding, state the project title exactly as in the proposal.') %>
<% required_research_org_tooltip = _('You must select a research organisation from the list or click the checkbox.') %>
<% research_org_tooltip = _('Please select a valid research organisation from the list.') %>
<% required_primary_funding_tooltip = _('You must select a funder from the list or click the checkbox.') %>
<% primary_funding_tooltip = _('Please select a valid funding organisation from the list.') %>

<div class="row">
<div class="col-md-12">
<h1><%= _('Create a new plan') %></h1>

<p class="start-indent">
<%= _("Before you get started, we need some information about your research project to set you up with the best DMP template for your needs.") %>
</p>
<form action="<%= plans_path %>" method="post" class="roadmap-form">
<div class="row form-row">
<div class="col-12">
<h1><%= _('Create a new plan') %></h1>
<p>
<%= _("Before you get started, we need some information about your research project to set you up with the best DMP template for your needs.") %>
</p>
</div>
</div>
</div>

<div class="row">
<div class="col-md-12">
<%= form_for Plan.new, url: plans_path do |f| %>
<!-- Plan name section -->
<h2 id="project-title"><span class="red" title="<%= required_project_title_tooltip %>">*<em class="sr-only"><%= required_project_title_tooltip %></em> </span><%= _('What research project are you planning?') %></h2>
<div class="row">
<div class="form-control mb-3 col-md-8">
<%= f.text_field(:title, class: 'form-control', 'aria-labelledby': 'project-title', 'aria-required': 'true', 'aria-label': 'project-title',
'data-toggle': 'tooltip',
'data-placement': 'bottom',
spellcheck: true,
title: project_title_tooltip ) %>
</div>
<div class="form-control mb-3 col-md-6">
<div class="checkbox">
<%= label_tag(:is_test) do %>
<%= check_box_tag(:is_test, "1", false) %>
<%= _('mock project for testing, practice, or educational purposes') %>
<% end %>
</div>
</div>
<fieldset>
<div class="row form-row">
<div class="col-12">
<!-- Rails form authenticity token -->
<%= hidden_field_tag :authenticity_token, form_authenticity_token %>
<label for="plan_title" id="project-title" class="form-label">
<%= _('Enter project title') %>
</label>
<input type="text"
id="plan_title"
name="plan[title]"
class="form-control"
required
aria-labelledby="project-title"
aria-required="true"
aria-label="project-title"
data-toggle="tooltip"
data-placement="bottom"
spellcheck="true">
</div>

<!-- Organisation selection -->
<h2 id="research-org">
<span class="red" title="<%= required_research_org_tooltip %>">*<em class="sr-only"><%= required_research_org_tooltip %></em> </span>
<%= _('Select the primary research organisation') %>
</h2>
<div id="research-org-controls" class="row">
<div class="form-control mb-3 col-md-6">
<em class="sr-only"><%= research_org_tooltip %></em>
<% dflt = @orgs.include?(current_user.org) ? current_user.org : nil %>
<%= f.fields_for :org, @plan.org do |org_fields| %>
<%= render partial: "shared/org_selectors/local_only",
locals: {
form: org_fields,
id_field: :id,
default_org: dflt,
orgs: @orgs,
required: false
} %>
<% end %>
</div>
<div class="col-md-3 text-center"><strong>- <%= _('or') %> -</strong></div>
<div class="form-control mb-3 col-md-3">
<div class="form-check">
<% primary_research_org_message = _('No research organisation associated with this plan or my research organisation is not listed') %>
<%= label_tag(:plan_no_org) do %>
<%= check_box_tag(:plan_no_org, "0", false, class: "toggle-autocomplete") %>
<%= primary_research_org_message %>
<% end %>
</div>
</div>
<div class="row form-row">
<div class="col-12">
<div class="form-check">
<input type="checkbox"
class="form-check-input"
id="is_test"
value="1">
<label class="form-check-label" for="is_test">
<%= _('Mock project for testing, practice, or educational purposes') %>
</label>
</div>
</div>

<!-- Funder selection -->
<h2 id="funder-org"><span class="red" title="<%= required_primary_funding_tooltip %>">* <em class="sr-only"><%= required_primary_funding_tooltip %></em> </span><%= _('Select the primary funding organisation') %></h2>
<div id="funder-org-controls" class="row">
<div class="form-control mb-3 col-md-6">
<em class="sr-only"><%= primary_funding_tooltip %></em>
<%= f.fields_for :funder, @plan.funder = Org.new do |funder_fields| %>
<%= render partial: "shared/org_selectors/local_only",
locals: {
form: funder_fields,
id_field: :id,
label: _("Funder"),
default_org: nil,
orgs: @funders,
required: false
} %>
<% end %>
</div>
<div class="col-md-3 text-center"><strong>- <%= _('or') %> -</strong></div>
<div class="form-control mb-3 col-md-3">
<div class="form-check">
<% primary_funding_message = _('No funder associated with this plan or my funder is not listed') %>
<%= label_tag(:plan_no_funder) do %>
<%= check_box_tag(:plan_no_funder, "0", false, class: "toggle-autocomplete") %>
<%= primary_funding_message %>
</div>
</div>
</fieldset>
<fieldset class="form-group">
<legend>
<%= _('Select a DMP template') %>
</legend>
<div class="row form-row">
<div class="col-12">
<% @templates_grouped.each do |group_label, group_templates| %>
Copy link
Contributor

@aaronskiba aaronskiba Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the label grouping. Maybe we should also be ordering the templates by title?

<div class="mb-3">
<div class="form-label fw-bold">
<%= group_label %>
</div>
<% group_templates.each do |template_title, template_id| %>
<div class="form-check">
<input
class="form-check-input"
type="radio"
id="template_<%= template_id %>"
name="plan[template_id]"
value="<%= template_id %>"
required
aria-labelledby="template_<%= template_id %>"
>
<label
class="form-check-label fw-normal"
id="template_<%= template_id %>"
for="template_<%= template_id %>"
>
<%= template_title %>
</label>
</div>
<% end %>
</div>
</div>
</div>
<% end %>

<!-- Template selection -->
<div id="available-templates" style="visibility: none;">
<%= hidden_field_tag 'template-option-target', template_options_path %>
<h2 id="template-selection"><%= _('Which DMP template would you like to use?') %></h2>
<div class="form-control mb-3 row">
<div class="col-md-6">
<%= select_tag(:plan_template_id, "<option value=\"\">#{_('Please select a template')}</option>", name: 'plan[template_id]',
class: 'form-select', 'aria-labelledby': 'template-selection') %>
</div>
<div class="col-md-6">
<span id="multiple-templates">
<%= _('We found multiple DMP templates corresponding to your funder.') %>
</span>
</div>
</div>
</div>

<%= f.hidden_field(:visibility, value: @is_test ? 'is_test' : Rails.configuration.x.plans.default_visibility) %>
<%= f.button(_('Create plan'), class: "btn btn-primary", type: "submit") %>
<%= link_to _('Cancel'), plans_path, class: 'btn btn-secondary' %>
<% end %>
</div>
</div>
</fieldset>
<div class="row form-row">
<div class="col-12">
<button type="submit" class="btn btn-primary">
<%= _('Create') %>
</button>
<%= link_to _('Cancel'), plans_path, class: 'btn btn-secondary' %>
</div>
</div>
</div>
</form>
Loading