Skip to content
Open
Show file tree
Hide file tree
Changes from all 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;
}
}
48 changes: 30 additions & 18 deletions app/controllers/plans_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,31 +29,43 @@ def index
# rubocop:enable Metrics/AbcSize

# GET /plans/new
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
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
# get funder templates
funder_templates = Template.published
.joins(:org)
.merge(Org.funder)
.distinct

# get global templates
global_templates = Template.published
.where(is_default: true)
.distinct

# get templates of user's org
user_org_templates = Template.published
.where(org: current_user.org)
.distinct
Comment on lines +37 to +51
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm comparing this simplified logic with what we were previously using (https://github.com/DMPRoadmap/roadmap/blob/main/app/controllers/template_options_controller.rb).

One thing I am concerned about with this new logic is the same template being listed in two, or all three sections. For example, what if Org.funder has a default template (which is the case with DMP Assistant). Then that same template is listed in both funder_templates and global_templates. And if user.org == Org.funder (again the case with DMP Assistant), then funder_templates and user_org_templates will have identical lists.

TemplateOptionsController also performed special handling for template customisations. Specifically, if an org had a customisation of the most recent version of a funder_template, then only that customisation would appear as an template option. With this logic, it looks like the most recent customisation will appear in user_org_templates, but the non-customised version of it will also be listed as an option in funder_templates.

Copy link
Contributor

Choose a reason for hiding this comment

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

If we are no longer fetching all of the possible org options, maybe PlansController#new should use the existing logic in TemplateOptionsController?


# create templates-grouped hash
@templates_grouped = {
_("Your Organisation's Templates:") => user_org_templates.map do |t|
[t.title, t.id]
end,
_('Global Templates:') => global_templates.map do |t|
[t.title, t.id]
end,
_('Funder Templates:') => funder_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

# 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