diff --git a/Dockerfile b/Dockerfile index 7ed2ac45..18872f27 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ruby:2.5 +FROM ruby:2.6 LABEL maintainer="AKIKO TAKANO / (Twitter: @akiko_pusu)" \ description="Image to run Redmine simply with sqlite to try/review plugin." diff --git a/README.md b/README.md index 6953565b..24ab398a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Plugin to generate and use issue templates for each project to assist issue creation. The latest version 1.0.x **is not compatible with IE11**. (Related: #310) -Please use version 0.3.7 or **0.3-stable branch** (uing jQuery version) as a stable release for Redmine4.x. +Please use version 0.3.8 or **[0.3-stable](https://github.com/akiko-pusu/redmine_issue_templates/tree/0.3-stable) branch** (uing jQuery version) as a stable release for Redmine4.x. ## Repository @@ -112,6 +112,15 @@ If you have any requests, bug reports, please use GitHub issues. <%= l(:orphaned_template) %> - +
diff --git a/app/views/global_issue_templates/index.html.erb b/app/views/global_issue_templates/index.html.erb index e46eec88..fbcdf159 100644 --- a/app/views/global_issue_templates/index.html.erb +++ b/app/views/global_issue_templates/index.html.erb @@ -21,7 +21,7 @@

<%= tracker.name %>

-
#
+
diff --git a/app/views/issue_templates/_form.html.erb b/app/views/issue_templates/_form.html.erb index 74a24c2d..1df0b527 100644 --- a/app/views/issue_templates/_form.html.erb +++ b/app/views/issue_templates/_form.html.erb @@ -60,7 +60,7 @@

- @@ -73,7 +73,7 @@

- + @@ -93,7 +93,7 @@ diff --git a/app/views/issue_templates/_issue_select_form.html.erb b/app/views/issue_templates/_issue_select_form.html.erb index f9f8f806..9f389a9b 100644 --- a/app/views/issue_templates/_issue_select_form.html.erb +++ b/app/views/issue_templates/_issue_select_form.html.erb @@ -39,11 +39,12 @@

×

- - +
+ + +
@@ -72,91 +73,82 @@ diff --git a/app/views/issue_templates/_note_form.html.erb b/app/views/issue_templates/_note_form.html.erb index 42015e49..52173a9c 100644 --- a/app/views/issue_templates/_note_form.html.erb +++ b/app/views/issue_templates/_note_form.html.erb @@ -10,7 +10,7 @@ - + <%=h l(:display_and_filter_issue_templates_in_dialog, default: 'Filter Templates') %> @@ -30,96 +30,24 @@ diff --git a/app/views/issue_templates/_template_pulldown.html.erb b/app/views/issue_templates/_template_pulldown.html.erb index 46d78525..8d68723d 100644 --- a/app/views/issue_templates/_template_pulldown.html.erb +++ b/app/views/issue_templates/_template_pulldown.html.erb @@ -2,4 +2,3 @@ <%= options_for_template_pulldown(grouped_options) %> - diff --git a/app/views/issue_templates/index.html.erb b/app/views/issue_templates/index.html.erb index 55c4f8a3..ee786420 100644 --- a/app/views/issue_templates/index.html.erb +++ b/app/views/issue_templates/index.html.erb @@ -23,7 +23,7 @@

<%= tracker.name %>

<%= non_project_tracker_msg(project_tracker?(tracker, @project)) %> -
#
+
@@ -99,7 +99,7 @@

<%=h "#{l(:label_inherited_templates)}" %>

-
#
+
@@ -180,7 +180,7 @@ <% end %> <%= l(:only_admin_can_associate_global_template) %> -
#
+
diff --git a/app/views/note_templates/_list_note_templates.html.erb b/app/views/note_templates/_list_note_templates.html.erb index 4b0c4f5c..78da7075 100644 --- a/app/views/note_templates/_list_note_templates.html.erb +++ b/app/views/note_templates/_list_note_templates.html.erb @@ -49,7 +49,8 @@ diff --git a/app/views/note_templates/index.html.erb b/app/views/note_templates/index.html.erb index baca606c..9f3a0d27 100644 --- a/app/views/note_templates/index.html.erb +++ b/app/views/note_templates/index.html.erb @@ -22,7 +22,7 @@

<%= tracker.name %>

-
#
+
diff --git a/assets/javascripts/issue_templates.js b/assets/javascripts/issue_templates.js index a74b4f1e..093f1a11 100644 --- a/assets/javascripts/issue_templates.js +++ b/assets/javascripts/issue_templates.js @@ -1,27 +1,28 @@ /* * To change this template, choose Tools | Templates * and open the template in the editor. + * + * Use '==' operator to evaluate null or undefined. */ // For namespace setting. // var ISSUE_TEMPLATE = ISSUE_TEMPLATE || function () {} -function ISSUE_TEMPLATE(pulldownUrl, loadUrl, confirmMsg, shouldReplaced, confirmToReplace, - confirmation, generalTextYes, generalTextNo, isTriggeredBy) { - this.pulldownUrl = pulldownUrl - this.loadUrl = loadUrl - this.confirmMsg = confirmMsg - this.shouldReplaced = shouldReplaced - this.confirmToReplace = confirmToReplace - this.confirmation = confirmation - this.generalTextYes = generalTextYes - this.generalTextNo = generalTextNo - this.isTriggeredBy = isTriggeredBy +'use strict' + +function ISSUE_TEMPLATE(config) { + this.pulldownUrl = config.pulldownUrl + this.loadUrl = config.loadUrl + this.confirmMsg = config.confirmMessage + this.shouldReplaced = config.shouldReplaced + this.generalTextYes = config.generalTextYes + this.generalTextNo = config.generalTextNo + this.isTriggeredBy = config.isTriggeredBy } ISSUE_TEMPLATE.prototype = { clearValue: (id) => { let target = document.getElementById(id) - if (target === null) { + if (target == null) { return } target.value = '' @@ -63,17 +64,17 @@ ISSUE_TEMPLATE.prototype = { let issueDescription = document.getElementById('issue_description') let oldDescription = document.getElementById('original_description') - let templateNS = this + let ns = this - issueSubject.value = templateNS.escapeHTML(oldSubject.textContent) + issueSubject.value = ns.escapeHTML(oldSubject.textContent) - if (issueDescription !== null) { - issueDescription.value = templateNS.escapeHTML(oldDescription.textContent) + if (issueDescription != null) { + issueDescription.value = ns.escapeHTML(oldDescription.textContent) } try { if (CKEDITOR.instances.issue_description) { - CKEDITOR.instances.issue_description.setData(templateNS.escapeHTML(oldDescription.text())) + CKEDITOR.instances.issue_description.setData(ns.escapeHTML(oldDescription.text())) } } catch (e) { // do nothing. @@ -82,14 +83,9 @@ ISSUE_TEMPLATE.prototype = { oldDescription.textContent = '' document.getElementById('revert_template').classList.add('disabled') }, - load_template: (confirm_flg) => { - let confirmFlg = true - if (confirm_flg != null) { - confirmFlg = confirm_flg - } - - let ns = templateNS + loadTemplate: function () { let selectedTemplate = document.getElementById('issue_template') + let ns = this if (selectedTemplate.value === '') return @@ -120,89 +116,91 @@ ISSUE_TEMPLATE.prototype = { // when operator submits new issue form without required field and returns // with error message. If flash message #errorExplanation exists, not overwrited. // (https://github.com/akiko-pusu/redmine_issue_templates/issues/50) - if (document.querySelector('#errorExplanation') && document.querySelector('#errorExplanation')[0]) return + if (document.querySelector('#errorExplanation') && document.querySelector('#errorExplanation')[0]) { + document.querySelector('#errorExplanation') + return + } // Returned JSON may have the key named 'global_template' or 'issue_template' let parsedData = JSON.parse(data) let templateKey = Object.keys(parsedData)[0] let obj = parsedData[templateKey] - obj.description = (obj.description === null) ? '' : obj.description - obj.issue_title = (obj.issue_title === null) ? '' : obj.issue_title + obj.description = (obj.description == null) ? '' : obj.description + obj.issue_title = (obj.issue_title == null) ? '' : obj.issue_title - let oldSubj = '' - let oldVal = '' let issueSubject = document.getElementById('issue_subject') let issueDescription = document.getElementById('issue_description') - if (confirmFlg === true && ns.confirmToReplace === true && ns.shouldReplaced === 'true' && (issueSubject.value !== '')) { - if (oldSubj !== obj.issue_title) { + this.loadedTemplate = obj + + if (ns.shouldReplaced === 'true' && (issueDescription.value !== '' || issueSubject.value !== '')) { + if (obj.description !== '' || obj.issue_title !== '') { let hideConfirmFlag = ns.hideOverwiteConfirm() if (hideConfirmFlag === false) { - return ns.confirmToReplaceMsg() + return ns.confirmToReplaceContent(obj) } } } + ns.replaceTemplateValue(obj) + }) + }, + replaceTemplateValue: function (obj) { + let ns = this - // for description - if (issueDescription !== null) { - let originalDescription = document.getElementById('original_description') - if (issueDescription.value !== '' && ns.shouldReplaced === 'false') { - oldVal = issueDescription.value + '\n\n' - } - - originalDescription.textContent = issueDescription.value + let oldSubj = '' + let oldVal = '' + let issueSubject = document.getElementById('issue_subject') + let issueDescription = document.getElementById('issue_description') - issueDescription.getAttribute('original_description', issueDescription.value) - if (oldVal.replace(/(?:\r\n|\r|\n)/g, '').trim() !== obj.description.replace(/(?:\r\n|\r|\n)/g, '').trim()) { - issueDescription.value = oldVal + obj.description - } - } + if (issueDescription != null) { + let originalDescription = document.getElementById('original_description') + if (issueDescription.value !== '' && ns.shouldReplaced === 'false') { + oldVal = issueDescription.value + '\n\n' + } - let originalSubject = document.getElementById('original_subject') - if (issueSubject.value !== '' && ns.shouldReplaced === 'false') { - oldSubj = issueSubject.value + ' ' - } - originalSubject.textContent = issueSubject.value + originalDescription.textContent = issueDescription.value - issueSubject.setAttribute('original_title', issueSubject.value) - if (oldSubj.trim() !== obj.issue_title.trim()) { - issueSubject.value = oldSubj + obj.issue_title - } + issueDescription.getAttribute('original_description', issueDescription.value) + if (oldVal.replace(/(?:\r\n|\r|\n)/g, '').trim() !== obj.description.replace(/(?:\r\n|\r|\n)/g, '').trim()) { + issueDescription.value = oldVal + obj.description + } + } - try { - if (CKEDITOR.instances.issue_description) { - CKEDITOR.instances.issue_description.setData(oldVal + obj.description) - } - } catch (e) { - // do nothing. - } - // show message just after default template loaded. - if (ns.confirmMsg) { - ns.show_loaded_message(ns.confirmMsg, issueSubject) - } + let originalSubject = document.getElementById('original_subject') + if (issueSubject.value !== '' && ns.shouldReplaced === 'false') { + oldSubj = issueSubject.value + ' ' + } + originalSubject.textContent = issueSubject.value - if (originalSubject.textContent.length > 0) { - document.getElementById('revert_template').classList.remove('disabled') - } + issueSubject.setAttribute('original_title', issueSubject.value) + if (oldSubj.trim() !== obj.issue_title.trim()) { + issueSubject.value = oldSubj + obj.issue_title + } - if (obj.related_link !== '') { - let relatedLink = document.getElementById('issue_template_related_link') + try { + if (CKEDITOR.instances.issue_description) { + CKEDITOR.instances.issue_description.setData(oldVal + obj.description) + } + } catch (e) { + // do nothing. + } + // show message just after default template loaded. + if (ns.confirmMsg && ns.shouldReplaced) { + ns.showLoadedMessage(issueDescription) + } - relatedLink.setAttribute('href', obj.related_link) - relatedLink.style.display = 'inline' - relatedLink.textContent = obj.link_title - } else { - let relatedLink = document.getElementById('issue_template_related_link') - relatedLink.style.display = 'none' - } + if (originalSubject.textContent.length > 0) { + document.getElementById('revert_template').classList.remove('disabled') + } - ns.addCheckList(obj) - ns.builtin_fields(obj) - }) + ns.setRelatedLink(obj) + ns.addCheckList(obj) + ns.builtinFields(obj) + ns.confirmToReplace = true }, - confirmToReplaceMsg: () => { - let ns = templateNS + confirmToReplaceContent: function (obj) { + let ns = this let dialog = document.getElementById('issue_template_confirm_to_replace_dialog') dialog.style.visibility = 'visible' dialog.classList.add('active') @@ -215,7 +213,7 @@ ISSUE_TEMPLATE.prototype = { document.cookie = 'issue_template_confirm_to_replace_hide_dialog=0' } dialog.classList.remove('active') - ns.load_template(false) + ns.replaceTemplateValue(obj) }) document.getElementById('overwrite_no').addEventListener('click', () => { @@ -233,22 +231,23 @@ ISSUE_TEMPLATE.prototype = { dialog.classList.remove('active') }) }, - show_loaded_message: (confirmMsg, target) => { + showLoadedMessage: function (target) { + let ns = this // in app/views/issue_templates/_issue_select_form.html.erb let templateStatusArea = document.getElementById('template_status-area') - if (templateStatusArea === null) return false + if (templateStatusArea == null) return false if (document.querySelector('div.flash_message')) { document.querySelector('div.flash_message').remove() } let messageElement = document.createElement('div') - messageElement.innerHTML = confirmMsg + messageElement.innerHTML = ns.confirmMsg messageElement.classList.add('flash_message') messageElement.classList.add('fadeout') templateStatusArea.appendChild(messageElement) }, - getCsrfToken: () => { + getCsrfToken: function () { const metas = document.getElementsByTagName('meta') for (let meta of metas) { if (meta.getAttribute('name') === 'csrf-token') { @@ -257,8 +256,14 @@ ISSUE_TEMPLATE.prototype = { } return '' }, - set_pulldown: function (tracker) { + setPulldown: function (tracker) { let ns = this + let params = { issue_tracker_id: tracker } + let pullDownProject = document.getElementById('issue_project_id') + if (pullDownProject) { + params.issue_project_id = pullDownProject.value + } + fetch(ns.pulldownUrl, { method: 'POST', @@ -267,9 +272,7 @@ ISSUE_TEMPLATE.prototype = { 'Content-Type': 'application/json', 'X-CSRF-Token': ns.getCsrfToken() }, - body: JSON.stringify({ - issue_tracker_id: tracker - }) + body: JSON.stringify(params) }) .then((response) => { return response.text() @@ -279,9 +282,9 @@ ISSUE_TEMPLATE.prototype = { let length = document.querySelectorAll('#issue_template > optgroup > option').length if (length < 1) { document.getElementById('template_area').style.display = 'none' - if (ns.isTriggeredBy !== undefined && this.isTriggeredBy === 'issue_tracker_id') { + if (ns.isTriggeredBy != null && this.isTriggeredBy === 'issue_tracker_id') { if (document.querySelectorAll('#issue-form.new_issue').length > 0 && ns.should_replaced === true) { - if (typeof templateNS !== 'undefined') { + if (typeof ns !== 'undefined') { ns.eraseSubjectAndDescription() } } @@ -295,7 +298,7 @@ ISSUE_TEMPLATE.prototype = { }, addCheckList: function (obj) { let list = obj.checklist - if (list === undefined) return false + if (list == null) return false let checklistForm = document.getElementById('checklist_form') if (!checklistForm) return @@ -314,21 +317,31 @@ ISSUE_TEMPLATE.prototype = { console.log(`NOTE: Checklist could not be applied due to this error. ${e.message} : ${e.message}`) } }, - escapeHTML: (val) => { + setRelatedLink: function (obj) { + let relatedLink = document.getElementById('issue_template_related_link') + if (obj.related_link != null && obj.related_link !== '') { + relatedLink.setAttribute('href', obj.related_link) + relatedLink.style.display = 'inline' + relatedLink.textContent = obj.link_title + } else { + relatedLink.style.display = 'none' + } + }, + escapeHTML: function (val) { const div = document.createElement('div') div.textContent = val return div.textContent }, - unescapeHTML: (val) => { + unescapeHTML: function (val) { const div = document.createElement('div') div.innerHTML = val return div.innerHTML }, - replaceCkeContent: () => { + replaceCkeContent: function () { let element = document.getElementById('issue_description') return CKEDITOR.instances.issue_description.setData(element.value) }, - hideOverwiteConfirm: () => { + hideOverwiteConfirm: function () { let cookieArray = [] if (document.cookie !== '') { let tmp = document.cookie.split('; ') @@ -338,16 +351,16 @@ ISSUE_TEMPLATE.prototype = { } } let confirmationCookie = cookieArray['issue_template_confirm_to_replace_hide_dialog'] - if (confirmationCookie === undefined || parseInt(confirmationCookie) === 0) { + if (confirmationCookie == null || parseInt(confirmationCookie) === 0) { return false } return true }, // support built-in field update - builtin_fields: (template) => { - let ns = templateNS + builtinFields: function (template) { + let ns = this let builtinFieldsJson = template.builtin_fields_json - if (builtinFieldsJson === undefined) return false + if (builtinFieldsJson == null) return false try { Object.keys(builtinFieldsJson).forEach(function (key) { @@ -363,7 +376,7 @@ ISSUE_TEMPLATE.prototype = { return ns.updateFieldValues(elements, value) } } - if (element === null) { + if (element == null) { return } ns.updateFieldValue(element, value) @@ -372,7 +385,7 @@ ISSUE_TEMPLATE.prototype = { console.log(`NOTE: Builtin / custom fields could not be applied due to this error. ${e.message} : ${e.message}`) } }, - updateFieldValue: (element, value) => { + updateFieldValue: function (element, value) { // In case field is a select element, scans its option values and marked 'selected'. if (element.tagName.toLowerCase() === 'select') { let values = [] @@ -393,11 +406,12 @@ ISSUE_TEMPLATE.prototype = { element.value = value } }, - updateFieldValues: (elements, value) => { + updateFieldValues: function (elements, value) { + let ns = this for (let i = 0; i < elements.length; i++) { let element = elements[i] if (element.tagName.toLowerCase() === 'select') { - return templateNS.updateFieldValue(element, value) + return ns.updateFieldValue(element, value) } if (element.value === value) { if (element.tagName.toLowerCase() === 'input') { @@ -414,7 +428,7 @@ ISSUE_TEMPLATE.prototype = { } } }, - updateTemplateSelect: (event) => { + updateTemplateSelect: function (event) { let link = event.target let optionId = link.getAttribute('data-issue-template-id') let optionSelector = '#issue_template > optgroup > option[value="' + optionId + '"]' @@ -427,7 +441,7 @@ ISSUE_TEMPLATE.prototype = { let changeEvent = new Event('change') document.getElementById('issue_template').dispatchEvent(changeEvent) }, - filterTemplate: (event) => { + filterTemplate: function (event) { let cols = document.getElementsByClassName('template_data') let searchWord = event.target.value let reg = new RegExp(searchWord, 'gi') @@ -440,7 +454,7 @@ ISSUE_TEMPLATE.prototype = { } } }, - changeTemplatePlace: () => { + changeTemplatePlace: function () { if (document.querySelector('div.flash_message')) { document.querySelector('div.flash_message').remove() } @@ -464,7 +478,7 @@ if (!Element.prototype.closest) { do { if (el.matches(s)) return el el = el.parentElement || el.parentNode - } while (el !== null && el.nodeType === 1) + } while (el != null && el.nodeType === 1) return null } } @@ -490,7 +504,7 @@ document.onreadystatechange = () => { let element = templateHelps[i] element.addEventListener('mouseenter', (event) => { let contentId = event.target.getAttribute('data-tooltip-content') - if (contentId === null) return + if (contentId == null) return let target = event.target.getAttribute('data-tooltip-area') let obj = document.getElementById(target) @@ -501,7 +515,7 @@ document.onreadystatechange = () => { }) element.addEventListener('mouseleave', (event) => { let contentId = event.target.getAttribute('data-tooltip-content') - if (contentId === null) return + if (contentId == null) return let target = event.target.getAttribute('data-tooltip-area') let obj = document.getElementById(target) @@ -544,3 +558,106 @@ document.onreadystatechange = () => { } } } + +// ------- fot NoteTemplate + +function NOTE_TEMPLATE(config) { + this.baseElementId = config.baseElementId + this.baseTemplateListUrl = config.baseTemplateListUrl + this.baseTrackerId = config.baseTrackerId + this.baseProjectId = config.baseProjectId + this.loadNoteTemplateUrl = config.loadNoteTemplateUrl +} + +NOTE_TEMPLATE.prototype = { + setNoteDescription: function (target, value, container) { + let element = document.getElementById(target) + if (element.value.length === 0) { + element.value = value + } else { + element.value += '\n\n' + value + } + element.focus() + container.style.display = 'none' + + try { + if (CKEDITOR.instances.issue_notes) { + CKEDITOR.instances.issue_notes.setData(value) + CKEDITOR.instances.issue_notes.focus() + } + } catch (e) { + // do nothing. + } + }, + applyNoteTemplate: function (targetElement) { + let ns = this + let templateId = targetElement.dataset.noteTemplateId + let projectId = document.getElementById('issue_project_id') + let loadUrl = targetElement.dataset.noteTemplateLoadUrl + + let JSONdata = { + note_template: { note_template_id: templateId } + } + + if (targetElement.classList.contains('template-global')) { + JSONdata.note_template.template_type = 'global' + JSONdata.note_template.project_id = ns.baseProjectId + if (projectId && projectId.value) { + JSONdata.note_template.project_id = projectId.value + } + } + + let token = document.querySelector('#issue-form input[name="authenticity_token"]') + let req = new XMLHttpRequest() + req.onreadystatechange = function() { + let container = targetElement.closest('div.overlay') + let target = container.id.replace('template_', '') + target = target.replace('_dialog', '') + if (req.readyState === 4) { + if (req.status === 200 || req.status === 304) { + let value = JSON.parse(req.responseText) + ns.setNoteDescription(target, value.note_template.description, container) + } + } + } + req.open('POST', loadUrl, true) + if (token) { + req.setRequestHeader('X-CSRF-Token', token.value) + } + req.setRequestHeader('Content-Type', 'application/json') + req.send(JSON.stringify(JSONdata)) + }, + changeNoteTemplateList: function (elementId) { + let ns = this + let token = document.querySelectorAll('#issue-form input[name="authenticity_token"]') + + let projectId = document.getElementById('issue_project_id') + let trackerId = document.getElementById('issue_tracker_id') + let templateListUrl = ns.baseTemplateListUrl + if (trackerId != null && projectId != null) { + templateListUrl += '?tracker_id=' + trackerId.value + templateListUrl += '&project_id=' + projectId.value + } else { + templateListUrl += '?tracker_id=' + ns.baseTrackerId + '&project_id=' + ns.baseProjectId + } + + let req = new XMLHttpRequest(); + req.onreadystatechange = function() { + if (req.readyState === 4) { + if (req.status === 200 || req.status === 304) { + let value = req.responseText + // replace here! + let dialog = document.getElementById(`${elementId}_dialog`) + let target = document.querySelector(`#${elementId}_dialog .popup .filtered_templates_list`) + target.innerHTML = value + dialog.style = 'display: block;' + } + } + } + req.open('GET', templateListUrl, true) + if (token) { + req.setRequestHeader('X-CSRF-Token', token.value) + } + req.send() + } +} diff --git a/assets/javascripts/template_checklists.js b/assets/javascripts/template_checklists.js index 7f547e72..0924165d 100644 --- a/assets/javascripts/template_checklists.js +++ b/assets/javascripts/template_checklists.js @@ -1,3 +1,4 @@ +'use strict' const removeCheckList = (obj) => { let target = obj.closest('li') target.remove() diff --git a/assets/javascripts/template_fields.js b/assets/javascripts/template_fields.js index 5df12f34..2d6fed48 100644 --- a/assets/javascripts/template_fields.js +++ b/assets/javascripts/template_fields.js @@ -1,4 +1,5 @@ // This JS is used only when create / edit template. (Using Vue.js) +'use strict' const vm = new Vue({ el: '#json_generator', data: { diff --git a/assets/stylesheets/issue_templates.css b/assets/stylesheets/issue_templates.css index ce52146f..6dcbcfbb 100644 --- a/assets/stylesheets/issue_templates.css +++ b/assets/stylesheets/issue_templates.css @@ -181,7 +181,7 @@ td.template_title { } .filtered_templates_list { - padding-top: 10px; + padding-top: 2px; } select.issue_template { @@ -388,7 +388,7 @@ a.icon-help:hover .tooltip-area { width: auto; background-color: #c6d9ec; padding: 6px; - margin-bottom: 20px; + margin-bottom: 2px; } .overlay .cancel { @@ -398,6 +398,11 @@ a.icon-help:hover .tooltip-area { cursor: default; } +.overlay .template_search_filter_wrapper { + margin-top: 8px; + margin-bottom: 8px; +} + /*--------- for Flash message --------*/ .fadeout { animation : fadeOut 5s; diff --git a/init.rb b/init.rb index 800a4a3b..bbe89ed4 100644 --- a/init.rb +++ b/init.rb @@ -47,7 +47,7 @@ def template_menu_allowed? name 'Redmine Issue Templates plugin' author 'Akiko Takano' description 'Plugin to generate and use issue templates for each project to assist issue creation.' - version '1.0.2' + version '1.0.3' author_url 'http://twitter.com/akiko_pusu' requires_redmine version_or_higher: '4.0' url 'https://github.com/akiko-pusu/redmine_issue_templates' diff --git a/spec/features/issue_template_spec.rb b/spec/features/issue_template_spec.rb index 13bbe676..01638c38 100644 --- a/spec/features/issue_template_spec.rb +++ b/spec/features/issue_template_spec.rb @@ -21,7 +21,8 @@ :issue_statuses, :trackers, :projects_trackers, - :enabled_modules + :enabled_modules, + :enumerations given(:role) { Role.find(1) } after do @@ -78,9 +79,10 @@ feature 'create template' do given!(:enabled_module) { FactoryBot.create(:enabled_module) } + given(:issue_template_title) { page.find('#issue_template_title') } + given(:issue_template_description) { page.find('#issue_template_description') } + context 'When user has priv to issue template' do - given(:issue_template_title) { page.find('#issue_template_title') } - given(:issue_template_description) { page.find('#issue_template_description') } given(:create_button) { page.find('#issue_template-form > input[type="submit"]') } given(:error_message) { page.find('#errorExplanation') } background do @@ -98,6 +100,28 @@ expect(error_message).to have_content('Title cannot be blank') end end + + # enable buildin-fields + context 'Setting "enable_builtin_fields" is true' do + background do + # enable_builtin_fields + Setting.send 'plugin_redmine_issue_templates=', 'enable_builtin_fields' => 'true' + assign_template_priv(role, add_permission: :edit_issue_templates) + log_user('jsmith', 'jsmith') + visit '/projects/ecookbook/issue_templates/new' + end + + scenario 'form for builtin_fields are shown' do + select 'Bug', from: 'issue_template[tracker_id]' + + expect(page).to have_selector('div#json_generator') + expect(page).to have_selector('select#field_selector') + + select 'Priority', from: 'field_selector' + + expect(page).to have_select('Value', options: IssuePriority.active.pluck(:name)) + end + end end feature 'Template feature at new issue screen' do @@ -163,6 +187,8 @@ context 'have subproject' do background do + template_setting = IssueTemplateSetting.find_or_create(1) + template_setting.inherit_templates = true sub_project = Project.find(3) sub_project.inherit_members = true sub_project.enabled_modules << EnabledModule.new(name: 'issue_templates') @@ -173,6 +199,8 @@ scenario 'Select sub project then template for subproject is shown' do sub_project = page.find('#issue_project_id > option[value="3"]') + + expect(page).to have_selector('#issue_template > optgroup > option', count: 3) template_option = page.find('#issue_template > optgroup > option:nth-child(1)') expect(template_option.text).to eq issue_templates.first.title @@ -288,4 +316,37 @@ expect(issue_subject.value).to eq 'Test for revert subject' end end + + # builtin fields test + feature 'Can apply value to the builtin field' do + given(:priority_name) { IssuePriority.active.sample.name } + given(:builtin_fields_json_value) do + { + "issue_priority_id": priority_name, + "issue_estimated_hours": 5 + } + end + given(:expected_title) { 'Sample Title for rspec' } + given!(:enabled_module) { FactoryBot.create(:enabled_module) } + given!(:issue_templates) do + FactoryBot.create(:issue_template, title: expected_title, + project_id: 1, tracker_id: 1, builtin_fields_json: builtin_fields_json_value) + end + background do + # enable_builtin_fields + Setting.send 'plugin_redmine_issue_templates=', 'enable_builtin_fields' => 'true' + + assign_template_priv(role, add_permission: :show_issue_templates) + log_user('jsmith', 'jsmith') + visit '/projects/ecookbook/issues/new' + + select expected_title, from: 'issue_template' + sleep(0.2) + end + + scenario 'Builtin fields are filled' do + expect(page).to have_select('issue[priority_id]', selected: priority_name) + expect(page.find('#issue_estimated_hours').value).to eq '5' + end + end end diff --git a/spec/features/update_issue_spec.rb b/spec/features/update_issue_spec.rb index 6d173f71..9f35c14d 100644 --- a/spec/features/update_issue_spec.rb +++ b/spec/features/update_issue_spec.rb @@ -103,8 +103,6 @@ Setting.send 'plugin_redmine_issue_templates=', 'apply_global_template_to_all_projects' => 'true' end - given(:template_rows) { page.find('div#template_issue_notes_dialog table > tbody') } - scenario 'One Global template for note' do visit_update_issue(user) issue = Issue.last @@ -116,6 +114,8 @@ page.find('a#link_template_issue_notes_dialog').click wait_for_ajax + template_rows = page.find('div#template_issue_notes_dialog table > tbody') + expect(page).to have_selector('div#template_issue_notes_dialog') expect(template_rows).to have_selector('tr:first-child > td:nth-child(3) > a.template-global') end
#