diff options
author | Kohei Yoshino <kohei.yoshino@gmail.com> | 2018-09-26 17:29:19 +0200 |
---|---|---|
committer | Dylan William Hardison <dylan@hardison.net> | 2018-09-26 17:29:19 +0200 |
commit | 291bb971595489070ed8db3da1785f5a3977a15a (patch) | |
tree | aabee50d9dccacb21cf57dce1a23dce586079e64 /extensions | |
parent | deec4ab75d6478f51d6c72a230343ab955116a6b (diff) | |
download | bugzilla-291bb971595489070ed8db3da1785f5a3977a15a.tar.gz bugzilla-291bb971595489070ed8db3da1785f5a3977a15a.tar.xz |
Bug 1489718 - Insert form widgets for approval flag requests instead of free-form comment text
Diffstat (limited to 'extensions')
4 files changed, 460 insertions, 29 deletions
diff --git a/extensions/BMO/template/en/default/hook/flag/type_comment-form.html.tmpl b/extensions/BMO/template/en/default/hook/flag/type_comment-form.html.tmpl new file mode 100644 index 000000000..7963af850 --- /dev/null +++ b/extensions/BMO/template/en/default/hook/flag/type_comment-form.html.tmpl @@ -0,0 +1,202 @@ +[%# This Source Code Form is subject to the terms of the Mozilla Public + # License, v. 2.0. If a copy of the MPL was not distributed with this + # file, You can obtain one at http://mozilla.org/MPL/2.0/. + # + # This Source Code Form is "Incompatible With Secondary Licenses", as + # defined by the Mozilla Public License, v. 2.0. + #%] + +<template class="approval-request" data-flags="approval‑mozilla‑beta approval‑mozilla‑release"> + <fieldset> + <legend>Beta/Release Uplift Approval Request</legend> + <table> + <tr> + <th id="_ar_beta_i1_label">Feature/[% terms.Bug %] causing the regression</th> + <td><input type="text" placeholder="[% terms.Bug %] ID" aria-labelledby="_ar_beta_i1_label" data-type="b[% %]ug"></td> + </tr> + <tr> + <th id="_ar_beta_i2_label">User impact if declined</th> + <td><textarea aria-labelledby="_ar_beta_i2_label"></textarea></td> + </tr> + <tr> + <th id="_ar_beta_i3_label">Is this code covered by automated tests?</th> + <td> + <div role="radiogroup" class="buttons toggle" aria-labelledby="_ar_beta_i3_label"> + <div class="item"><input id="_ar_beta_i3_r1" type="radio" name="_ar_beta_i3_radio" value="Yes"><label for="_ar_beta_i3_r1">Yes</label></div> + <div class="item"><input id="_ar_beta_i3_r2" type="radio" name="_ar_beta_i3_radio" value="No"><label for="_ar_beta_i3_r2">No</label></div> + <div class="item"><input id="_ar_beta_i3_r3" type="radio" name="_ar_beta_i3_radio" value="Dunno"><label for="_ar_beta_i3_r3">Dunno</label></div> + </div> + </td> + </tr> + <tr> + <th id="_ar_beta_i4_label">Has the fix been verified in Nightly?</th> + <td> + <div role="radiogroup" class="buttons toggle" aria-labelledby="_ar_beta_i4_label"> + <div class="item"><input id="_ar_beta_i4_r1" type="radio" name="_ar_beta_i4_radio" value="Yes"><label for="_ar_beta_i4_r1">Yes</label></div> + <div class="item"><input id="_ar_beta_i4_r2" type="radio" name="_ar_beta_i4_radio" value="No"><label for="_ar_beta_i4_r2">No</label></div> + </div> + </td> + </tr> + <tr> + <th id="_ar_beta_i5_label">Needs manual test from QE?</th> + <td> + <div role="radiogroup" class="buttons toggle" aria-labelledby="_ar_beta_i5_label"> + <div class="item"><input id="_ar_beta_i5_r1" type="radio" name="_ar_beta_i5_radio" value="Yes"><label for="_ar_beta_i5_r1">Yes</label></div> + <div class="item"><input id="_ar_beta_i5_r2" type="radio" name="_ar_beta_i5_radio" value="No"><label for="_ar_beta_i5_r2">No</label></div> + </div> + </td> + </tr> + <tr> + <th id="_ar_beta_i6_label">If yes, steps to reproduce</th> + <td><textarea aria-labelledby="_ar_beta_i6_label"></textarea></td> + </tr> + <tr> + <th id="_ar_beta_i7_label">List of other uplifts needed</th> + <td><input type="text" placeholder="[% terms.Bug %] IDs" aria-labelledby="_ar_beta_i7_label" data-type="b[% %]ugs"></td> + </tr> + <tr> + <th id="_ar_beta_i8_label">Risk to taking this patch</th> + <td> + <div role="radiogroup" class="buttons toggle" aria-labelledby="_ar_beta_i8_label"> + <div class="item"><input id="_ar_beta_i8_r1" type="radio" name="_ar_beta_i8_radio" value="Low"><label for="_ar_beta_i8_r1">Low</label></div> + <div class="item"><input id="_ar_beta_i8_r2" type="radio" name="_ar_beta_i8_radio" value="Medium"><label for="_ar_beta_i8_r2">Medium</label></div> + <div class="item"><input id="_ar_beta_i8_r3" type="radio" name="_ar_beta_i8_radio" value="High"><label for="_ar_beta_i8_r3">High</label></div> + </div> + </td> + </tr> + <tr> + <th id="_ar_beta_i9_label">Why is the change risky/not risky?<br>(and alternatives if risky)</th> + <td><textarea aria-labelledby="_ar_beta_i9_label"></textarea></td> + </tr> + <tr> + <th id="_ar_beta_i10_label">String changes made/needed</th> + <td><input type="text" class="long" aria-labelledby="_ar_beta_i10_label"></td> + </tr> + </table> + </fieldset> +</template> + +<template class="approval-request" data-flags="approval‑mozilla‑esr*"> + <fieldset> + <legend>ESR Uplift Approval Request</legend> + <table> + <tr> + <th id="_ar_esr_i1_label">If this is not a sec:{high,crit} [% terms.bug %], please state case for ESR consideration</th> + <td><textarea aria-labelledby="_ar_esr_i1_label"></textarea></td> + </tr> + <tr> + <th id="_ar_esr_i2_label">User impact if declined</th> + <td><textarea aria-labelledby="_ar_esr_i2_label"></textarea></td> + </tr> + <tr> + <th id="_ar_esr_i3_label">Fix Landed on Version</th> + <td><input type="text" aria-labelledby="_ar_esr_i3_label"></td> + </tr> + <tr> + <th id="_ar_esr_i4_label">Risk to taking this patch</th> + <td> + <div role="radiogroup" class="buttons toggle" aria-labelledby="_ar_esr_i4_label"> + <div class="item"><input id="_ar_esr_i4_r1" type="radio" name="_ar_esr_i4_radio" value="Low"><label for="_ar_esr_i4_r1">Low</label></div> + <div class="item"><input id="_ar_esr_i4_r2" type="radio" name="_ar_esr_i4_radio" value="Medium"><label for="_ar_esr_i4_r2">Medium</label></div> + <div class="item"><input id="_ar_esr_i4_r3" type="radio" name="_ar_esr_i4_radio" value="High"><label for="_ar_esr_i4_r3">High</label></div> + </div> + </td> + </tr> + <tr> + <th id="_ar_esr_i5_label">Why is the change risky/not risky?<br>(and alternatives if risky)</th> + <td><textarea aria-labelledby="_ar_esr_i5_label"></textarea></td> + </tr> + <tr> + <th id="_ar_esr_i6_label">String or UUID changes made by this patch</th> + <td><input type="text" class="long" aria-labelledby="_ar_esr_i6_label"></td> + </tr> + </table> + <p>See <a href="https://wiki.mozilla.org/Release_Management/ESR_Landing_Process" target="_blank">ESR Landing Process</a> for more info.</p> + </fieldset> +</template> + +<template class="approval-request" data-flags="approval‑mozilla‑geckoview*"> + <fieldset> + <legend>GeckoView Uplift Approval Request</legend> + <table> + <tr> + <th id="_ar_gkv_i1_label">If this is not a sec:{high,crit} [% terms.bug %], please state case for consideration</th> + <td><textarea aria-labelledby="_ar_gkv_i1_label"></textarea></td> + </tr> + <tr> + <th id="_ar_gkv_i2_label">User impact if declined</th> + <td><textarea aria-labelledby="_ar_gkv_i2_label"></textarea></td> + </tr> + <tr> + <th id="_ar_gkv_i3_label">Fix Landed on Version</th> + <td><input type="text" aria-labelledby="_ar_gkv_i3_label"></td> + </tr> + <tr> + <th id="_ar_gkv_i4_label">Risk to taking this patch</th> + <td> + <div role="radiogroup" class="buttons toggle" aria-labelledby="_ar_gkv_i4_label"> + <div class="item"><input id="_ar_gkv_i4_r1" type="radio" name="_ar_gkv_i4_radio" value="Low"><label for="_ar_gkv_i4_r1">Low</label></div> + <div class="item"><input id="_ar_gkv_i4_r2" type="radio" name="_ar_gkv_i4_radio" value="Medium"><label for="_ar_gkv_i4_r2">Medium</label></div> + <div class="item"><input id="_ar_gkv_i4_r3" type="radio" name="_ar_gkv_i4_radio" value="High"><label for="_ar_gkv_i4_r3">High</label></div> + </div> + </td> + </tr> + <tr> + <th id="_ar_gkv_i5_label">Why is the change risky/not risky?<br>(and alternatives if risky)</th> + <td><textarea aria-labelledby="_ar_gkv_i5_label"></textarea></td> + </tr> + <tr> + <th id="_ar_gkv_i6_label">String or UUID changes made by this patch</th> + <td><input type="text" class="long" aria-labelledby="_ar_gkv_i6_label"></td> + </tr> + </table> + <p>See <a href="https://wiki.mozilla.org/Release_Management/Uplift_rules" target="_blank">Patch uplifting rules</a> for more info.</p> + </fieldset> +</template> + +<template class="approval-request" data-flags="sec‑approval"> + <fieldset> + <legend>Security Approval Request</legend> + <table> + <tr> + <th id="_ar_sec_i1_label">How easily could an exploit be constructed based on the patch?</th> + <td><textarea aria-labelledby="_ar_sec_i1_label"></textarea></td> + </tr> + <tr> + <th id="_ar_sec_i2_label">Do comments in the patch, the check-in comment, or tests included in the patch paint a bulls-eye on the security problem?</th> + <td> + <div role="radiogroup" class="buttons toggle" aria-labelledby="_ar_sec_i2_label"> + <div class="item"><input id="_ar_sec_i2_r1" type="radio" name="_ar_sec_i2_radio" value="Yes"><label for="_ar_sec_i2_r1">Yes</label></div> + <div class="item"><input id="_ar_sec_i2_r2" type="radio" name="_ar_sec_i2_radio" value="No"><label for="_ar_sec_i2_r2">No</label></div> + <div class="item"><input id="_ar_sec_i2_r3" type="radio" name="_ar_sec_i2_radio" value="Dunno"><label for="_ar_sec_i2_r3">Dunno</label></div> + </div> + </td> + </tr> + <tr> + <th id="_ar_sec_i3_label">Which older supported branches are affected by this flaw?</th> + <td><input type="text" aria-labelledby="_ar_sec_i3_label"></td> + </tr> + <tr> + <th id="_ar_sec_i4_label">If not all supported branches, which [% terms.bug %] introduced the flaw?</th> + <td><input type="text" placeholder="[% terms.Bug %] ID" aria-labelledby="_ar_sec_i4_label" data-type="b[% %]ug"></td> + </tr> + <tr> + <th id="_ar_sec_i5_label">Do you have backports for the affected branches?</th> + <td> + <div role="radiogroup" class="buttons toggle" aria-labelledby="_ar_sec_i5_label"> + <div class="item"><input id="_ar_sec_i5_r1" type="radio" name="_ar_sec_i5_radio" value="Yes"><label for="_ar_sec_i5_r1">Yes</label></div> + <div class="item"><input id="_ar_sec_i5_r2" type="radio" name="_ar_sec_i5_radio" value="No"><label for="_ar_sec_i5_r2">No</label></div> + </div> + </td> + </tr> + <tr> + <th id="_ar_sec_i6_label">If not, how different, hard to create, and risky will they be?</th> + <td><textarea aria-labelledby="_ar_sec_i6_label"></textarea></td> + </tr> + <tr> + <th id="_ar_sec_i7_label">How likely is this patch to cause regressions; how much testing does it need?</th> + <td><textarea aria-labelledby="_ar_sec_i7_label"></textarea></td> + </tr> + </table> + </fieldset> +</template> diff --git a/extensions/FlagTypeComment/template/en/default/flag/type_comment.html.tmpl b/extensions/FlagTypeComment/template/en/default/flag/type_comment.html.tmpl index 88d9d4dd7..56e03040a 100644 --- a/extensions/FlagTypeComment/template/en/default/flag/type_comment.html.tmpl +++ b/extensions/FlagTypeComment/template/en/default/flag/type_comment.html.tmpl @@ -20,35 +20,17 @@ # byron jones <glob@mozilla.com> #%] -[% IF ftc_flags.keys.size %] - <script [% script_nonce FILTER none %]> - YAHOO.util.Event.onDOMReady(function() { - var selects = YAHOO.util.Dom.getElementsByClassName('flag_select'); - for (var i = 0; i < selects.length; i++) { - YAHOO.util.Event.on(selects[i], 'change', ftc_on_change); - } - }); +<link rel="stylesheet" href="extensions/FlagTypeComment/web/styles/ftc.css"> +<script [% script_nonce FILTER none %] src="extensions/FlagTypeComment/web/js/ftc.js"></script> - function ftc_on_change(ev) { - var id = ev.target.id.split('-')[1]; - var state = ev.target.value; - var commentEl = document.getElementById('comment'); - if (!commentEl) return; - [% FOREACH type_id = ftc_flags.keys %] - [% FOREACH state = ftc_states %] - if ([% type_id FILTER none %] == id && '[% state FILTER js %]' == state) { - var text = '[% ftc_flags.$type_id.$state FILTER js %]'; - var value = commentEl.value; - if (value == text) { - return; - } else if (value == '') { - commentEl.value = text; - } else { - commentEl.value = text + "\n\n" + value; - } - } - [% END %] +[% IF ftc_flags.keys.size %] + [%# plaintext templates from database %] + [% FOREACH type_id = ftc_flags.keys %] + [% FOREACH state = ftc_states %] + <meta name="ftc:[% type_id FILTER none %]:[% state FILTER html %]" + content="[% ftc_flags.$type_id.$state FILTER html_linebreak %]"> [% END %] - } - </script> + [% END %] + [%# HTML form templates from extensions %] + [% Hook.process("form") %] [% END %] diff --git a/extensions/FlagTypeComment/web/js/ftc.js b/extensions/FlagTypeComment/web/js/ftc.js new file mode 100644 index 000000000..2682721bc --- /dev/null +++ b/extensions/FlagTypeComment/web/js/ftc.js @@ -0,0 +1,189 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This Source Code Form is "Incompatible With Secondary Licenses", as + * defined by the Mozilla Public License, v. 2.0. */ + +/** + * Reference or define the Bugzilla app namespace. + * @namespace + */ +var Bugzilla = Bugzilla || {}; // eslint-disable-line no-var + +/** + * Provide the ability to insert a comment template when a patch's approval flag is selected. + */ +Bugzilla.FlagTypeComment = class FlagTypeComment { + /** + * Initialize a new FlagTypeComment instance. + */ + constructor() { + this.templates = [...document.querySelectorAll('template.approval-request')]; + this.$flags = document.querySelector('#flags'); + this.$comment = document.querySelector('#comment'); + + if (this.$flags && this.$comment) { + this.selects = [...this.$flags.querySelectorAll('.flag_select')]; + this.selects.forEach($select => $select.addEventListener('change', () => this.flag_onselect($select))); + this.$comment.form.addEventListener('submit', () => this.form_onsubmit()); + } + } + + /** + * Check if a `<fieldset>` is compatible with the given flag. For example, `approval‑mozilla‑beta` matches + * `<fieldset data-flags="approval‑mozilla‑beta approval‑mozilla‑release">` while `approval‑mozilla‑esr60` + * matches `<fieldset data-flags="approval‑mozilla‑esr*">`. + * @param {String} name Flag name, such as `approval‑mozilla‑beta`. + * @param {(HTMLFieldSetElement|HTMLTemplateElement)} $element `<fieldset>` or `<template>` element with the + * `data-flags` attribute which is a space-separated list of flag names (wildcard chars can be used). + * @returns {Boolean} Whether the `<fieldset>` is compatible. + */ + check_compatibility(name, $element) { + return !!$element.dataset.flags.split(' ') + .find(_name => !!name.match(new RegExp(`^${_name.replace('*', '.+')}$`, 'i'))); + } + + /** + * Return a list of temporary `<fieldset>`s already inserted to the current page. + * @type {Array<HTMLFieldSetElement>} + */ + get inserted_fieldsets() { + return [...this.$flags.parentElement.querySelectorAll('fieldset.approval-request')]; + } + + /** + * Find a temporary `<fieldset>` already inserted to the current page by a flag name. + * @param {String} name Flag name, such as `approval‑mozilla‑beta`. + * @returns {HTMLFieldSetElement} Any `<fieldset>` element. + */ + find_inserted_fieldset(name) { + return this.inserted_fieldsets.find($fieldset => this.check_compatibility(name, $fieldset)); + } + + /** + * Find an available `<fieldset>` embedded in HTML by a flag name. + * @param {String} name Flag name, such as `approval‑mozilla‑beta`. + * @returns {HTMLFieldSetElement} Any `<fieldset>` element. + */ + find_available_fieldset(name) { + for (const $template of this.templates) { + if (this.check_compatibility(name, $template)) { + const $fieldset = $template.content.cloneNode(true).querySelector('fieldset'); + + $fieldset.className = 'approval-request'; + $fieldset.dataset.flags = $template.dataset.flags; + + return $fieldset; + } + } + + return null; + } + + /** + * Find a `<select>` element for a requested flag that matches the given `<fieldset>`. + * @param {HTMLFieldSetElement} $fieldset `<fieldset>` element with the `data-flags` attribute. + * @returns {HTMLSelectElement} Any `<select>` element. + */ + find_select($fieldset) { + return this.selects + .find($_select => $_select.value === '?' && this.check_compatibility($_select.dataset.name, $fieldset)); + } + + /** + * Add text to the comment box at the end of any existing comment. + * @param {String} text Comment text to be added. + */ + add_comment(text) { + this.$comment.value = this.$comment.value.match(/\S+/g) ? [this.$comment.value, text].join('\n\n') : text; + } + + /** + * Called whenever a flag selection is changed. Insert or remove a comment template. + * @param {HTMLSelectElement} $select `<select>` element that the `change` event is fired. + */ + flag_onselect($select) { + const id = Number($select.dataset.id); + const { name } = $select.dataset; + const state = $select.value; + let $fieldset = this.find_inserted_fieldset(name); + + // Remove the temporary `<fieldset>` if not required. One `<fieldset>` can support multiple flags, so, for example, + // if `approval‑mozilla‑release` is unselected but `approval‑mozilla‑beta` is still selected, keep it + if (state !== '?' && $fieldset && !this.find_select($fieldset)) { + $fieldset.remove(); + } + + // Insert a temporary `<fieldset>` if available + if (state === '?' && !$fieldset) { + $fieldset = this.find_available_fieldset(name); + + if ($fieldset) { + this.$flags.parentElement.appendChild($fieldset); + } + } + + // Insert a traditional plaintext comment template if available + if (!$fieldset) { + const $meta = document.querySelector(`meta[name="ftc:${id}:${state}"]`); + const text = $meta ? $meta.content : ''; + + if (text && this.$comment.value !== text) { + this.add_comment(text); + } + } + } + + /** + * Convert the input values into comment text and remove the temporary `<fieldset>` before submitting the form. + * @returns {Boolean} Always `true` to allow submitting the form. + */ + form_onsubmit() { + for (const $fieldset of this.inserted_fieldsets) { + const text = [ + `[${$fieldset.querySelector('legend').innerText}]`, + ...[...$fieldset.querySelectorAll('tr')].map($tr => { + const checkboxes = [...$tr.querySelectorAll('input[type="checkbox"]:checked')]; + const $radio = $tr.querySelector('input[type="radio"]:checked'); + const $input = $tr.querySelector('textarea,select,input'); + const label = $tr.querySelector('th').innerText.replace(/\n/g, ' '); + let value = ''; + + if (checkboxes.length) { + value = checkboxes.map($checkbox => $checkbox.value.trim()).join(', '); + } else if ($radio) { + value = $radio.value.trim(); + } else if ($input) { + value = $input.value.trim(); + + if ($input.dataset.type === 'bug') { + if (!value) { + value = 'None'; + } else if (!isNaN(value)) { + value = `Bug ${value}`; + } + } + + if ($input.dataset.type === 'bugs') { + if (!value) { + value = 'None'; + } else { + value = value.split(/,\s*/).map(str => (!isNaN(str) ? `Bug ${str}` : str)).join(', '); + } + } + } + + return `${label}: ${value}`; + }), + ].join('\n\n'); + + this.add_comment(text); + $fieldset.remove(); + } + + return true; + } +}; + +window.addEventListener('DOMContentLoaded', () => new Bugzilla.FlagTypeComment()); diff --git a/extensions/FlagTypeComment/web/styles/ftc.css b/extensions/FlagTypeComment/web/styles/ftc.css new file mode 100644 index 000000000..93d190935 --- /dev/null +++ b/extensions/FlagTypeComment/web/styles/ftc.css @@ -0,0 +1,58 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This Source Code Form is "Incompatible With Secondary Licenses", as + * defined by the Mozilla Public License, v. 2.0. */ + +fieldset.approval-request { + margin: 1em 0; +} + +fieldset.approval-request legend { + font-weight: bold; +} + +fieldset.approval-request th { + padding: .4em; + width: 20em; + line-height: 1.25; + font-weight: normal; + text-align: right; + vertical-align: top; + white-space: normal !important; +} + +fieldset.approval-request td { + vertical-align: middle; +} + +fieldset.approval-request label { + font-weight: normal !important; +} + +fieldset.approval-request input[type="text"] { + width: 10em; +} + +fieldset.approval-request input.long[type="text"] { + width: 40em; +} + +fieldset.approval-request textarea { + width: 40em; + min-height: 5em; + font-size: inherit; + line-height: inherit; + font-family: inherit; + resize: vertical; +} + +fieldset.approval-request div { + padding: 0 !important; +} + +fieldset.approval-request table ~ p:last-child { + margin: .4em; + text-align: right; +} |