Commit 2558d21e authored by Taico Aerts's avatar Taico Aerts
Browse files

Merge branch 'development' into 'master'

Project Forum Release 2.8.2

See merge request !787
parents ad6620da 83b1a74b
......@@ -13,6 +13,9 @@ include:
- template: Dependency-Scanning.gitlab-ci.yml
- template: Security/SAST.gitlab-ci.yml
variables:
SAST_EXCLUDED_ANALYZERS: 'brakeman,bundler-audit'
license_scanning:
variables:
USE_FREEDESKTOP_PLACEHOLDER: "true"
......
$(document).on('turbolinks:load', function(){
var typingTimer;
var typingInterval = 200;
var $input = $('.register #user_email');
var $preview = $('#email-preview');
var $panel = $preview.parent().parent();
var $spinner = $panel.find('.spinner-container');
var loadingAnimation = {
start: function() {
$spinner.height($spinner.parent().height() + 2 * parseInt($spinner.parent().css('padding-bottom')));
$spinner.show();
},
stop: function() {
$spinner.hide();
}
}
$input.on('keyup keydown', function() {
resetTypingTimer();
loadingAnimation.start();
});
function resetTypingTimer() {
clearTimeout(typingTimer);
typingTimer = setInterval(refreshPreview, typingInterval);
}
function refreshPreview() {
clearTimeout(typingTimer);
$.ajax({
url: $input.attr('data-url'),
data: {
email: $input.val()
},
type: 'post',
dataType: "script",
success: loadingAnimation.stop
});
}
if ($input.length > 0) {
refreshPreview();
}
});
......@@ -31,7 +31,7 @@
}
.login--wide {
max-width: 840px; margin: 15vh auto 0 auto;
max-width: 630px; margin: 15vh auto 0 auto;
}
.login {
......
......@@ -108,6 +108,23 @@
.pno { padding: 0; }
.mno { margin: 0; }
.d-flex {
display: flex;
}
.justify-content-space-between {
justify-content: space-between;
}
.flex-column {
flex-direction: column;
}
.row-equal-height {
display: flex;
flex-wrap: wrap;
}
/* --------------
BORDER
-------------- */
......@@ -197,6 +214,7 @@
.txt-strong, .txt-bold { font-weight:bold; }
.txt-italic { font-style: italic; }
.txt-underline { text-decoration: underline; }
.txt-uppercase{ text-transform: uppercase; }
.txt-lowercase{ text-transform: lowercase; }
......
......@@ -77,7 +77,6 @@ module Admin
@course_edition = old_course_edition.dup
@course_edition.configuration = old_course_edition.configuration.dup
@course_edition.name += ' - Copy'
@course_edition.status = :hidden
old_course_edition.deadlines.each do |deadline|
......@@ -100,6 +99,15 @@ module Admin
end
@override_staff_roles[staffmember] = r
end
if params[:active_period].nil?
@course_edition.name += ' - Copy'
else
@course_edition.name += " - #{Period.find_by(id: params[:active_period]).name}"
@course_edition.active_period_id = params[:active_period]
@course_edition.recurrent = false
@course_edition.recurrent_edition_id = old_course_edition.id
end
end
def update
......
......@@ -89,7 +89,7 @@ module Admin
def programme_params
params.require(:programme).permit(
%i[name sso_name] + [configuration_attributes: %i[course_name group_name project_name company_name]]
%i[name sso_name] + [configuration_attributes: %i[course_name group_name student_group_name project_name company_name]]
)
end
end
......
......@@ -49,8 +49,26 @@ module Companies
protected
def invite_existing_user(email)
user = User.find_by email: email
# do not create a new invitation if the user is an unconfirmed employee
if user.has_role?(:unconfirmed_employee, @company)
User.transaction do
user.remove_role :unconfirmed_employee, @company
user.add_role :employee, @company
end
flash[:success] = 'Employee successfully confirmed.'
return
end
# do not create a new invitation if the user is already an employee
if user.has_role?(:employee, @company) || user.has_role?(:registrar, @company)
flash[:danger] = 'This user is already an employee.'
return
end
@role_invitation = RoleInvitation.new role_invitation_params
@role_invitation.user = User.find_by email: email
@role_invitation.user = user
@role_invitation.save
if @role_invitation.persisted?
......
......@@ -82,9 +82,13 @@ class CompaniesController < OfferersController
def join
@company = Company.find(params[:company])
if @company.allow_to_join
current_user.add_role :unconfirmed_employee, @company
if current_user.role_invitations.where(resource: @company, name: :employee).none?
current_user.add_role :unconfirmed_employee, @company
else
current_user.add_role :employee, @company
end
end
if current_user.has_role? :unconfirmed_employee, @company
if current_user.has_role?(:unconfirmed_employee, @company) || current_user.has_role?(:employee, @company)
flash[:success] = 'Successfully joined company'
else
flash[:danger] = 'Failed to join company'
......@@ -92,25 +96,14 @@ class CompaniesController < OfferersController
redirect_back(fallback_location: offerer_url(@company.acting_as), allow_other_host: false)
end
def search
matching_companies = Company.without_role(%i[registrar employee unconfirmed_employee], current_user)
unless params[:name].empty?
matching_companies = matching_companies.where(id: Company.search(params[:name]).records.ids)
end
respond_to do |format|
format.json { render json: matching_companies }
end
end
protected
def page_title
case action_name
when 'show', 'edit'
"#{@company.name} - Companies - #{super}"
"#{@company.name} - #{current_user.companies_name.capitalize} - #{super}"
else
"Companies - #{super}"
"#{current_user.companies_name.capitalize} - #{super}"
end
end
......
......@@ -10,6 +10,8 @@ module ParamsConcerns
enrolment_starts_at enrolment_ends_at
offering_projects_starts_at offering_projects_ends_at
preference_ends_at proposal_template
active_period_id
recurrent recurrent_edition_id
].freeze
##
......@@ -19,6 +21,7 @@ module ParamsConcerns
BASE + [
deadlines_attributes: ParamsConcerns::Deadlines::BASE + %i[id _destroy],
course_specific_roles_attributes: ParamsConcerns::CourseSpecificRoles::BASE + %i[id _destroy],
projects_attributes: ParamsConcerns::Projects::BASE + %i[id _destroy],
periods_attributes: ParamsConcerns::Periods::BASE + %i[id _destroy],
configuration_attributes: \
ParamsConcerns::CourseConfigurations::BASE + %i[id]
......
......@@ -10,6 +10,7 @@ module ParamsConcerns
:offerer_id,
:course_edition_id,
:company_contact,
:projects_modify,
{ period_ids: [] },
{ tag_ids: [] }].freeze
......
module PreferenceMapper
# Copies project preferences from the given user for the source course edition to the target course edition.
# Both editions should have been created from a recurring course edition.
# Projects are matched through their original project (recurrent course edition). Projects that don't appear in the target are excluded.
# Priorities are remapped such that they all appear at the top of the list, while new projects appear with a "new" tag.
#
# @param user [User]
# @param source_ce [CourseEdition] source course edition
# @param target_ce [CourseEdition] target course edition
def copy_priorities(user, source_ce, target_ce)
if user.project_preferences.where(course_edition_id: target_ce).any?
# Ask for confirmation? Already preferences present.
return false
end
# We need to reassign priorities for the course edition, they start at 1.
priority = 1
# All the preferences in the source edition which match in original_project_id with projects in the target edition
user.project_preferences
.where(course_edition_id: source_ce)
.joins(:project)
.where(projects: { original_project_id: target_ce.projects.select(:original_project_id) })
.order(:priority)
.each do |pp|
# Find the copy of the project in the target course edition
project = pp.project.original_project.copies.find_by(course_edition_id: target_ce)
# Create a copy
ProjectPreference.create(user_id: user.id, project_id: project.id, course_edition_id: target_ce.id, priority: priority)
# Increment the priority
priority += 1
end
end
# Copies project interests from the given user for the source course edition to the target course edition.
# Both editions should have been created from a recurring course edition.
# Projects are matched through their original project (recurrent course edition). Projects that don't appear in the target are excluded.
#
# @param user [User]
# @param source_ce [CourseEdition] source course edition
# @param target_ce [CourseEdition] target course edition
def copy_project_interests(user, source_ce, target_ce)
user.project_interests
.joins(:project)
.where(projects: { course_edition_id: source_ce })
.where(projects: { original_project_id: target_ce.projects.select(:original_project_id) })
.each do |pi|
# Find the copy of the project in the target course edition
project = pi.project.original_project.copies.find_by(course_edition_id: target_ce)
# Create a copy
ProjectInterest.create(user_id: user.id, project_id: project.id)
end
end
end
module CourseEditions
class ProjectPreferencesController < ApplicationController
include CourseEditionAccessible
include PreferenceMapper
load_and_authorize_resource
......@@ -22,6 +23,12 @@ module CourseEditions
# If the user has any preferences stored
@any_preferences = current_user.project_preferences?(@course_edition)
@other_preferences = if @course_edition.recurrent_edition.nil?
false
else
ProjectPreference.where(user: current_user,
course_edition: CourseEdition.descendants_of(@course_edition.recurrent_edition)).any?
end
@project_name = @course_edition.configuration.project_name_text(false, false)
@projects_name = @course_edition.configuration.project_name_text(false, true)
......@@ -30,6 +37,11 @@ module CourseEditions
@projects = @course_edition.approved_projects(current_user)
end
def copy
copy_priorities(current_user, CourseEdition.find_by(id: params[:previous_course_edition]), @course_edition)
redirect_to course_edition_preferences_path(@course_edition)
end
protected
def page_title
......
module CourseEditions
class ProjectsController < ApplicationController
include CourseEditionAccessible
include PreferenceMapper
# Load the project
before_action :load_project, only: %i[show update destroy]
......@@ -46,22 +47,38 @@ module CourseEditions
@visible_project_count = @possible_projects.count
@page_count = 25
@any_interests = current_user.project_interests?(@course_edition)
@other_interests = if @course_edition.recurrent_edition.nil?
false
else
ProjectInterest.joins(:project)
.where(user: current_user,
projects: {
course_edition_id: CourseEdition.descendants_of(@course_edition.recurrent_edition)
})
.any?
end
apply_search
apply_filters
apply_ordering
paginate_projects
end
def copy_interests
copy_project_interests(current_user, CourseEdition.find_by(id: params[:previous_course_edition]), @course_edition)
redirect_to course_edition_projects_path(@course_edition)
end
protected
def page_title
case action_name
when 'index'
"Projects - #{@course_edition.display_name} - #{super}"
"Proposals - #{@course_edition.display_name} - #{super}"
when 'show'
"#{@project.name} - Projects - #{super}"
"#{@project.name} - Proposals - #{super}"
else
"Projects - #{super}"
"Proposals - #{super}"
end
end
......
......@@ -12,7 +12,7 @@ class CoursesController < ApplicationController
def show
add_breadcrumb @course.name
@course_editions = @course.editions.accessible_by(current_ability, :read)
@course_editions = @course.editions.accessible_by(current_ability, :read).non_recurrent
end
protected
......
......@@ -8,11 +8,13 @@ class GenericProjectsController < ProjectsController
authorize_resource :project, parent: false
before_action do
add_breadcrumb 'My Projects', generic_projects_path
add_breadcrumb 'My Proposals', generic_projects_path
end
before_action only: %i[show edit] do
add_breadcrumb @project.name, project_path(@project)
@project_name = @project.course_edition.configuration.project_name_text(false, false)
end
before_action :load_course_selection, only: %i[new create edit update]
......@@ -66,6 +68,7 @@ class GenericProjectsController < ProjectsController
# Allow selecting previously selected course edition in edits
@possible_course_editions = CourseEdition.where(id: @possible_course_editions)
.or(CourseEdition.where(id: @project.course_edition_id))
.standalone
.relevance_ordered
@possible_courses = Course.where(id: @possible_course_editions.map(&:course_id)).sort_by_name
end
......@@ -150,23 +153,29 @@ class GenericProjectsController < ProjectsController
end
def update
@project = @specific_project.acting_as
edition = @project.course_edition
role_hash = create_role_hash(edition)
main_specific_project = @specific_project
@role_creation_failed = false
update_failed = false
# If recurrent course edition, also apply update to each selected project
if @specific_project.course_edition.recurrent
Project.where(id: params[:generic_project][:projects_modify]).each do |project|
unless apply_update(project.specific)
update_failed = true
break
end
end
end
begin
fill_role_assignments(role_hash, params[:generic_project])
rescue ArgumentError => e
flash[:danger] = e.message
if update_failed
render 'edit'
return
end
if update_project(project_update_params)
# assign course-specific roles
role_creation_failed = assign_course_specific_roles(role_hash)
if role_creation_failed
@specific_project = main_specific_project
@project = @specific_project.acting_as
if apply_update(main_specific_project)
if @role_creation_failed
flash[:danger] = I18n.t 'activerecord.flashes.project.updated.role_failed'
end
......@@ -184,12 +193,33 @@ class GenericProjectsController < ProjectsController
protected
def apply_update(specific_project)
@specific_project = specific_project
@project = @specific_project.acting_as
authorize! :update, @project
role_hash = create_role_hash(@project.course_edition)
begin
fill_role_assignments(role_hash, params[:generic_project])
rescue ArgumentError => e
flash[:danger] = e.message
return false
end
if update_project(project_update_params)
@role_creation_failed |= assign_course_specific_roles(role_hash)
true
else
false
end
end
def page_title
case action_name
when 'show', 'edit'
"#{@project.name} - Projects - #{super}"
"#{@project.name} - Proposals - #{super}"
else
"Projects - #{super}"
"Proposals - #{super}"
end
end
......
class GroupsController < ApplicationController
skip_authorization_check
add_breadcrumb 'My Groups'
before_action do
add_breadcrumb "My #{current_user.groups_name.capitalize}"
end
def index
@groups = current_user.groups.order(created_at: :desc).limit(100)
......@@ -14,6 +16,6 @@ class GroupsController < ApplicationController
protected
def page_title
"Groups - #{super}"
"#{current_user.groups_name.capitalize} - #{super}"
end
end
......@@ -212,7 +212,9 @@ class ProjectsController < ApplicationController
end
# Sort them by relevance
@possible_course_editions = @possible_course_editions.relevance_ordered
@possible_course_editions = @possible_course_editions
.standalone
.relevance_ordered
# Also load courses
@possible_courses = Course.where(id: @possible_course_editions.map(&:course_id)).sort_by_name
......@@ -336,6 +338,7 @@ class ProjectsController < ApplicationController
next if csr.assignment_rule == 'not_specifiable_by_client'
role_hash[csr.id.to_s].each do |user_csr|
user_csr.resource_id = @project.id
user_csr.course_specific_role = csr
unless user_csr.save
......
......@@ -31,13 +31,13 @@ class UserProgrammesController < ApplicationController
url = params[:cont_url]
if url.blank?
# If there is no continuation url, redirect to user_path
redirect_to user_path(current_user)
# If there is no continuation url, redirect to dashboard
redirect_to root_path
elsif url.start_with?('/')
begin
refer_url = URI.parse(url).path
rescue URI::InvalidURIError
refer_url = user_path(current_user)
refer_url = root_path
end
# If the url starts with / then we trust it and we can redirect to it
......
module Users
class RegistrationPreviewsController < ActionController::Base
protect_from_forgery with: :exception
def create
@email = params[:email]
@user = User.new(email: @email)
respond_to do |format|
format.js
end
end
protected
def devise_controller
true
end
end
end
......@@ -365,7 +365,6 @@ class Ability
# Project
# Students can modify groups of approved projects if their enrolment is approved
can :index, Project
can :modify, Project, id: user.student_approved_projects.ids
# Group
......@@ -381,6 +380,9 @@ class Ability
can :create, RoleInvitation
can :invite_to, Group, id: user.leader_groups.ids
can :manage, RoleInvitation.manageable_by(user)
can :copy, ProjectPreference
can :copy_interests, Project
end
# The rights to view the content of a set of course editions.
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment