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:
parent
f314d1a472
commit
e451b45f45
@ -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
|
||||||
|
|||||||
@ -81,6 +81,17 @@ 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){
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -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 */
|
if(error.length)
|
||||||
$('#all_attributes').html($(parsed).find('#all_attributes').html());
|
{
|
||||||
|
if($('html').find("#errorExplanation").length == 0)
|
||||||
/* we update the history list */
|
{
|
||||||
$('#history').append($(parsed).find('#history .journal.has-details:last-child'));
|
$('.issue.details').before("<div id='errorExplanation'>" + error.html() + "</div>");
|
||||||
|
} else
|
||||||
/* data updated, remove spin and add success icon for 2sec */
|
{
|
||||||
setTimeout(function(){
|
$('html').find("#errorExplanation").html(error.html());
|
||||||
$('.details .attributes .' + cssClass + '.attribute i.fa-spin').removeClass('fa-refresh fa-spin').addClass('fa-check statusOk');
|
}
|
||||||
|
/* 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);
|
||||||
|
}, 500);
|
||||||
|
} else {
|
||||||
|
|
||||||
// update other fields to avoid conflict
|
/* removing error div if exists */
|
||||||
$('#issue_lock_version').val(parseInt($('#issue_lock_version').val()) + 1 );
|
$('html').find("#errorExplanation").remove();
|
||||||
$('#last_journal_id').val(parseInt($('#last_journal_id').val()) + 1 );
|
|
||||||
|
|
||||||
|
/* 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 {
|
||||||
|
|||||||
@ -5,3 +5,4 @@ en:
|
|||||||
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'
|
||||||
@ -5,3 +5,4 @@ fr:
|
|||||||
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'
|
||||||
4
init.rb
4
init.rb
@ -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
|
||||||
|
|||||||
@ -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>"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user