fixed Github issues #6, #12 : User can't update status until all required field are filled for this step of the issue

This commit is contained in:
Ilogeek 2017-09-19 16:40:07 +02:00
parent f314d1a472
commit e451b45f45
6 changed files with 92 additions and 32 deletions

View File

@ -1,6 +1,6 @@
# redmine_issue_dynamic_edit # redmine_issue_dynamic_edit
Add new dropdown elements on detailed issue page to dynamically update issue's status, assignee, priority, start and due dates, ratio and estimated time fields, directly in the details block of the issue. Add new dropdown elements on detailed issue page to dynamically update issue's status, assignee, priority, start and due dates, ratio and estimated time fields, directly in the details block of the issue without any page refresh (JIRA style).
### Example ### Example
@ -14,10 +14,12 @@ Add new dropdown elements on detailed issue page to dynamically update issue's s
### Customization ### Customization
Feel free to edit `assets/stylesheets/issue_dynamic_edit.css` to update the look of your fields depending on your current Redmine Theme. Feel free to edit `assets/stylesheets/issue_dynamic_edit.css` to update the look of your fields depending on your current Redmine Theme.
This plugin uses [FontAwesome icons](http://fontawesome.io/) This plugin uses [FontAwesome icons](http://fontawesome.io/)
### Changelog ### Changelog
* **v 0.4.4** : fixed Github issues #6, #12 : User can't update status until all required field are filled for this step of the issue
* **v 0.4.3** : partially fixed Github issue #12 : Read only attributes can't be edited anymore. Dynamic refresh for read only attributes when status changes * **v 0.4.3** : partially fixed Github issue #12 : Read only attributes can't be edited anymore. Dynamic refresh for read only attributes when status changes
* **v 0.4.2** : fixed Github issue #10 : History list updated after modification * **v 0.4.2** : fixed Github issue #10 : History list updated after modification
* **v 0.4.1** : fixed Github issue #7 : update status list to follow Redmine workflow * **v 0.4.1** : fixed Github issue #7 : update status list to follow Redmine workflow

View File

@ -79,7 +79,18 @@ function initEditFields()
} }
} }
initEditFields(); initEditFields();
/* Add required style to attributes */
function updateRequiredFields(reqFieldsArray)
{
for (var i = 0; i < reqFieldsArray.length; i++) {
var htmlLabel = reqFieldsArray[i].replace(/_/g, '-');
$('.issue.details .attribute.' + htmlLabel + ' .label').html('<span title=\"' + _TXT_REQUIRED_FIELD + '\" class=\"field-description\">' + $('.issue.details .attribute.' + htmlLabel + ' .label').html() + '</span> <span class=\"required\"> *</span>');
}
}
updateRequiredFields(JSON.parse($('#required_field_array').html()));
$('body.controller-issues.action-show').on('click', '.btn.close', function(e){ $('body.controller-issues.action-show').on('click', '.btn.close', function(e){
@ -134,32 +145,63 @@ function issueDynamicUpdate(field_name, field_value, type, cssClass){
}, },
success: function(msg) { success: function(msg) {
/* get result page content (updated issue detail page with new status) */ /* get result page content (updated issue detail page with new status) */
var parsed = $.parseHTML(msg); var parsed = $.parseHTML(msg);
/* we update the details block */ var error = $(parsed).find("#errorExplanation");
$('div.issue.details').html($(parsed).find('div.issue.details').html());
/* we init edit fields */
initEditFields();
initEditFieldListeners();
/* we update issue properties edit block */
$('#all_attributes').html($(parsed).find('#all_attributes').html());
/* we update the history list */ if(error.length)
$('#history').append($(parsed).find('#history .journal.has-details:last-child')); {
if($('html').find("#errorExplanation").length == 0)
/* data updated, remove spin and add success icon for 2sec */ {
setTimeout(function(){ $('.issue.details').before("<div id='errorExplanation'>" + error.html() + "</div>");
$('.details .attributes .' + cssClass + '.attribute i.fa-spin').removeClass('fa-refresh fa-spin').addClass('fa-check statusOk'); } else
{
$('html').find("#errorExplanation").html(error.html());
}
/* data updated, remove spin and add success icon for 2sec */
setTimeout(function(){ setTimeout(function(){
$('.details .attributes .' + cssClass + '.attribute i.fa-check.statusOk').remove(); $('.details .attributes .' + cssClass + '.attribute i.fa-spin').removeClass('fa-refresh fa-spin').addClass('fa-times statusKo');
}, 2000); setTimeout(function(){
}, 500); $('.details .attributes .' + cssClass + '.attribute i.fa-times.statusKo').remove();
}, 2000);
// update other fields to avoid conflict }, 500);
$('#issue_lock_version').val(parseInt($('#issue_lock_version').val()) + 1 ); } else {
$('#last_journal_id').val(parseInt($('#last_journal_id').val()) + 1 );
/* removing error div if exists */
$('html').find("#errorExplanation").remove();
/* we update the details block */
$('div.issue.details').html($(parsed).find('div.issue.details').html());
if(type == "progress") { // specific case for progress bar
$('body').find('.details .attributes .' + cssClass + '.attribute .value').append(' <i class="fa fa-refresh fa-spin fa-fw"></i>');
} else {
$('body').find('.details .attributes .' + cssClass + '.attribute .value').append(' <i class="fa fa-refresh fa-spin fa-fw"></i>');
}
/* we init edit fields */
initEditFields();
initEditFieldListeners();
updateRequiredFields(JSON.parse($(parsed).find('#required_field_array').html()));
/* we update issue properties edit block */
$('#all_attributes').html($(parsed).find('#all_attributes').html());
/* we update the history list */
$('#history').append($(parsed).find('#history .journal.has-details:last-child'));
/* data updated, remove spin and add success icon for 2sec */
setTimeout(function(){
$('.details .attributes .' + cssClass + '.attribute i.fa-spin').removeClass('fa-refresh fa-spin').addClass('fa-check statusOk');
setTimeout(function(){
$('.details .attributes .' + cssClass + '.attribute i.fa-check.statusOk').remove();
}, 2000);
}, 500);
// update other fields to avoid conflict
$('#issue_lock_version').val(parseInt($('#issue_lock_version').val()) + 1 );
$('#last_journal_id').val(parseInt($('#last_journal_id').val()) + 1 );
}
}, },
error: function(xhr, msg, error) { error: function(xhr, msg, error) {
/* error and no update, info logged into console */ /* error and no update, info logged into console */
@ -242,7 +284,7 @@ function initEditFieldListeners()
{ {
e.preventDefault(); e.preventDefault();
$('.start-date .value .error').remove(); $('.start-date .value .error').remove();
if(new Date(domInputStartDate.val()).getTime() <= new Date($('body').find('#DueDateInput input').val()).getTime()) if(new Date(domInputStartDate.val()).getTime() <= new Date($('body').find('#DueDateInput input').val()).getTime() || $('body').find('#DueDateInput input').val() == "")
{ {
issueDynamicUpdate('start_date', domInputStartDate.val(), 'date', 'start-date'); issueDynamicUpdate('start_date', domInputStartDate.val(), 'date', 'start-date');
} else { } else {
@ -263,7 +305,7 @@ function initEditFieldListeners()
{ {
e.preventDefault(); e.preventDefault();
$('.due-date .value .error').remove(); $('.due-date .value .error').remove();
if(new Date($('body').find('#StartDateInput input').val()).getTime() <= new Date(domInputDueDate.val()).getTime()) if(new Date($('body').find('#StartDateInput input').val()).getTime() <= new Date(domInputDueDate.val()).getTime() || $('body').find('#StartDateInput input').val() == "" )
{ {
issueDynamicUpdate('due_date', domInputDueDate.val(), 'date', 'due-date'); issueDynamicUpdate('due_date', domInputDueDate.val(), 'date', 'due-date');
} else { } else {

View File

@ -4,4 +4,5 @@ en:
ide_txt_error_start_date : 'Start date must be anterior to due date' ide_txt_error_start_date : 'Start date must be anterior to due date'
ide_txt_error_due_date : 'Due date must be posterior to start date' ide_txt_error_due_date : 'Due date must be posterior to start date'
ide_txt_error_ajax_call : 'Error (check your JS console)' ide_txt_error_ajax_call : 'Error (check your JS console)'
ide_txt_cancel_btn : 'Cancel the modification' ide_txt_cancel_btn : 'Cancel the modification'
ide_txt_required_field : 'This field is required to go to next step'

View File

@ -4,4 +4,5 @@ fr:
ide_txt_error_start_date : "La date de début doit être antérieure à la date d'échéance" ide_txt_error_start_date : "La date de début doit être antérieure à la date d'échéance"
ide_txt_error_due_date : "La date d'échéance doit être postérieure à la date de début" ide_txt_error_due_date : "La date d'échéance doit être postérieure à la date de début"
ide_txt_error_ajax_call : 'Erreur (logs dans la console JS)' ide_txt_error_ajax_call : 'Erreur (logs dans la console JS)'
ide_txt_cancel_btn : 'Annuler la modification' ide_txt_cancel_btn : 'Annuler la modification'
ide_txt_required_field : 'Ce champs est obligatoire pour passer au Statut suivant'

View File

@ -5,8 +5,8 @@ require 'details_issue_hooks'
Redmine::Plugin.register :redmine_issue_dynamic_edit do Redmine::Plugin.register :redmine_issue_dynamic_edit do
name 'Redmine Dynamic edit Issue plugin' name 'Redmine Dynamic edit Issue plugin'
author 'Hugo Zilliox' author 'Hugo Zilliox'
description 'Allows users to dynamically update issue attributes in detailed view' description 'Allows users to dynamically update issue attributes in detailed view without refreshing the page (JIRA style)'
version '0.4.3' version '0.4.4'
url 'https://github.com/ilogeek/redmine_issue_dynamic_edit' url 'https://github.com/ilogeek/redmine_issue_dynamic_edit'
author_url 'https://hzilliox.fr' author_url 'https://hzilliox.fr'
end end

View File

@ -23,7 +23,16 @@ class DetailsIssueHooks < Redmine::Hook::ViewListener
if (issue_id) if (issue_id)
issue = Issue.find(issue_id) issue = Issue.find(issue_id)
readOnlyAttributes = issue.read_only_attribute_names(User.current) readOnlyAttributes = issue.read_only_attribute_names(User.current)
# o << issue.required_attribute_names(User.current).to_json requiredAttributes = issue.required_attribute_names(User.current)
# o << requiredAttributes.to_json
allRequiredFieldsFilled = true
requiredAttributes.each do |attr|
if(issue.read_attribute(attr).to_s.empty?)
allRequiredFieldsFilled = false
end
end
if (issue) if (issue)
if (User.current.allowed_to?(:edit_issues, project)) if (User.current.allowed_to?(:edit_issues, project))
@ -33,7 +42,7 @@ class DetailsIssueHooks < Redmine::Hook::ViewListener
# Status dropdown # Status dropdown
statuses = issue.new_statuses_allowed_to(User.current) statuses = issue.new_statuses_allowed_to(User.current)
if (!statuses.empty? && !(readOnlyAttributes.include? 'status_id')) if (!statuses.empty? && !(readOnlyAttributes.include? 'status_id') && allRequiredFieldsFilled)
o << "<span class='dynamicEdit' id='statusListDropdown'>" o << "<span class='dynamicEdit' id='statusListDropdown'>"
o << "<select data-issue='#{issue_id}'><option disabled='disabled' selected> </option>" o << "<select data-issue='#{issue_id}'><option disabled='disabled' selected> </option>"
statuses.each do |s| statuses.each do |s|
@ -146,8 +155,13 @@ class DetailsIssueHooks < Redmine::Hook::ViewListener
o << " var _TXT_ERROR_START_DATE = \"" + l(:ide_txt_error_start_date) + "\";\n" o << " var _TXT_ERROR_START_DATE = \"" + l(:ide_txt_error_start_date) + "\";\n"
o << " var _TXT_ERROR_DUE_DATE = \"" + l(:ide_txt_error_due_date) + "\";\n" o << " var _TXT_ERROR_DUE_DATE = \"" + l(:ide_txt_error_due_date) + "\";\n"
o << " var _TXT_ERROR_AJAX_CALL = \"" + l(:ide_txt_error_ajax_call) + "\";\n" o << " var _TXT_ERROR_AJAX_CALL = \"" + l(:ide_txt_error_ajax_call) + "\";\n"
o << " var _TXT_REQUIRED_FIELD = \"" + l(:ide_txt_required_field) + "\";\n"
o << "</script>\n"
o << "<div style='display:none' id='required_field_array'>#{requiredAttributes.to_json}</div>\n"
o << "</script>"
# closing the display none div parent # closing the display none div parent
o << "</div>" o << "</div>"