Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
7276811
feat: adjust in_use validation
mirele-ufc Nov 28, 2025
4285b1b
feat: create used_by_multiple_exams validation in question.rb
mirele-ufc Nov 28, 2025
9874e05
feat: create can_questions_be_edited? validation in exams.rb
mirele-ufc Nov 28, 2025
87e156d
feat: adjust can_questions_be_edited? validation in exams.rb
mirele-ufc Nov 28, 2025
98f5fc2
feat: adjust can_change? on question.rb with repository workflow
mirele-ufc Nov 28, 2025
57c794f
feat: adjust can_change? on question.rb with repository workflow
mirele-ufc Dec 3, 2025
b7defed
feat: add edit verifications in questions_controller.rb
mirele-ufc Dec 3, 2025
0378705
test: add test messages
mirele-ufc Dec 3, 2025
6d83ce6
doc: adjust code documentation
mirele-ufc Dec 4, 2025
2ec4c14
feat: adjust error message when copy question
mirele-ufc Dec 4, 2025
8dbebe5
feat: adjust error message when copy question in a started exam
mirele-ufc Dec 4, 2025
2623d7e
feat: adjust update permissions and remove console messages
mirele-ufc Dec 5, 2025
6523025
feat: create send_email_to_responsible_users function
mirele-ufc Dec 8, 2025
4da7e7d
feat: create notify_responsibles function
mirele-ufc Dec 8, 2025
15ed635
feat: adjust verify_edit_permission function on question.rb
mirele-ufc Dec 8, 2025
055f518
feat: adjust verify_owners function
mirele-ufc Dec 8, 2025
d51f8c3
feat: add e-mail notification
mirele-ufc Dec 8, 2025
942fcbf
feat: add e-mail notification
mirele-ufc Dec 8, 2025
35c76d7
feat: create very_edit_permissions route
mirele-ufc Dec 9, 2025
6f8dfdc
feat: add exam.nil? verification in verify_edit_permission function
mirele-ufc Dec 9, 2025
8a17a8c
feat: adjust questions.js.erb and questions_controller.rb to verify_e…
mirele-ufc Dec 9, 2025
9d055e7
doc: adjust function documentation
mirele-ufc Dec 9, 2025
9860f7a
fix: remove correction info from repository page
mirele-ufc Dec 10, 2025
17376d6
feat: adjust label input and delete button behavior on add/edit quest…
mirele-ufc Dec 10, 2025
86cc7b3
fix: adjust correction_info behavior
mirele-ufc Dec 10, 2025
d7404eb
refactor: adjust item data-tooltip
mirele-ufc Dec 10, 2025
e4d744e
refactor: adjust delete button on form question
mirele-ufc Dec 10, 2025
68e7b0b
Merge branch 'development' into allow-edit-in-use-questions-ii
mirele-ufc Dec 10, 2025
b840dbd
refactor: adjust _image.html.haml layout
mirele-ufc Dec 11, 2025
2df0cb7
refactor: adjust _audio.html.haml layout
mirele-ufc Dec 11, 2025
02ae2bb
chore: minor layout corrections
mirele-ufc Dec 11, 2025
13169f7
feat: create verify_edit_permissions route
mirele-ufc Dec 15, 2025
755fb37
feat: adjust verify_edit_permission to include exam_question params
mirele-ufc Dec 15, 2025
7215d35
feat: implement edit permissions on controller e js file
mirele-ufc Dec 15, 2025
b4a2b98
refactor: adjust question_controller veryfi_edit_permissions function…
mirele-ufc Dec 16, 2025
b6f470c
chore: add alert messages to internationalization
mirele-ufc Dec 16, 2025
0e02e25
chore: adjust edit_question function on js
mirele-ufc Dec 16, 2025
ce412ab
feat: add fork_copy and copy_repository logic
mirele-ufc Dec 16, 2025
2366496
feat: implement only points logic
mirele-ufc Dec 16, 2025
afe747b
refactor: remove console messages
mirele-ufc Dec 16, 2025
7dd4fab
doc: adjust verify_edit_permission documentation
mirele-ufc Dec 16, 2025
2e90b7e
Merge branch 'development' into allow-edit-in-use-questions-ii
mirele-ufc Dec 16, 2025
d6e0c41
chore: minor code adjusts
mirele-ufc Dec 17, 2025
89e38e5
Merge branch 'development' into allow-edit-in-use-questions-ii
mirele-ufc Dec 17, 2025
74222c4
chore: minor code adjusts
mirele-ufc Dec 17, 2025
2a13e05
fix: solve render form error
mirele-ufc Dec 18, 2025
cdae5ac
fix: solve copy status error
mirele-ufc Dec 18, 2025
fc6a788
Merge branch 'development' into allow-edit-in-use-questions-ii
mirele-ufc Dec 18, 2025
158437b
fix: adjust only_points logic error
mirele-ufc Dec 18, 2025
db21651
fix: adjust score render error
mirele-ufc Dec 18, 2025
2c7b85b
refactor: adjust fork_copy logic
mirele-ufc Dec 18, 2025
9c761fa
feat: add email notification in exam_question controller
mirele-ufc Dec 18, 2025
937dd52
refactor: adjust fork_copy logic
mirele-ufc Dec 22, 2025
d114b93
refactor: adjust _item media forms
mirele-ufc Jan 7, 2026
74c8815
refactor: add arrow behavior to info page.
mirele-ufc Jan 7, 2026
b9e9090
refactor: minor image and audio forms adjusts
mirele-ufc Jan 7, 2026
700b51c
chore: remove new question form layout editions
mirele-ufc Jan 7, 2026
4ac2982
chore: do not show correction_info if question_type persisted
mirele-ufc Jan 8, 2026
9d2e4cc
fix: solve error when question was copied only to the repository
mirele-ufc Jan 9, 2026
9d049bc
chore: adjust exam_question can_save? function
mirele-ufc Jan 12, 2026
865b5b1
feat: adjust fork_copy implementation
mirele-ufc Jan 12, 2026
0e6f87a
chore: minor adjusts in alert messages
mirele-ufc Jan 13, 2026
68e4c59
chore: adjusts exam_question.rb verifications
mirele-ufc Jan 13, 2026
184023e
fix: fix error when create new exam_question
mirele-ufc Jan 13, 2026
530d3af
Merge branch 'development' into allow-edit-in-use-questions-ii
mirele-ufc Jan 14, 2026
3760291
chore: minor layout adjusts
mirele-ufc Jan 14, 2026
b25a4a9
chore: add verification in question in ended exams
mirele-ufc Jan 14, 2026
df01a72
chore: adjust cancel button response in can_fork_copy data.action
mirele-ufc Jan 14, 2026
b0b999f
chore: correct verification when the question has many exams and it i…
mirele-ufc Jan 14, 2026
229667d
chore: remove unused code
mirele-ufc Jan 14, 2026
4810b5c
chore: adjust raise message
mirele-ufc Jan 14, 2026
b081d16
chore: add new raise message to verify_edit_permission function
mirele-ufc Jan 15, 2026
90439e7
refactor: adjust edition permission in exams screen
mirele-ufc Jan 15, 2026
ead7291
fix: remove unused variable
mirele-ufc Jan 15, 2026
4972383
feat: add internationalization to score fancybox title
mirele-ufc Jan 20, 2026
f5fb1d8
Merge branch 'development' into allow-edit-in-use-questions-ii
mirele-ufc Jan 26, 2026
05ba7da
Merge branch 'development' into allow-edit-in-use-questions-ii
mirele-ufc Feb 23, 2026
b96ab92
Merge branch 'development' into allow-edit-in-use-questions-ii
biancastephani Apr 7, 2026
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
141 changes: 105 additions & 36 deletions app/assets/javascripts/questions.js.erb
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ function back(){
$.fancybox.update();
$('.block_content').css('width', 'auto');
}

function back_medias(){
go_to_medias();
}
Expand Down Expand Up @@ -245,56 +246,106 @@ function edit_question(icon, replace_list){
return false;
}

var questions_ids;
var ids;

if(replace_list != '.list_exams'){
var questions_ids = $('.ckb_question:checked', $(icon).parents("div.list_questions")).map(function() { return this.value; }).get();
var ids = questions_ids;
questions_ids = $('.ckb_question:checked', $(icon).parents("div.list_questions")).map(function() { return this.value; }).get();
ids = questions_ids
}else{
var ids = $('.ckb_question:checked', $(icon).parents("[id^='exam_']")).map(function() { return this.value; }).get();
var questions_ids = $('.ckb_question:checked', $(icon).parents("[id^='exam_']")).map(function() { return $(this).data('question-id'); }).get();
var exam_id = $('.ckb_question:checked', $(icon).parents("[id^='exam_']")).map(function() { return $(this).data('exam-id'); }).get();
ids = $('.ckb_question:checked', $(icon).parents("[id^='exam_']")).map(function() { return this.value; }).get();
questions_ids = $('.ckb_question:checked', $(icon).parents("[id^='exam_']")).map(function() { return $(this).data('question-id'); }).get();
var exam_id = $('.ckb_question:checked', $(icon).parents("[id^='exam_']")).map(function() { return $(this).data('exam-id'); }).get();
}

if (!(!ids.length || ids.length > 1)) {
var base_verify_url = "<%=Rails.application.routes.url_helpers.verify_owners_question_path(':id')%>".replace(':id', questions_ids);
var base_verify_url = "<%=Rails.application.routes.url_helpers.verify_edit_permissions_question_path(':id')%>".replace(':id', questions_ids);

$.get(exam_id ? (base_verify_url + `/${exam_id}`) : base_verify_url, function(data){
var url_edit = $(icon).data('link-to-edit').replace(':id', ids);
$(icon).call_fancybox({
href : url_edit,
open: true,
'autoDimensions': false,
'autoSize': false,
width: '700',
height: 'auto',
//maxHeight: '70%',
beforeClose: function() { clear_ckeditor(); }
});
}).fail(function(data){

var data = $.parseJSON(data.responseText);
if (!confirm(data.alert))
return this;

$.get("<%=Rails.application.routes.url_helpers.copy_verify_owners_question_path(':id')%>".replace(':id', questions_ids), function(data){
var url_copy = $(icon).prev().data('link-to-copy').replace(':id', ids);
$(icon).prev().call_fancybox({ href : url_copy, open: true, maxHeight: '70%', 'autoDimensions': false, 'autoSize': false, width: '700', height: 'auto', });
if(replace_list != ''){
$.get($(replace_list).data("link-list"), function(data2){
$(replace_list).replaceWith(data2);
flash_message(data.notice, 'notice');
update_nice_checkbox();
});
if (data.action === 'only_points') {
if (!confirm(data.alert))
return this;
} else if (data.action === 'can_fork_copy') {
if (confirm(data.alert)) {
data.action = 'only_points';
} else {
if (confirm("<%=I18n.t('questions.error.fork_copy')%>")) {
data.action = 'fork_copy';
}
return this;
}
}).fail(function(data){
var data = $.parseJSON(data.responseText);
if (typeof(data.alert) != "undefined")
flash_message(data.alert, 'alert');
});
}

if (['copy_repository', 'copy', 'fork_copy'].includes(data.action)) {

if (data.action !== 'fork_copy'){
if (!confirm(data.alert))
return this;
}

var url_copy;

if (data.action === 'copy_repository') {
url_copy = "<%=Rails.application.routes.url_helpers.copy_question_path(':id')%>".replace(':id', questions_ids);
} else if (data.action === 'fork_copy') {
url_copy = "<%=Rails.application.routes.url_helpers.fork_copy_exam_question_path(':id')%>".replace(':id', ids);
} else {
url_copy = $(icon).prev().data('link-to-copy').replace(':id', ids);
}

$(icon).prev().call_fancybox({
href : url_copy,
open: true,
maxHeight: '70%',
'autoDimensions': false,
'autoSize': false,
width: '700',
height: 'auto',
beforeClose: function() { clear_ckeditor(); }
});

if(replace_list !== ''){
$.get($(replace_list).data("link-list"), function(data2){
$(replace_list).replaceWith(data2);
flash_message(data.notice, 'notice');
update_nice_checkbox();
});
}
} else {

var url_edit = $(icon).data('link-to-edit').replace(':id', ids) + (data.action === 'only_points' ? "?only_points=true" : "");

if (data.notify) {
if (!confirm("<%=I18n.t('questions.questions.notify_editors_alert')%>")) {
return this;
}
url_edit += "?notify_responsibles=true";
}

$(icon).call_fancybox({
href : url_edit,
open: true,
'autoDimensions': false,
'autoSize': false,
width: '700',
height: 'auto',
beforeClose: function() { clear_ckeditor(); }
});
}
}).fail(function(data){
var data = $.parseJSON(data.responseText);
if (typeof(data.alert) != "undefined")
flash_message(data.alert, 'alert');
});
}else{
flash_message("<%=I18n.t('questions.error.choose_one')%>", "alert");
return false;
}
}

function copy_question(icon, replace_list){

if ($(icon).attr('disabled') == 'disabled'){
flash_message("<%=I18n.t('questions.error.choose_one')%>", "alert");
return false;
Expand Down Expand Up @@ -410,6 +461,24 @@ function expand_or_compress_icon(icon){
}
}

function toggle_alt_info(link){
var parent = $(link).parent();
parent.siblings().focus();
var information = parent.siblings('.alt_info_text')

if(information.hasClass('invisible')){
information.removeClass('invisible')
parent.find('i.icon-arrow-up-triangle').removeClass('invisible');
parent.find('i.icon-arrow-down-triangle').addClass('invisible');
}else{
information.addClass('invisible')
parent.find('i.icon-arrow-up-triangle').addClass('invisible');
parent.find('i.icon-arrow-down-triangle').removeClass('invisible');
}

$(link).focus();
}

function annul(icon){
if ($(icon).prop('disabled'))
return false;
Expand Down
64 changes: 63 additions & 1 deletion app/controllers/exam_questions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,15 @@ def new
def create
@exam_question = ExamQuestion.new exam_question_params
authorize! :create, Question, { on: at_ids = @exam_question.exam.allocation_tags.map(&:id) }
@exam_question.question.user_id = current_user.id

if params[:forked_question_id].present?
@forked_question = ExamQuestion.find(params[:forked_question_id])
@exam_question.question.user_id = @forked_question.question.user_id
@exam_question.question.updated_by_user_id = current_user.id unless current_user.id == @forked_question.question.user_id
@exam_question.question.status = true
else
@exam_question.question.user_id = current_user.id
end

ActiveRecord::Base.transaction do
if !params['question_texts']['text'].strip.blank?
Expand All @@ -37,6 +45,12 @@ def create
end

if @exam_question.save

if @forked_question.present?
log(@forked_question.exam, "question: #{@forked_question.question.id} [moved/deleted after copy] exam: #{@forked_question.exam.id}", LogAction::TYPE[:update]) rescue nil
@forked_question.destroy
end

if @exam_question.exam.questions.size > 1
render partial: 'question', locals: { question: @exam_question.question, exam_question: @exam_question, exam: @exam_question.exam, hide_columns: false, can_see_preview: true }
else
Expand All @@ -60,6 +74,7 @@ def create

def edit
@exam_question = ExamQuestion.find(params[:id])
@only_points = params[:only_points].nil? ? false : true
@question_text = QuestionText.find(@exam_question.question.question_text_id) unless @exam_question.question.question_text_id.blank?
build_exam_question
end
Expand Down Expand Up @@ -89,6 +104,13 @@ def update
end

if @exam_question.update_attributes exam_question_params
if params[:notify_responsibles] == 'true'
message = t('questions.questions.notification', question_enunciation: @exam_question.question.enunciation)
@exam_question.question.exams.each do |exam|
exam.notify_responsibles(message)
end
end

if !params['question_texts_id'].blank? && params['question_texts']['media_question'].to_i == 0
if Question.where(:question_text_id => params['question_texts_id']).count == 0
QuestionText.find(params['question_texts_id']).destroy
Expand All @@ -111,6 +133,28 @@ def update
render_json_error(error, 'questions.error')
end

def fork_copy
authorize! :copy, Question

original_exam_question = ExamQuestion.find params[:id]
original_question = original_exam_question.question
original_question.can_copy?

@new_question = original_question.dup

copy_exam_question_associations(original_question, @new_question)

@exam_question = ExamQuestion.new(original_exam_question.attributes.except('id', 'question_id', 'annulled').merge({ annulled: false }))
@exam_question.question = @new_question
@forked_question = original_exam_question

render :new
rescue CanCan::AccessDenied
render text: t(:no_permission)
rescue => error
render text: t("exam_questions.errors.#{error}")
end

def remove_file_item
authorize! :update, Question

Expand Down Expand Up @@ -401,6 +445,24 @@ def log(object, message, type=LogAction::TYPE[:update])
end
end

def copy_exam_question_associations(source_question, target_question)
source_question.question_items.each do |item|
target_question.question_items << item.dup
end

source_question.question_images.each do |img|
target_question.question_images << img.dup
end

source_question.question_labels.each do |label|
target_question.question_labels << label.dup
end

source_question.question_audios.each do |audio|
target_question.question_audios << audio.dup
end
end

def build_exam_question
if @exam_question.question.nil?
@exam_question.build_question do |q|
Expand Down
54 changes: 49 additions & 5 deletions app/controllers/questions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ class QuestionsController < ApplicationController

include SysLog::Actions

before_action :set_current_user, only: [:destroy, :change_status, :verify_owners, :copy, :index]
before_action :set_current_user, only: [:destroy, :change_status, :verify_owners, :verify_edit_permissions, :copy, :index]

layout false, except: :index

Expand Down Expand Up @@ -93,6 +93,13 @@ def update
end

if @question.update_attributes question_params
if params[:notify_responsibles] == 'true' && @question.previous_changes.present?
message = t('questions.questions.notification', question_enunciation: @question.enunciation)
@question.exams.each do |exam|
exam.notify_responsibles(message)
end
end

render partial: 'question', locals: { question: @question }
else
@errors = []
Expand All @@ -108,6 +115,29 @@ def update
render_json_error(error, 'questions.error')
end

def verify_edit_permissions
question = Question.find params[:id]
exam_id = params[:exam_id]

edition_status = question.verify_edit_permission(current_user, exam_id)

if [:edit_full, :edit_and_notify].include?(edition_status)
authorize! :update, Question
render json: { action: 'edit', notify: edition_status == :edit_and_notify }, status: :ok
else
repository_action = [:copy_blocked_exam, :copy_ownership].include?(edition_status) ? :copy : edition_status
render json: { action: repository_action, alert: I18n.t(get_edit_permissions_alert_key(edition_status), scope: [:questions, :error]) }, status: :ok
end

rescue RuntimeError => error
if error.message == 'in_use'
error_message = I18n.translate!("#{error.message}", scope: [:questions, :error], :raise => true) rescue t('general_message', scope: [:questions, :error])
render json: { success: false, alert: error_message }, status: :forbidden
end
rescue => error
render_json_error(error, 'questions.error')
end

def destroy
authorize! :destroy, Question
ActiveRecord::Base.transaction do
Expand Down Expand Up @@ -135,10 +165,6 @@ def show

def verify_owners
question = Question.find params[:id]
if params[:update]
question.can_change?(params[:exam_id])
authorize! :update, Question
end
if params[:copy]
question.can_copy?
authorize! :copy, Question
Expand All @@ -154,6 +180,7 @@ def verify_owners
end

def copy

authorize! :copy, Question

question = Question.find params[:id]
Expand Down Expand Up @@ -189,4 +216,21 @@ def log(object, message, type=LogAction::TYPE[:update])
LogAction.create(params_to_log.merge!(description: message, log_type: type))
end

def get_edit_permissions_alert_key(status)
case status
when :copy_blocked_exam
'in_use_question'
when :copy_ownership
'copy_ownership'
when :copy_repository
'copy_repository'
when :only_points
'only_points'
when :can_fork_copy
'can_fork_copy'
else
'in_use'
end
end

end
13 changes: 13 additions & 0 deletions app/models/allocation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,19 @@ def calculate_grade_and_hours
end
end

def self.send_email_to_responsible_users(allocation_tag_ids ,subject, message_body)
responsibles_list = responsibles(allocation_tag_ids)
emails = responsibles_list.each do |responsible|
responsible.user.email
end
clean_emails = emails.compact.uniq

if emails.any?
Job.send_mass_email(clean_emails, subject, message_body)
end

end

private

def verify_profile
Expand Down
Loading