From 91dc6dc99fa7a699e0b8e822a5c294509c9e9eb7 Mon Sep 17 00:00:00 2001 From: Kohei Yoshino Date: Mon, 8 Jan 2018 14:52:22 -0500 Subject: Bug 1428641 - Implement Requests quick look dropdown on global header --- .../en/default/hook/global/header-badge.html.tmpl | 47 +++++++--- .../en/default/hook/global/header-start.html.tmpl | 8 +- extensions/Review/web/js/badge.js | 102 +++++++++++++++++++++ extensions/Review/web/styles/badge.css | 27 +++++- js/lib/md5.min.js | 2 + skins/standard/global.css | 88 ++++++++++++++++++ template/en/default/request/queue.json.tmpl | 42 +++++++++ 7 files changed, 294 insertions(+), 22 deletions(-) create mode 100644 extensions/Review/web/js/badge.js create mode 100644 js/lib/md5.min.js create mode 100644 template/en/default/request/queue.json.tmpl diff --git a/extensions/Review/template/en/default/hook/global/header-badge.html.tmpl b/extensions/Review/template/en/default/hook/global/header-badge.html.tmpl index aca61561e..df3dd82be 100644 --- a/extensions/Review/template/en/default/hook/global/header-badge.html.tmpl +++ b/extensions/Review/template/en/default/hook/global/header-badge.html.tmpl @@ -6,18 +6,35 @@ # defined by the Mozilla Public License, v. 2.0. #%] -[% RETURN UNLESS - user.review_request_count - || user.feedback_request_count - || user.needinfo_request_count -%] - - - [%- user.review_request_count + user.feedback_request_count + user.needinfo_request_count ~%] - +[% IF user.id %] + [% request_count = user.review_request_count + user.feedback_request_count + user.needinfo_request_count %] + +[% END %] diff --git a/extensions/Review/template/en/default/hook/global/header-start.html.tmpl b/extensions/Review/template/en/default/hook/global/header-start.html.tmpl index 3da136f41..5441ea270 100644 --- a/extensions/Review/template/en/default/hook/global/header-start.html.tmpl +++ b/extensions/Review/template/en/default/hook/global/header-start.html.tmpl @@ -6,12 +6,8 @@ # defined by the Mozilla Public License, v. 2.0. #%] -[% IF user.review_request_count - || user.feedback_request_count - || user.needinfo_request_count -%] - [% style_urls.push('extensions/Review/web/styles/badge.css') %] -[% END %] +[% style_urls.push('extensions/Review/web/styles/badge.css') %] +[% javascript_urls.push('js/util.js', 'js/lib/md5.min.js', 'extensions/Review/web/js/badge.js') %] [% RETURN UNLESS template.name == 'attachment/edit.html.tmpl' || template.name == 'attachment/create.html.tmpl' diff --git a/extensions/Review/web/js/badge.js b/extensions/Review/web/js/badge.js new file mode 100644 index 000000000..cff22dc40 --- /dev/null +++ b/extensions/Review/web/js/badge.js @@ -0,0 +1,102 @@ +/* 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 || {}; + +/** + * Reference or define the Review namespace. + * @namespace + */ +Bugzilla.Review = Bugzilla.Review || {}; + +/** + * Provide the Badge functionality that shows the current review summary in the dropdown. + */ +Bugzilla.Review.Badge = class Badge { + /** + * Get a new Badge instance. + * @returns {Badge} New Badge instance. + */ + constructor() { + this.$button = document.querySelector('#header-requests-menu-button'); + this.$panel = document.querySelector('#header-requests .dropdown-panel'); + this.$loading = document.querySelector('#header-requests .dropdown-panel .loading'); + + if (this.$loading) { + this.$button.addEventListener('click', () => this.init(), { once: true }); + } + } + + /** + * Initialize the Reviews dropdown menu. + */ + async init() { + const url = this.$panel.querySelector('footer a').href + '&ctype=json'; + const response = await fetch(url, { credentials: 'same-origin' }); + const _requests = response.ok ? await response.json() : []; + + if (!response.ok) { + this.$loading.innerHTML = 'Couldn’t load requests for you.
Please try again later.'; + + return; + } + + if (!_requests.length) { + this.$loading.className = 'empty'; + this.$loading.innerHTML = 'You’re all caught up!'; + + return; + } + + const requests = []; + const $ul = this.$panel.querySelector('ul'); + const $fragment = document.createDocumentFragment(); + + // Sort requests from new to old, then group reviews/feedbacks asked by the same person in the same bug + _requests.reverse().forEach(_req => { + const dup_index = requests.findIndex(req => req.requester === _req.requester + && req.bug_id === _req.bug_id && req.type === _req.type && req.attach_id && _req.attach_id); + + if (dup_index > -1) { + requests[dup_index].dup_count++; + } else { + _req.dup_count = 1; + requests.push(_req); + } + }); + + // Show up to 20 newest requests + requests.slice(0, 20).forEach(req => { + const $li = document.createElement('li'); + const [, name, email] = req.requester.match(/^(.*)\s<(.*)>$/); + const pretty_name = name.replace(/([\[\(<‹].*?[›>\)\]]|\:[\w\-]+|\s+\-\s+.*)/g, '').trim(); + const link = req.attach_id && req.dup_count === 1 + ? `attachment.cgi?id=${req.attach_id}&action=edit` : `show_bug.cgi?id=${req.bug_id}`; + + $li.setAttribute('role', 'none'); + $li.innerHTML = ` + + + `; + $fragment.appendChild($li); + }); + + this.$loading.remove(); + $ul.appendChild($fragment); + $ul.hidden = false; + } +} + +window.addEventListener('DOMContentLoaded', () => new Bugzilla.Review.Badge(), { once: true }); diff --git a/extensions/Review/web/styles/badge.css b/extensions/Review/web/styles/badge.css index 53f539df8..9a6e14a8c 100644 --- a/extensions/Review/web/styles/badge.css +++ b/extensions/Review/web/styles/badge.css @@ -5,7 +5,32 @@ * This Source Code Form is "Incompatible With Secondary Licenses", as * defined by the Mozilla Public License, v. 2.0. */ -#header .badge { +#header-requests { + display: flex; + justify-content: center; + align-items: center; + margin: 0 4px !important; + width: 32px; + height: 32px; +} + +#header-requests-menu-button .icon { + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + background: #BBB; + color: #FFF; + border-radius: 50%; +} + +#header-requests-menu-button .icon::before { + font-size: 16px; + content: '\E7F4'; +} + +#header-requests-menu-button .badge { display: flex; align-items: center; justify-content: center; diff --git a/js/lib/md5.min.js b/js/lib/md5.min.js new file mode 100644 index 000000000..7d8a3f53c --- /dev/null +++ b/js/lib/md5.min.js @@ -0,0 +1,2 @@ +!function(n){"use strict";function t(n,t){var r=(65535&n)+(65535&t);return(n>>16)+(t>>16)+(r>>16)<<16|65535&r}function r(n,t){return n<>>32-t}function e(n,e,o,u,c,f){return t(r(t(t(e,n),t(u,f)),c),o)}function o(n,t,r,o,u,c,f){return e(t&r|~t&o,n,t,u,c,f)}function u(n,t,r,o,u,c,f){return e(t&o|r&~o,n,t,u,c,f)}function c(n,t,r,o,u,c,f){return e(t^r^o,n,t,u,c,f)}function f(n,t,r,o,u,c,f){return e(r^(t|~o),n,t,u,c,f)}function i(n,r){n[r>>5]|=128<>>9<<4)]=r;var e,i,a,d,h,l=1732584193,g=-271733879,v=-1732584194,m=271733878;for(e=0;e>5]>>>t%32&255);return r}function d(n){var t,r=[];for(r[(n.length>>2)-1]=void 0,t=0;t>5]|=(255&n.charCodeAt(t/8))<16&&(o=i(o,8*n.length)),r=0;r<16;r+=1)u[r]=909522486^o[r],c[r]=1549556828^o[r];return e=i(u.concat(d(t)),512+8*t.length),a(i(c.concat(e),640))}function g(n){var t,r,e="";for(r=0;r>>4&15)+"0123456789abcdef".charAt(15&t);return e}function v(n){return unescape(encodeURIComponent(n))}function m(n){return h(v(n))}function p(n){return g(m(n))}function s(n,t){return l(v(n),v(t))}function C(n,t){return g(s(n,t))}function A(n,t,r){return t?r?s(t,n):C(t,n):r?m(n):p(n)}"function"==typeof define&&define.amd?define(function(){return A}):"object"==typeof module&&module.exports?module.exports=A:n.md5=A}(this); +//# sourceMappingURL=md5.min.js.map \ No newline at end of file diff --git a/skins/standard/global.css b/skins/standard/global.css index ea25eb88a..48e4754ab 100644 --- a/skins/standard/global.css +++ b/skins/standard/global.css @@ -311,6 +311,94 @@ background-color: transparent; } + #header .dropdown-panel { + padding: 0 !important; + width: 400px; + max-width: none !important; + } + + #header .dropdown-panel header { + border-bottom: 1px solid #CCC; + } + + #header .dropdown-panel h2 { + margin: 0; + padding: 8px 12px; + font-size: 14px; + line-height: 100%; + font-weight: normal; + } + + #header .dropdown-panel ul { + overflow-y: auto; + margin: 0; + padding: 0; + max-height: 480px; + list-style-type: none; + } + + #header .dropdown-panel li:not(:last-child) { + border-bottom: 1px solid #CCC; + } + + #header .dropdown-panel li a { + padding: 12px !important; + } + + #header .dropdown-panel li a:hover { + background-color: rgba(0, 0, 0, .05) !important; + } + + #header .dropdown-panel li a * { + pointer-events: none; + } + + #header .dropdown-panel .notifications img { + float: left; + border-radius: 50%; + width: 40px; + height: 40px; + } + + #header .dropdown-panel .notifications img ~ * { + display: block; + margin-left: 52px; + } + + #header .dropdown-panel .notifications label { + overflow: hidden; + max-height: 40px; + } + + #header .dropdown-panel .notifications strong { + font-weight: 600; + } + + #header .dropdown-panel .notifications time { + font-size: 12px; + color: #999; + } + + #header .dropdown-panel .loading, + #header .dropdown-panel .empty { + display: flex; + align-items: center; + justify-content: center; + height: 240px; + line-height: 150%; + text-align: center; + } + + #header .dropdown-panel footer { + border-top: 1px solid #CCC; + text-align: center; + } + + #header .dropdown-panel footer a { + padding: 8px 16px !important; + line-height: 100% !important; + } + #header-search h2 { position: absolute; left: -99999px; diff --git a/template/en/default/request/queue.json.tmpl b/template/en/default/request/queue.json.tmpl new file mode 100644 index 000000000..121b52337 --- /dev/null +++ b/template/en/default/request/queue.json.tmpl @@ -0,0 +1,42 @@ +[%# 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. #%] + +[% RAWPERL %] +my @display_columns = ( + "requester", "requestee", "type", "status", "bug_id", "bug_summary", + "attach_id", "attach_summary", "ispatch", "created", "category" +); +my $requests = $stash->get('requests'); +my $time_filter = $context->filter('time', [ '%Y-%m-%dT%H:%M:%SZ', 'UTC' ]); +my $mail_filter = $context->filter('email'); + +my @results; +foreach my $request (@$requests) { + my %item = (); + foreach my $column (@display_columns) { + my $val; + if ( $column eq 'created' ) { + $val = $time_filter->( $request->{$column} ); + } + elsif ( $column =~ /^requeste/ ) { + $val = $mail_filter->( $request->{$column} ); + } + elsif ( $column =~ /_id$/ ) { + $val = $request->{$column} ? 0 + $request->{$column} : undef; + } + elsif ( $column =~ /^is/ ) { + $val = $request->{$column} ? \1 : \0; + } + else { + $val = $request->{$column}; + } + $item{$column} = $val; + } + push @results, \%item; +} +$output .= JSON::XS::encode_json( \@results ); +[% END %] -- cgit v1.2.3-24-g4f1b