diff options
author | Dylan William Hardison <dylan@hardison.net> | 2018-08-04 18:24:15 +0200 |
---|---|---|
committer | Dylan William Hardison <dylan@hardison.net> | 2018-08-04 18:24:15 +0200 |
commit | f44392e8cdbea85ac308b2472f813ee605ebae4b (patch) | |
tree | 6e7adaf99a0e5a43eb1bf5a0d673d86b60f34f99 /extensions | |
parent | 5be3a7fd0061aa0bc3059e09079741873b9b833f (diff) | |
parent | 4528b21bc922f8b1e0ba8581d230a492aa43c9cf (diff) | |
download | bugzilla-f44392e8cdbea85ac308b2472f813ee605ebae4b.tar.gz bugzilla-f44392e8cdbea85ac308b2472f813ee605ebae4b.tar.xz |
Merge branch 'mojo-poc'
Diffstat (limited to 'extensions')
31 files changed, 834 insertions, 405 deletions
diff --git a/extensions/BMO/Extension.pm b/extensions/BMO/Extension.pm index d2e62eccd..743d03099 100644 --- a/extensions/BMO/Extension.pm +++ b/extensions/BMO/Extension.pm @@ -2742,4 +2742,96 @@ sub enter_bug_entrydefaultvars { } } +sub app_startup { + my ($self, $args) = @_; + my $app = $args->{app}; + my $r = $app->routes; + + $r->any( '/:REWRITE_itrequest' => [ REWRITE_itrequest => qr{form[\.:]itrequest} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Infrastructure & Operations', 'format' => 'itrequest' } ); + $r->any( '/:REWRITE_mozlist' => [ REWRITE_mozlist => qr{form[\.:]mozlist} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'product' => 'mozilla.org', 'format' => 'mozlist' } ); + $r->any( '/:REWRITE_poweredby' => [ REWRITE_poweredby => qr{form[\.:]poweredby} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'product' => 'mozilla.org', 'format' => 'poweredby' } ); + $r->any( '/:REWRITE_presentation' => [ REWRITE_presentation => qr{form[\.:]presentation} ] ) + ->to( 'cgi#enter_bug_cgi' => { 'product' => 'mozilla.org', 'format' => 'presentation' } ); + $r->any( '/:REWRITE_trademark' => [ REWRITE_trademark => qr{form[\.:]trademark} ] ) + ->to( 'cgi#enter_bug_cgi' => { 'product' => 'mozilla.org', 'format' => 'trademark' } ); + $r->any( '/:REWRITE_recoverykey' => [ REWRITE_recoverykey => qr{form[\.:]recoverykey} ] ) + ->to( 'cgi#enter_bug_cgi' => { 'product' => 'mozilla.org', 'format' => 'recoverykey' } ); + $r->any( '/:REWRITE_legal' => [ REWRITE_legal => qr{form[\.:]legal} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Legal', 'format' => 'legal' }, ); + $r->any( '/:REWRITE_recruiting' => [ REWRITE_recruiting => qr{form[\.:]recruiting} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Recruiting', 'format' => 'recruiting' } ); + $r->any( '/:REWRITE_intern' => [ REWRITE_intern => qr{form[\.:]intern} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Recruiting', 'format' => 'intern' } ); + $r->any( '/:REWRITE_mozpr' => [ REWRITE_mozpr => qr{form[\.:]mozpr} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Mozilla PR', 'format' => 'mozpr' }, ); + $r->any( '/:REWRITE_reps_mentorship' => [ REWRITE_reps_mentorship => qr{form[\.:]reps[\.:]mentorship} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Mozilla Reps', 'format' => 'mozreps' }, ); + $r->any( '/:REWRITE_reps_budget' => [ REWRITE_reps_budget => qr{form[\.:]reps[\.:]budget} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Mozilla Reps', 'format' => 'remo-budget' } ); + $r->any( '/:REWRITE_reps_swag' => [ REWRITE_reps_swag => qr{form[\.:]reps[\.:]swag} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Mozilla Reps', 'format' => 'remo-swag' } ); + $r->any( '/:REWRITE_reps_it' => [ REWRITE_reps_it => qr{form[\.:]reps[\.:]it} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Mozilla Reps', 'format' => 'remo-it' } ); + $r->any( '/:REWRITE_reps_payment' => [ REWRITE_reps_payment => qr{form[\.:]reps[\.:]payment} ] ) + ->to( 'CGI#page_cgi' => { 'id' => 'remo-form-payment.html' } ); + $r->any( '/:REWRITE_csa_discourse' => [ REWRITE_csa_discourse => qr{form[\.:]csa[\.:]discourse} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Infrastructure & Operations', 'format' => 'csa-discourse' } ); + $r->any( '/:REWRITE_employee_incident' => [ REWRITE_employee_incident => qr{form[\.:]employee[\.\-:]incident} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'product' => 'mozilla.org', 'format' => 'employee-incident' } ); + $r->any( '/:REWRITE_brownbag' => [ REWRITE_brownbag => qr{form[\.:]brownbag} ] ) + ->to( 'CGI#https_air_mozilla_org_requests' => {} ); + $r->any( '/:REWRITE_finance' => [ REWRITE_finance => qr{form[\.:]finance} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Finance', 'format' => 'finance' } ); + $r->any( + '/:REWRITE_moz_project_review' => [ REWRITE_moz_project_review => qr{form[\.:]moz[\.\-:]project[\.\-:]review} ] + )->to( 'CGI#enter_bug_cgi' => { 'product' => 'mozilla.org', 'format' => 'moz-project-review' } ); + $r->any( '/:REWRITE_docs' => [ REWRITE_docs => qr{form[\.:]docs?} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Developer Documentation', 'format' => 'doc' } ); + $r->any( '/:REWRITE_mdn' => [ REWRITE_mdn => qr{form[\.:]mdn?} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'format' => 'mdn', 'product' => 'developer.mozilla.org' } ); + $r->any( '/:REWRITE_swag_gear' => [ REWRITE_swag_gear => qr{form[\.:](swag|gear)} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'format' => 'swag', 'product' => 'Marketing' } ); + $r->any( '/:REWRITE_costume' => [ REWRITE_costume => qr{form[\.:]costume} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Marketing', 'format' => 'costume' } ); + $r->any( '/:REWRITE_ipp' => [ REWRITE_ipp => qr{form[\.:]ipp} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Internet Public Policy', 'format' => 'ipp' } ); + $r->any( '/:REWRITE_creative' => [ REWRITE_creative => qr{form[\.:]creative} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'format' => 'creative', 'product' => 'Marketing' } ); + $r->any( '/:REWRITE_user_engagement' => [ REWRITE_user_engagement => qr{form[\.:]user[\.\-:]engagement} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'format' => 'user-engagement', 'product' => 'Marketing' } ); + $r->any( '/:REWRITE_dev_engagement_event' => + [ REWRITE_dev_engagement_event => qr{form[\.:]dev[\.\-:]engagement[\.\-\:]event} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Developer Engagement', 'format' => 'dev-engagement-event' } ); + $r->any( '/:REWRITE_mobile_compat' => [ REWRITE_mobile_compat => qr{form[\.:]mobile[\.\-:]compat} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Tech Evangelism', 'format' => 'mobile-compat' } ); + $r->any( '/:REWRITE_web_bounty' => [ REWRITE_web_bounty => qr{form[\.:]web[\.:]bounty} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'format' => 'web-bounty', 'product' => 'mozilla.org' } ); + $r->any( '/:REWRITE_automative' => [ REWRITE_automative => qr{form[\.:]automative} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Testing', 'format' => 'automative' } ); + $r->any( '/:REWRITE_comm_newsletter' => [ REWRITE_comm_newsletter => qr{form[\.:]comm[\.:]newsletter} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'format' => 'comm-newsletter', 'product' => 'Marketing' } ); + $r->any( '/:REWRITE_screen_share_whitelist' => + [ REWRITE_screen_share_whitelist => qr{form[\.:]screen[\.:]share[\.:]whitelist} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'format' => 'screen-share-whitelist', 'product' => 'Firefox' } ); + $r->any( '/:REWRITE_data_compliance' => [ REWRITE_data_compliance => qr{form[\.:]data[\.\-:]compliance} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Data Compliance', 'format' => 'data-compliance' } ); + $r->any( '/:REWRITE_fsa_budget' => [ REWRITE_fsa_budget => qr{form[\.:]fsa[\.:]budget} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'product' => 'FSA', 'format' => 'fsa-budget' } ); + $r->any( '/:REWRITE_triage_request' => [ REWRITE_triage_request => qr{form[\.:]triage[\.\-]request} ] ) + ->to( 'CGI#page_cgi' => { 'id' => 'triage_request.html' } ); + $r->any( '/:REWRITE_crm_CRM' => [ REWRITE_crm_CRM => qr{form[\.:](crm|CRM)} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'format' => 'crm', 'product' => 'Marketing' } ); + $r->any( '/:REWRITE_nda' => [ REWRITE_nda => qr{form[\.:]nda} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Legal', 'format' => 'nda' } ); + $r->any( '/:REWRITE_name_clearance' => [ REWRITE_name_clearance => qr{form[\.:]name[\.:]clearance} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'format' => 'name-clearance', 'product' => 'Legal' } ); + $r->any( '/:REWRITE_shield_studies' => [ REWRITE_shield_studies => qr{form[\.:]shield[\.:]studies} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Shield', 'format' => 'shield-studies' } ); + $r->any( '/:REWRITE_client_bounty' => [ REWRITE_client_bounty => qr{form[\.:]client[\.:]bounty} ] ) + ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Firefox', 'format' => 'client-bounty' } ); +} + __PACKAGE__->NAME; diff --git a/extensions/BMO/template/en/default/bug/create/create-swag.html.tmpl b/extensions/BMO/template/en/default/bug/create/create-swag.html.tmpl index cff5e5796..05ec4e2d9 100644 --- a/extensions/BMO/template/en/default/bug/create/create-swag.html.tmpl +++ b/extensions/BMO/template/en/default/bug/create/create-swag.html.tmpl @@ -13,106 +13,106 @@ items = [ { id => '', name => 'Splendidest Gear' }, { id => '#185687', name => 'Moleskine Notebook (Firefox)' }, - { id => '#155749', name => 'Rickshaw Messenger Bag ' }, - { id => '#155415S', name => 'Champion Hooded Sweatshirt S' }, - { id => '#155415M', name => 'Champion Hooded Sweatshirt M' }, - { id => '#155415L', name => 'Champion Hooded Sweatshirt L' }, - { id => '#155415X', name => 'Champion Hooded Sweatshirt XL' }, - { id => '#1554152', name => 'Champion Hooded Sweatshirt 2XL' }, - { id => '#157452S', name => 'Very Splendid Package Ladies S' }, - { id => '#157452M', name => 'Very Splendid Package Ladies M' }, - { id => '#157452L', name => 'Very Splendid Package Ladies L' }, - { id => '#157452X', name => 'Very Splendid Package Ladies XL' }, - { id => '#1574522', name => 'Very Splendid Package Ladies 2XL' }, - { id => '#157454S', name => 'Very Splendid Package Men\'s S' }, - { id => '#157454M', name => 'Very Splendid Package Men\'s M' }, - { id => '#157454L', name => 'Very Splendid Package Men\'s L' }, - { id => '#157454X', name => 'Very Splendid Package Men\'s XL' }, - { id => '#1574542', name => 'Very Splendid Package Men\'s 2XL' }, - { id => '#1574543', name => 'Very Splendid Package Men\'s 3XL' }, - { id => '#157451S', name => 'Most Splendid Package, S' }, - { id => '#157451M', name => 'Most Splendid Package, M' }, - { id => '#157451L', name => 'Most Splendid Package, L' }, - { id => '#157451X', name => 'Most Splendid Package, XL' }, - { id => '', name => 'Splendider Gear' }, - { id => '#155751', name => 'Drawstring Tote ' }, - { id => '#155340', name => 'Beanie' }, - { id => '#155339', name => 'Black Cap with Tote ' }, - { id => '#212669', name => 'Stoneware Ceramic Mug' }, - { id => '#190928S', name => 'Firefox Tee w/Woven Tag Navy S' }, - { id => '#190928M', name => 'Firefox Tee w/Woven Tag Navy M' }, - { id => '#190928L', name => 'Firefox Tee w/Woven Tag Navy L' }, - { id => '#190928X', name => 'Firefox Tee w/Woven Tag Navy XL' }, - { id => '#1909282', name => 'Firefox Tee w/Woven Tag Navy 2XL' }, - { id => '#1909283', name => 'Firefox Tee w/Woven Tag Navy 3XL' }, - { id => '#190929S', name => 'Firefox SS Lapis Tee Lapis S' }, - { id => '#190929M', name => 'Firefox SS Lapis Tee Lapis M' }, - { id => '#190929L', name => 'Firefox SS Lapis Tee Lapis L' }, - { id => '#190929X', name => 'Firefox SS Lapis Tee Lapis XL' }, - { id => '#155413S', name => 'Ladies\' T-shirt Navy S' }, - { id => '#155413M', name => 'Ladies\' T-shirt Navy M' }, - { id => '#155413L', name => 'Ladies\' T-shirt Navy L' }, - { id => '#155413X', name => 'Ladies\' T-shirt Navy XL' }, - { id => '#1554132', name => 'Ladies\' T-shirt Navy 2XL' }, +# { id => '#155749', name => 'Rickshaw Messenger Bag ' }, +# { id => '#155415S', name => 'Champion Hooded Sweatshirt S' }, +# { id => '#155415M', name => 'Champion Hooded Sweatshirt M' }, +# { id => '#155415L', name => 'Champion Hooded Sweatshirt L' }, +# { id => '#155415X', name => 'Champion Hooded Sweatshirt XL' }, +# { id => '#1554152', name => 'Champion Hooded Sweatshirt 2XL' }, +# { id => '#157452S', name => 'Very Splendid Package Ladies S' }, +# { id => '#157452M', name => 'Very Splendid Package Ladies M' }, +# { id => '#157452L', name => 'Very Splendid Package Ladies L' }, +# { id => '#157452X', name => 'Very Splendid Package Ladies XL' }, +# { id => '#1574522', name => 'Very Splendid Package Ladies 2XL' }, +# { id => '#157454S', name => 'Very Splendid Package Men\'s S' }, +# { id => '#157454M', name => 'Very Splendid Package Men\'s M' }, +# { id => '#157454L', name => 'Very Splendid Package Men\'s L' }, +# { id => '#157454X', name => 'Very Splendid Package Men\'s XL' }, +# { id => '#1574542', name => 'Very Splendid Package Men\'s 2XL' }, +# { id => '#1574543', name => 'Very Splendid Package Men\'s 3XL' }, +# { id => '#157451S', name => 'Most Splendid Package, S' }, +# { id => '#157451M', name => 'Most Splendid Package, M' }, +# { id => '#157451L', name => 'Most Splendid Package, L' }, +# { id => '#157451X', name => 'Most Splendid Package, XL' }, +# { id => '', name => 'Splendider Gear' }, +# { id => '#155751', name => 'Drawstring Tote ' }, +# { id => '#155340', name => 'Beanie' }, +# { id => '#155339', name => 'Black Cap with Tote ' }, +# { id => '#212669', name => 'Stoneware Ceramic Mug' }, +# { id => '#190928S', name => 'Firefox Tee w/Woven Tag Navy S' }, +# { id => '#190928M', name => 'Firefox Tee w/Woven Tag Navy M' }, +# { id => '#190928L', name => 'Firefox Tee w/Woven Tag Navy L' }, +# { id => '#190928X', name => 'Firefox Tee w/Woven Tag Navy XL' }, +# { id => '#1909282', name => 'Firefox Tee w/Woven Tag Navy 2XL' }, +# { id => '#1909283', name => 'Firefox Tee w/Woven Tag Navy 3XL' }, +# { id => '#190929S', name => 'Firefox SS Lapis Tee Lapis S' }, +# { id => '#190929M', name => 'Firefox SS Lapis Tee Lapis M' }, +# { id => '#190929L', name => 'Firefox SS Lapis Tee Lapis L' }, +# { id => '#190929X', name => 'Firefox SS Lapis Tee Lapis XL' }, +# { id => '#155413S', name => 'Ladies\' T-shirt Navy S' }, +# { id => '#155413M', name => 'Ladies\' T-shirt Navy M' }, +# { id => '#155413L', name => 'Ladies\' T-shirt Navy L' }, +# { id => '#155413X', name => 'Ladies\' T-shirt Navy XL' }, +# { id => '#1554132', name => 'Ladies\' T-shirt Navy 2XL' }, { id => '', name => 'Splendid Gear' }, - { id => '#192150', name => '1.25" Firefox Button-PKG25 ' }, +# { id => '#192150', name => '1.25" Firefox Button-PKG25 ' }, { id => '#197156', name => 'Firefox Tattoos- Pkg50' }, - { id => '#197158', name => 'Firefox Sticker' }, - { id => '#197159', name => 'Firefox Laminated Badge' }, - { id => '#155754', name => 'Lanyard with Bulldog Clip (Mozilla)' }, +# { id => '#197158', name => 'Firefox Sticker' }, +# { id => '#197159', name => 'Firefox Laminated Badge' }, +# { id => '#155754', name => 'Lanyard with Bulldog Clip (Mozilla)' }, { id => '#155756', name => 'Silicone Wristband ' }, - { id => '', name => 'Limited Availability Gear' }, - { id => '#265073', name => 'Mozilla Cap' }, +# { id => '', name => 'Limited Availability Gear' }, +# { id => '#265073', name => 'Mozilla Cap' }, { id => '#265080', name => 'Fox Plush' }, - { id => '#265072S', name => 'Mozilla Custom Hoodie' }, - { id => '#265072M', name => 'Mozilla Custom Hoodie' }, - { id => '#265072L', name => 'Mozilla Custom Hoodie' }, - { id => '#265072X', name => 'Mozilla Custom Hoodie' }, - { id => '#2650722', name => 'Mozilla Custom Hoodie' }, - { id => '#265074S', name => 'Ladies\' Firefox Logo T-Shirt' }, - { id => '#265074M', name => 'Ladies\' Firefox Logo T-Shirt' }, - { id => '#265074L', name => 'Ladies\' Firefox Logo T-Shirt' }, - { id => '#265074X', name => 'Ladies\' Firefox Logo T-Shirt' }, - { id => '#2650742', name => 'Ladies\' Firefox Logo T-Shirt' }, - { id => '#265075S', name => 'Women\'s Vertical T-Shirt' }, - { id => '#265075M', name => 'Women\'s Vertical T-Shirt' }, - { id => '#265075L', name => 'Women\'s Vertical T-Shirt' }, - { id => '#265075X', name => 'Women\'s Vertical T-Shirt' }, - { id => '#2650752', name => 'Women\'s Vertical T-Shirt' }, - { id => '#265078S', name => 'Mozilla Horizontal T-Shirt' }, - { id => '#265078M', name => 'Mozilla Horizontal T-Shirt' }, - { id => '#265078L', name => 'Mozilla Horizontal T-Shirt' }, - { id => '#265078X', name => 'Mozilla Horizontal T-Shirt' }, - { id => '#2650782', name => 'Mozilla Horizontal T-Shirt' }, +# { id => '#265072S', name => 'Mozilla Custom Hoodie' }, +# { id => '#265072M', name => 'Mozilla Custom Hoodie' }, +# { id => '#265072L', name => 'Mozilla Custom Hoodie' }, +# { id => '#265072X', name => 'Mozilla Custom Hoodie' }, +# { id => '#2650722', name => 'Mozilla Custom Hoodie' }, +# { id => '#265074S', name => 'Ladies\' Firefox Logo T-Shirt' }, +# { id => '#265074M', name => 'Ladies\' Firefox Logo T-Shirt' }, +# { id => '#265074L', name => 'Ladies\' Firefox Logo T-Shirt' }, +# { id => '#265074X', name => 'Ladies\' Firefox Logo T-Shirt' }, +# { id => '#2650742', name => 'Ladies\' Firefox Logo T-Shirt' }, +# { id => '#265075S', name => 'Women\'s Vertical T-Shirt' }, +# { id => '#265075M', name => 'Women\'s Vertical T-Shirt' }, +# { id => '#265075L', name => 'Women\'s Vertical T-Shirt' }, +# { id => '#265075X', name => 'Women\'s Vertical T-Shirt' }, +# { id => '#2650752', name => 'Women\'s Vertical T-Shirt' }, +# { id => '#265078S', name => 'Mozilla Horizontal T-Shirt' }, +# { id => '#265078M', name => 'Mozilla Horizontal T-Shirt' }, +# { id => '#265078L', name => 'Mozilla Horizontal T-Shirt' }, +# { id => '#265078X', name => 'Mozilla Horizontal T-Shirt' }, +# { id => '#2650782', name => 'Mozilla Horizontal T-Shirt' }, ]; mozspaces = [ { name => 'Beijing', - address1 => 'Mozilla Online Ltd, International Club Office Tower 800A', - address2 => '21 Jian Guo Men Wai Avenue', + address1 => 'Mozilla Online Ltd.', + address2 => 'China Resources Building, Suite 1708, 8 Jianguomenbei Avenue', city => 'Beijing', - state => 'Chaoyang District', + state => 'Dongcheng District', country => 'China', - postcode => '100020', + postcode => '100005', }, { name => 'Berlin', address1 => 'MZ Denmark ApS - Germany', - address2 => 'Voltastrasse 5 / Building (Haus) 10 / Stair (Treppe) 6 / 2nd floor.', + address2 => 'GSG-Hof Schlesische Straße, Gebäude 3, 4. Obergeschoss, Schlesische Straße 27', city => 'Berlin', state => 'Germany', country => 'Germany', - postcode => '13355', + postcode => '10997', }, { name => 'London', address1 => 'Mozilla London', - address2 => '101 St. Martin\'s Lane, 3rd Floor', + address2 => 'Metal Box Factory, Suite 441, 4th floor, 30 Great Guildford Street', city => 'London', state => 'Greater London', country => 'UK', - postcode => 'WC2N 4AZ', + postcode => 'SE1 0HS', }, { name => 'Mountain View', @@ -160,15 +160,6 @@ mozspaces = [ postcode => '11047', }, { - name => 'Tokyo', - address1 => '7-5-6 Roppongi', - address2 => '', - city => 'Minato-ku', - state => 'Tokyo', - country => 'Japan', - postcode => '106-0032', - }, - { name => 'Toronto', address1 => 'Mozilla Canada', address2 => '366 Adelaide Street W, Suite 500', diff --git a/extensions/BMO/template/en/default/hook/global/header-external-links.html.tmpl b/extensions/BMO/template/en/default/hook/global/header-external-links.html.tmpl index 54a2f0e49..f79548e3d 100644 --- a/extensions/BMO/template/en/default/hook/global/header-external-links.html.tmpl +++ b/extensions/BMO/template/en/default/hook/global/header-external-links.html.tmpl @@ -15,7 +15,7 @@ <li role="presentation"> <a href="https://www.mozilla.org/" role="menuitem" tabindex="-1">Mozilla Home</a> </li> - <li role="separator" class="dropdown-separator"></li> + <li role="separator"></li> <li role="presentation"> <a href="https://www.mozilla.org/privacy/websites/" role="menuitem" tabindex="-1">Privacy</a> </li> diff --git a/extensions/BMO/template/en/default/hook/reports/components-start.html.tmpl b/extensions/BMO/template/en/default/hook/reports/components-start.html.tmpl new file mode 100644 index 000000000..a4234caa2 --- /dev/null +++ b/extensions/BMO/template/en/default/hook/reports/components-start.html.tmpl @@ -0,0 +1,10 @@ +[%# 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. + #%] + +[%# Don't show the default assignees and QA contacts %] +[% show_default_people = 0 %] diff --git a/extensions/BMO/template/en/default/pages/group_members.html.tmpl b/extensions/BMO/template/en/default/pages/group_members.html.tmpl index ec2cb2e46..1c593c07e 100644 --- a/extensions/BMO/template/en/default/pages/group_members.html.tmpl +++ b/extensions/BMO/template/en/default/pages/group_members.html.tmpl @@ -82,7 +82,7 @@ <a href="editusers.cgi?action=edit&userid=[% member.id FILTER none %]" target="_blank"> [% ELSE %] - <a href="user_profile?login=[% member.login FILTER uri %]" + <a href="user_profile?user_id=[% member.id FILTER none %]" target="_blank"> [% END %] <span [% 'class="bz_inactive"' UNLESS member.is_enabled %]> diff --git a/extensions/BMO/template/en/default/reports/components.html.tmpl b/extensions/BMO/template/en/default/reports/components.html.tmpl deleted file mode 100644 index 3e23d389e..000000000 --- a/extensions/BMO/template/en/default/reports/components.html.tmpl +++ /dev/null @@ -1,99 +0,0 @@ -[%# The contents of this file are subject to the Mozilla Public - # License Version 1.1 (the "License"); you may not use this file - # except in compliance with the License. You may obtain a copy of - # the License at http://www.mozilla.org/MPL/ - # - # Software distributed under the License is distributed on an "AS - # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or - # implied. See the License for the specific language governing - # rights and limitations under the License. - # - # The Original Code is the Bugzilla Bug Tracking System. - # - # The Initial Developer of the Original Code is Netscape Communications - # Corporation. Portions created by Netscape are - # Copyright (C) 1998 Netscape Communications Corporation. All - # Rights Reserved. - # - # Contributor(s): Bradley Baetz <bbaetz@student.usyd.edu.au> - # Max Kanat-Alexander <mkanat@bugzilla.org> - #%] - -[%# INTERFACE: - # product: object. The product for which we want to display component - # descriptions. - # component: string. The name of the component to hilight in the browser - #%] - -[% title = BLOCK %] - Components for [% product.name FILTER html %] -[% END %] - -[% inline_style = BLOCK %] -.product_name { - font-size: 2em; - font-weight: normal; -} -.component_name { - font-size: 1.5em; - font-weight: normal; -} -.product_desc, .component_desc { - padding-left: 1em; - font-size: 1em; -} -.component_container { - padding-left: 1em; - margin-bottom: 1em; -} -.product_container, .instructions { - margin-bottom: 1em; -} -.component_highlight { - padding: 0 0 0 1em; -} -[% END %] - -[% PROCESS global/header.html.tmpl - style_urls = [ "skins/standard/reports.css" ] - title = title - style = inline_style -%] - -<h2>[% mark FILTER html %]</h2> - -<div class="product_container"> - <span class="product_name">[% product.name FILTER html %]</span> - <div class="product_desc"> - [% product.description FILTER html_light %] - </div> -</div> - -<div class="instructions"> - Select a component to see open [% terms.bugs %] in that component: -</div> - -[% FOREACH comp = product.components %] - [% INCLUDE describe_comp %] -[% END %] - -[% PROCESS global/footer.html.tmpl %] - -[%############################################################################%] -[%# BLOCK for components %] -[%############################################################################%] - -[% BLOCK describe_comp %] - <div class="component_container [%- IF comp.name == component_mark %] component_hilite[% END %]"> - <div class="component_name"> - <a name="[% comp.name FILTER html %]" - href="buglist.cgi?product= - [%- product.name FILTER uri %]&component= - [%- comp.name FILTER uri %]&resolution=---"> - [% comp.name FILTER html %]</a> - </div> - <div class="component_desc"> - [% comp.description FILTER html_light %] - </div> - </div> -[% END %] diff --git a/extensions/BMO/web/js/edituser_menu.js b/extensions/BMO/web/js/edituser_menu.js index 7008a2b84..a300f5c24 100644 --- a/extensions/BMO/web/js/edituser_menu.js +++ b/extensions/BMO/web/js/edituser_menu.js @@ -10,7 +10,7 @@ function show_usermenu(id, email, show_edit) { { name: "Profile", callback: function () { - var href = "user_profile?login=" + encodeURIComponent(email); + var href = "user_profile?user_id=" + id; window.open(href, "_blank"); } }, diff --git a/extensions/BugModal/lib/MonkeyPatches.pm b/extensions/BugModal/lib/MonkeyPatches.pm index 88fce11af..54bd6e560 100644 --- a/extensions/BugModal/lib/MonkeyPatches.pm +++ b/extensions/BugModal/lib/MonkeyPatches.pm @@ -40,31 +40,6 @@ sub active_attachments { 1; -package Bugzilla::User; - -use 5.10.1; -use strict; -use warnings; - -sub moz_nick { - my ($self) = @_; - if (!exists $self->{moz_nick}) { - if ($self->name =~ /:?:(\S+?)\b/) { - $self->{moz_nick} = $1; - } - elsif ($self->name) { - $self->{moz_nick} = $self->name; - } - else { - $self->login =~ /^([^\@]+)\@/; - $self->{moz_nick} = $1; - } - } - return $self->{moz_nick}; -} - -1; - package Bugzilla::Attachment; use 5.10.1; diff --git a/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl b/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl index 51919ab27..36494773b 100644 --- a/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl +++ b/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl @@ -15,8 +15,8 @@ <div class="dropdown"> <button type="button" id="comment-tags-btn" arai-haspopup="true" aria-label="Tags Menu" aria-expanded="false" aria-controls="comment-tags-menu" class="dropdown-button minor">Tags ▾</button> - <ul id="comment-tags-menu" role="menu" tabindex="0" class="dropdown-content" style="display:none"> - <li class="dropdown-separator" role="presentation"> + <ul id="comment-tags-menu" role="menu" tabindex="0" class="dropdown-content left" style="display:none"> + <li role="presentation"> <a role="menuitem" tabindex="-1" data-comment-tag="">Reset</a> </li> </ul> @@ -24,19 +24,21 @@ <div class="dropdown"> <button type="button" id="view-menu-btn" arai-haspopup="true" aria-label="View Menu" aria-expanded="false" aria-controls="view-menu" class="dropdown-button minor">View ▾</button> - <ul id="view-menu" role="menu" tabindex="0" class="dropdown-content" style="display:none"> - <li class="dropdown-separator" role="presentation"> + <ul id="view-menu" role="menu" tabindex="0" class="dropdown-content left" style="display:none"> + <li role="presentation"> <a id="view-reset" role="menuitem" tabindex="-1">Reset</a> </li> + <li role="separator"></li> <li role="presentation"> <a id="view-collapse-all" role="menuitem" tabindex="-1">Collapse All</a> </li> <li role="presentation"> <a id="view-expand-all" role="menuitem" tabindex="-1">Expand All</a> </li> - <li class="dropdown-separator" role="presentation"> + <li role="presentation"> <a id="view-comments-only" role="menuitem" tabindex="-1">Comments Only</a> </li> + <li role="separator"></li> <li role="presentation"> <a id="view-toggle-cc" role="menuitem" tabindex="-1">Show CC Changes</a> </li> @@ -146,7 +148,7 @@ [% END %] <button type="button" class="reply-btn minor" data-reply-id="[% comment.count FILTER none %]" - data-reply-name="[% comment.author.name || comment.author.moz_nick FILTER html %]" + data-reply-name="[% comment.author.name || comment.author.nick FILTER html %]" >Reply</button> [% END %] <button type="button" class="change-spinner minor" id="cs-[% comment.count FILTER none %]">-</button> @@ -184,9 +186,9 @@ <tr> <td class="comment-collapse-reason" [% IF user.setting("ui_use_absolute_time") == "on" %] - title="[% comment.author.moz_nick FILTER html %] [[% comment.creation_ts FILTER time("%Y-%m-%d %H:%M %Z") FILTER html %]]"> + title="[% comment.author.nick FILTER html %] [[% comment.creation_ts FILTER time("%Y-%m-%d %H:%M %Z") FILTER html %]]"> [% ELSE %] - title="[% comment.author.moz_nick FILTER html %] [[% comment.creation_ts FILTER time_duration FILTER html %]]"> + title="[% comment.author.nick FILTER html %] [[% comment.creation_ts FILTER time_duration FILTER html %]]"> [% END %] Comment hidden ([% comment.collapsed_reason FILTER html %]) </td> @@ -383,7 +385,7 @@ value FILTER bug_list_link; ELSE; - value FILTER truncate(256, '…') FILTER html; + value FILTER truncate(256, '…') FILTER html; END; END; diff --git a/extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl b/extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl index 48c2c1803..4e740e35d 100644 --- a/extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl +++ b/extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl @@ -320,24 +320,26 @@ <div class="dropdown"> <button type="button" id="action-menu-btn" aria-haspopup="true" aria-label="Actions Menu" aria-expanded="false" aria-controls="action-menu" class="dropdown-button minor">▾</button> - <ul class="dropdown-content" id="action-menu" role="menu" style="display:none;"> + <ul class="dropdown-content left" id="action-menu" role="menu" style="display:none;"> <li role="presentation"> <a id="action-reset" role="menuitem" tabindex="-1">Reset Sections</a> </li> <li role="presentation"> <a id="action-expand-all" role="menuitem" tabindex="-1">Expand All Sections</a> </li> - <li class="dropdown-separator" role="presentation"> + <li role="presentation"> <a id="action-collapse-all" role="menuitem" tabindex="-1">Collapse All Sections</a> </li> + <li role="separator"></li> [% IF user.id %] <li role="presentation"> <a id="action-add-comment" role="menuitem" tabindex="-1">Add Comment</a> </li> [% END %] - <li class="dropdown-separator" role="presentation"> + <li role="presentation"> <a id="action-last-comment" role="menuitem" tabindex="-1">Last Comment</a> </li> + <li role="separator"></li> <li role="presentation"> <a id="action-history" role="menuitem" tabindex="-1">History</a> </li> @@ -374,17 +376,29 @@ hide_on_edit = can_edit_product help = "describecomponents.cgi?product=$filtered_product" %] - <span aria-owns="product-name product-latch"> - <span role="button" aria-label="show product information" aria-expanded="false" tabindex="0" - class="spin-latch" id="product-latch" data-latch="product" data-for="product">▸</span> - <div title="show product information" tabindex="0" class="spin-toggle" - id="product-name" data-latch="product" data-for="product"> + <div class="name-info-outer dropdown"> + <span id="product-name" class="dropdown-button" tabindex="0" role="button" + aria-haspopup="menu" aria-controls="product-info"> [% bug.product FILTER html %] - </div> - <div id="product-info" style="display:none"> - [% bug.product_obj.description FILTER html_light %] - </div> - </span> + <span class="icon" aria-hidden="true">▾</span> + </span> + <aside id="product-info" class="name-info-popup dropdown-content right hover-display" hidden role="menu" + aria-label="Product description and actions"> + <header> + <div class="title">[%~ bug.product FILTER html ~%]</div> + <div class="description">[% bug.product_obj.description FILTER html_light %]</div> + </header> + <li role="separator"></li> + <div class="actions"> + <div><a href="buglist.cgi?product=[% bug.product FILTER uri %]&bug_status=__open__" + target="_blank" role="menuitem" tabindex="-1">See Other [% terms.Bugs %]</a></div> + <div><button disabled type="button" class="minor component-watching" role="menuitem" tabindex="-1" + data-product="[% bug.product FILTER html %]" + data-label-watch="Watch This Product" data-label-unwatch="Unwatch This Product" + data-source="BugModal">Watch This Product</button></div> + </div> + </aside> + </div> [% END %] [% WRAPPER bug_modal/field.html.tmpl field = bug_fields.product @@ -417,20 +431,30 @@ help = "describecomponents.cgi?product=$filtered_product&component=$filtered_component#$filtered_component" %] - <span aria-owns="component-name component-latch"> - <span role="button" aria-label="show component description" aria-expanded="false" tabindex="0" - class="spin-latch" id="component-latch" data-latch="component" data-for="component">▸</span> - <div title="show component information" tabindex="0" class="spin-toggle" id="component-name" - data-latch="#component-latch" data-for="component"> - [% bug.component FILTER html %] - </div> - <div id="component-info" style="display:none"> - <div>[% bug.component_obj.description FILTER html_light %]</div> - <a href="buglist.cgi?component=[% bug.component FILTER uri %]& - [%~ %]product=[% bug.product FILTER uri %]& - [%~ %]bug_status=__open__" target="_blank">Other [% terms.Bugs %]</a> - </div> - </span> + <div class="name-info-outer dropdown"> + <span id="component-name" class="dropdown-button" tabindex="0" role="button" + aria-haspopup="menu" aria-controls="component-info"> + [% bug.component FILTER html %] + <span class="icon" aria-hidden="true">▾</span> + </span> + <aside id="component-info" class="name-info-popup dropdown-content right hover-display" hidden role="menu" + aria-label="Component description and actions"> + <header> + <div class="title">[%~ bug.product _ " :: " _ bug.component FILTER html ~%]</div> + <div class="description">[% bug.component_obj.description FILTER html_light %]</div> + </header> + <li role="separator"></li> + <div class="actions"> + <div><a href="buglist.cgi?product=[% bug.product FILTER uri %]& + [%~ %]component=[% bug.component FILTER uri %]&bug_status=__open__" + target="_blank" role="menuitem" tabindex="-1">See Other [% terms.Bugs %]</a></div> + <div><button disabled type="button" class="minor component-watching" role="menuitem" tabindex="-1" + data-product="[% bug.product FILTER html %]" data-component="[% bug.component FILTER html %]" + data-label-watch="Watch This Component" data-label-unwatch="Unwatch This Component" + data-source="BugModal">Watch This Component</button></div> + </div> + </aside> + </div> [% END %] [%# importance %] @@ -565,8 +589,8 @@ [% sub = []; - sub.push("Reporter: " _ bug.reporter.moz_nick); - sub.push(unassigned ? "Unassigned" : "Assigned: " _ bug.assigned_to.moz_nick); + sub.push("Reporter: " _ bug.reporter.nick); + sub.push(unassigned ? "Unassigned" : "Assigned: " _ bug.assigned_to.nick); IF bug.mentors.size; sub.push("Mentored"); END; @@ -1023,7 +1047,7 @@ [% sub = []; IF bug.status_whiteboard != ""; - sub.push("Whiteboard: " _ bug.status_whiteboard.truncate(256, '…')); + sub.push("Whiteboard: " _ bug.status_whiteboard.truncate(256, '…')); END; IF bug.cf_crash_signature != ""; sub.push("crash signature"); @@ -1325,7 +1349,7 @@ <div class="dropdown"> <button type="button" id="format-btn" aria-haspopup="true" aria-label="Format [% terms.Bug %] Menu" aria-expanded="false" aria-controls="format-menu" class="dropdown-button minor">Format [% terms.Bug %] ▴</button> - <ul class="dropdown-content menu-up" id="format-menu" role="menu" style="display:none;"> + <ul class="dropdown-content left menu-up" id="format-menu" role="menu" style="display:none;"> <li role="presentation"> <a href="show_bug.cgi?format=multiple&id=[% bug.id FILTER uri %]" role="menuitem" tabindex="-1">For Printing</a> </li> @@ -1346,7 +1370,7 @@ <div class="dropdown"> <button type="button" id="new-bug-btn" aria-haspopup="true" aria-label="New/Clone [% terms.Bug %] Menu" aria-expanded="false" aria-controls="new-bug-menu" class="dropdown-button minor">New/Clone [% terms.Bug %] ▴</button> - <ul class="dropdown-content menu-up" id="new-bug-menu" role="menu" style="display:none;"> + <ul class="dropdown-content left menu-up" id="new-bug-menu" role="menu" style="display:none;"> <li role="presentation"> <a href="enter_bug.cgi" role="menuitem" tabindex="-1" target="_blank"> Create a new [% terms.bug %]</a> @@ -1355,18 +1379,20 @@ <a href="enter_bug.cgi?product=[% bug.product FILTER uri %]" role="menuitem" tabindex="-1" target="_blank">… in this product</a> </li> - <li class="dropdown-separator" role="presentation"> + <li role="presentation"> <a href="enter_bug.cgi?product=[% bug.product FILTER uri %]&component=[% bug.component FILTER uri %]" role="menuitem" tabindex="-1" target="_blank">… in this component</a> </li> + <li role="separator"></li> <li role="presentation"> <a href="enter_bug.cgi?format=__default__&product=[% bug.product FILTER uri %]&blocked=[% bug.id FILTER uri %]" role="menuitem" tabindex="-1" target="_blank">… that blocks this [% terms.bug %]</a> </li> - <li class="dropdown-separator" role="presentation"> + <li role="presentation"> <a href="enter_bug.cgi?format=__default__&product=[% bug.product FILTER uri %]&dependson=[% bug.id FILTER uri %]" role="menuitem" tabindex="-1" target="_blank">… that depends on this [% terms.bug %]</a> </li> + <li role="separator"></li> <li role="presentation"> <a href="enter_bug.cgi?format=__default__&product=[% bug.product FILTER uri %]&cloned_bug_id=[% bug.id FILTER uri %]" role="menuitem" tabindex="-1" target="_blank">… as a clone of this [% terms.bug %]</a> diff --git a/extensions/BugModal/template/en/default/bug_modal/header.html.tmpl b/extensions/BugModal/template/en/default/bug_modal/header.html.tmpl index b9a42caf3..20561c760 100644 --- a/extensions/BugModal/template/en/default/bug_modal/header.html.tmpl +++ b/extensions/BugModal/template/en/default/bug_modal/header.html.tmpl @@ -8,6 +8,7 @@ [% PROCESS global/variables.none.tmpl; + USE Bugzilla; # <title> IF bugs.defined; @@ -53,6 +54,7 @@ "extensions/ProdCompSearch/web/js/prod_comp_search.js", "extensions/BugModal/web/bug_modal.js", "extensions/BugModal/web/comments.js", + "extensions/ComponentWatching/web/js/overlay.js", "js/bugzilla-readable-status-min.js", "js/field.js", "js/comments.js", @@ -89,6 +91,8 @@ [%# expose useful data to js %] BUGZILLA.bug_id = [% bug.id FILTER none %]; BUGZILLA.bug_title = '[% unfiltered_title FILTER js %]'; + BUGZILLA.bug_summary = '[% bug.short_desc FILTER js %]'; + BUGZILLA.bug_url = '[% Bugzilla.cgi.self_url FILTER js %]'; BUGZILLA.user = { id: [% user.id FILTER none %], login: '[% user.login FILTER js %]', diff --git a/extensions/BugModal/template/en/default/bug_modal/user.html.tmpl b/extensions/BugModal/template/en/default/bug_modal/user.html.tmpl index 9eda7b936..6a0ce4e24 100644 --- a/extensions/BugModal/template/en/default/bug_modal/user.html.tmpl +++ b/extensions/BugModal/template/en/default/bug_modal/user.html.tmpl @@ -30,9 +30,9 @@ END; [% IF simple %] [% IF user.id %] - <span class="fn" title="[% u.identity FILTER html %]">[% u.moz_nick FILTER html %]</span> + <span class="fn" title="[% u.identity FILTER html %]">[% u.nick FILTER html %]</span> [% ELSE %] - <span class="fn">[% u.moz_nick FILTER html %]</span> + <span class="fn">[% u.nick FILTER html %]</span> [% END %] [% ELSE %] @@ -52,7 +52,7 @@ END; href="user_profile?user_id=[% u.id FILTER none %]" [% END %] > - <span class="[% user.id ? 'fn' : 'fna' %]">[% nick_only ? u.moz_nick : (u.name || u.nick) FILTER html %]</span> + <span class="[% user.id ? 'fn' : 'fna' %]">[% nick_only ? u.nick : (u.name || u.nick) FILTER html %]</span> [%~~%] </a> [% END %] diff --git a/extensions/BugModal/web/bug_modal.css b/extensions/BugModal/web/bug_modal.css index a8c469ad6..ee50c6b77 100644 --- a/extensions/BugModal/web/bug_modal.css +++ b/extensions/BugModal/web/bug_modal.css @@ -44,26 +44,6 @@ button.major { padding: 4px 12px; } -button.minor { - background-color: #eee; - background-image: linear-gradient(#fcfcfc, #eee); - color: #000; - font-size: inherit; - font-weight: 500; - padding: 4px 8px; - margin-bottom: 1px; - text-shadow: none; - -web-kit-box-shadow: 0 1px 0 0 rgba(0,0,0,0.1), inset 0 -1px 0 0 rgba(0,0,0,0.1); - -moz-box-shadow: 0 1px 0 0 rgba(0,0,0,0.1), inset 0 -1px 0 0 rgba(0,0,0,0.1); - box-shadow: 0 1px 0 0 rgba(0,0,0,0.1), inset 0 -1px 0 0 rgba(0,0,0,0.1), inset 0 0 1px 0 rgba(0,0,0,0.1); -} - -button.minor:hover { - -webkit-box-shadow: 0 1px 0 0 rgba(0,0,0,0.2), inset 0 -1px 0 0 rgba(0,0,0,0.3), inset 0 12px 24px 2px #ddd; - -moz-box-shadow: 0 1px 0 0 rgba(0,0,0,0.2), inset 0 -1px 0 0 rgba(0,0,0,0.3), inset 0 12px 24px 2px #ddd; - box-shadow: 0 1px 0 0 rgba(0,0,0,0.1), inset 0 -1px 0 0 rgba(0,0,0,0.1), inset 0 12px 24px 2px #ddd; -} - select[multiple], .text_input, .yui-ac-input, input { font-size: 12px !important; } @@ -329,16 +309,6 @@ input[type="number"] { margin-bottom: 50px; } -#product-info, #component-info { - color: #484; - white-space: normal; -} - -#product-latch, #component-latch { - padding-right: 0; - cursor: pointer; -} - #cc-latch { color: #999; } @@ -968,6 +938,28 @@ div.ui-tooltip { right: 8px; } +/* product/component popup */ + +.name-info-popup { + width: 320px; +} + +.name-info-popup header { + margin: 8px 16px; +} + +.name-info-popup header .title { + margin: 0 0 4px; + font-size: 16px; +} + +.name-info-popup header .description { + font-size: 12px; + line-height: 150%; + white-space: normal; + color: #666; +} + /* product search */ #field-product { diff --git a/extensions/BugModal/web/bug_modal.js b/extensions/BugModal/web/bug_modal.js index 4a770e66c..a4ae83d72 100644 --- a/extensions/BugModal/web/bug_modal.js +++ b/extensions/BugModal/web/bug_modal.js @@ -339,10 +339,6 @@ $(function() { // copy summary to clipboard - function clipboardSummary() { - return 'Bug ' + BUGZILLA.bug_id + ' - ' + $('#field-value-short_desc').text(); - } - if ($('#copy-summary').length) { var hasExecCopy = false; try { @@ -352,11 +348,24 @@ $(function() { } if (hasExecCopy) { + const url = BUGZILLA.bug_url; + const text = `Bug ${BUGZILLA.bug_id} - ${BUGZILLA.bug_summary}`; + const html = `<a href="${url}">${text}</a>`; + + document.addEventListener('copy', event => { + if (event.target.nodeType === 1 && event.target.matches('#clip')) { + event.clipboardData.setData('text/uri-list', url); + event.clipboardData.setData('text/plain', text); + event.clipboardData.setData('text/html', html); + event.preventDefault(); + } + }); + $('#copy-summary') .click(function() { // execCommand("copy") only works on selected text $('#clip-container').show(); - $('#clip').val(clipboardSummary()).select(); + $('#clip').val(text).select(); $('#floating-message-text') .text(document.execCommand("copy") ? 'Bug summary copied!' : 'Couldn’t copy bug summary'); $('#floating-message').fadeIn(250).delay(2500).fadeOut(); @@ -377,27 +386,6 @@ $(function() { lb_show(this); }); - // when copying the bug id and summary, reformat to remove \n and alias - $(document).on( - 'copy', function(event) { - var selection = document.getSelection().toString().trim(); - var match = selection.match(/^(Bug \d+)\s*\n(.+)$/) || - selection.match(/^(Bug \d+)\s+\([^\)]+\)\s*\n(.+)$/); - if (match) { - var content = match[1] + ' - ' + match[2].trim(); - if (event.originalEvent.clipboardData) { - event.originalEvent.clipboardData.setData('text/plain', content); - } - else if (window.clipboardData) { - window.clipboardData.setData('Text', content); - } - else { - return; - } - event.preventDefault(); - } - }); - // action button actions // reset @@ -1446,46 +1434,6 @@ if (history && history.replaceState) { } } -// ajax wrapper, to simplify error handling and auth -function bugzilla_ajax(request, done_fn, error_fn) { - $('#xhr-error').hide(''); - $('#xhr-error').html(''); - request.url += (request.url.match('\\?') ? '&' : '?') + - 'Bugzilla_api_token=' + encodeURIComponent(BUGZILLA.api_token); - if (request.type != 'GET') { - request.contentType = 'application/json'; - request.processData = false; - if (request.data && request.data.constructor === Object) { - request.data = JSON.stringify(request.data); - } - } - return $.ajax(request) - .done(function(data) { - if (data.error) { - if (!request.hideError) { - $('#xhr-error').html(data.message); - $('#xhr-error').show('fast'); - } - if (error_fn) - error_fn(data.message); - } - else if (done_fn) { - done_fn(data); - } - }) - .fail(function(data) { - if (data.statusText === 'abort') - return; - var message = data.responseJSON ? data.responseJSON.message : 'Unexpected Error'; // all errors are unexpected :) - if (!request.hideError) { - $('#xhr-error').html(message); - $('#xhr-error').show('fast'); - } - if (error_fn) - error_fn(message); - }); -} - // lightbox function lb_show(el) { diff --git a/extensions/BugmailFilter/template/en/default/account/prefs/bugmail_filter.html.tmpl b/extensions/BugmailFilter/template/en/default/account/prefs/bugmail_filter.html.tmpl index bb1381c46..ff64f8323 100644 --- a/extensions/BugmailFilter/template/en/default/account/prefs/bugmail_filter.html.tmpl +++ b/extensions/BugmailFilter/template/en/default/account/prefs/bugmail_filter.html.tmpl @@ -221,7 +221,7 @@ var cpts = new Array(); [% FOREACH flag = type.flags %] [% IF flag_count > 10 && loop.count == 10 %] <span id="show_all"> - … + … (<a href="#" onclick="showAllFlags(); return false">show all</a>) </span> <span id="all_flags" class="bz_default_hidden"> diff --git a/extensions/ComponentWatching/Extension.pm b/extensions/ComponentWatching/Extension.pm index ed47e64c3..96eb877a6 100644 --- a/extensions/ComponentWatching/Extension.pm +++ b/extensions/ComponentWatching/Extension.pm @@ -469,15 +469,18 @@ sub bugmail_relationships { # sub _getWatches { - my ($user) = @_; + my ($user, $watch_id) = @_; my $dbh = Bugzilla->dbh; + $watch_id = (defined $watch_id && $watch_id =~ /^(\d+)$/) ? $1 : undef; + my $sth = $dbh->prepare(" SELECT id, product_id, component_id, component_prefix FROM component_watch - WHERE user_id = ? - "); - $sth->execute($user->id); + WHERE user_id = ?" . ($watch_id ? " AND id = ?" : "") + ); + $watch_id ? $sth->execute($user->id, $watch_id) : $sth->execute($user->id); + my @watches; while (my ($id, $productId, $componentId, $prefix) = $sth->fetchrow_array) { my $product = Bugzilla::Product->new({ id => $productId, cache => 1 }); @@ -500,6 +503,10 @@ sub _getWatches { push @watches, \%watch; } + if ($watch_id) { + return $watches[0] || {}; + } + @watches = sort { $a->{'product_name'} cmp $b->{'product_name'} || $a->{'component_name'} cmp $b->{'component_name'} @@ -565,6 +572,8 @@ sub _addProductWatch { VALUES (?, ?) "); $sth->execute($user->id, $product->id); + + return _getWatches($user, $dbh->bz_last_key()); } sub _addComponentWatch { @@ -585,6 +594,8 @@ sub _addComponentWatch { VALUES (?, ?, ?) "); $sth->execute($user->id, $component->product_id, $component->id); + + return _getWatches($user, $dbh->bz_last_key()); } sub _addPrefixWatch { @@ -620,8 +631,9 @@ sub _deleteWatch { my $dbh = Bugzilla->dbh; detaint_natural($id) || ThrowCodeError("component_watch_invalid_id"); - $dbh->do("DELETE FROM component_watch WHERE id=? AND user_id=?", - undef, $id, $user->id); + + return $dbh->do("DELETE FROM component_watch WHERE id=? AND user_id=?", + undef, $id, $user->id); } sub _addDefaultSettings { @@ -717,4 +729,13 @@ sub sanitycheck_repair { } } +# +# webservice +# + +sub webservice { + my ($self, $args) = @_; + $args->{dispatch}->{ComponentWatching} = "Bugzilla::Extension::ComponentWatching::WebService"; +} + __PACKAGE__->NAME; diff --git a/extensions/ComponentWatching/lib/WebService.pm b/extensions/ComponentWatching/lib/WebService.pm new file mode 100644 index 000000000..ba4cb0225 --- /dev/null +++ b/extensions/ComponentWatching/lib/WebService.pm @@ -0,0 +1,113 @@ +# 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. + +package Bugzilla::Extension::ComponentWatching::WebService; + +use 5.10.1; +use strict; +use warnings; + +use base qw(Bugzilla::WebService); + +use Bugzilla; +use Bugzilla::Component; +use Bugzilla::Constants; +use Bugzilla::Error; +use Bugzilla::Product; +use Bugzilla::User; + +sub rest_resources { + return [ + qr{^/component-watching$}, { + GET => { + method => 'list', + }, + POST => { + method => 'add', + }, + }, + qr{^/component-watching/(\d+)$}, { + GET => { + method => 'get', + params => sub { + return { id => $_[0] } + }, + }, + DELETE => { + method => 'remove', + params => sub { + return { id => $_[0] } + }, + }, + }, + ]; +} + +# +# API methods based on Bugzilla::Extension::ComponentWatching->user_preferences +# + +sub list { + my ($self, $params) = @_; + my $user = Bugzilla->login(LOGIN_REQUIRED); + + return Bugzilla::Extension::ComponentWatching::_getWatches($user); +} + +sub add { + my ($self, $params) = @_; + my $user = Bugzilla->login(LOGIN_REQUIRED); + my $result; + + # load product and verify access + my $productName = $params->{'product'}; + my $product = Bugzilla::Product->new({ name => $productName, cache => 1 }); + unless ($product && $user->can_access_product($product)) { + ThrowUserError('product_access_denied', { product => $productName }); + } + + my $ra_componentNames = $params->{'component'}; + $ra_componentNames = [$ra_componentNames || ''] unless ref($ra_componentNames); + + if (grep { $_ eq '' } @$ra_componentNames) { + # watching a product + $result = Bugzilla::Extension::ComponentWatching::_addProductWatch($user, $product); + + } else { + # watching specific components + foreach my $componentName (@$ra_componentNames) { + my $component = Bugzilla::Component->new({ + name => $componentName, product => $product, cache => 1 + }); + unless ($component) { + ThrowUserError('product_access_denied', { product => $productName }); + } + $result = Bugzilla::Extension::ComponentWatching::_addComponentWatch($user, $component); + } + } + + Bugzilla::Extension::ComponentWatching::_addDefaultSettings($user); + + return $result; +} + +sub get { + my ($self, $params) = @_; + my $user = Bugzilla->login(LOGIN_REQUIRED); + + return Bugzilla::Extension::ComponentWatching::_getWatches($user, $params->{'id'}); +} + +sub remove { + my ($self, $params) = @_; + my $user = Bugzilla->login(LOGIN_REQUIRED); + my %result = (status => Bugzilla::Extension::ComponentWatching::_deleteWatch($user, $params->{'id'})); + + return \%result; +} + +1; diff --git a/extensions/ComponentWatching/template/en/default/hook/reports/components-component_footer.html.tmpl b/extensions/ComponentWatching/template/en/default/hook/reports/components-component_footer.html.tmpl new file mode 100644 index 000000000..b8921bcf0 --- /dev/null +++ b/extensions/ComponentWatching/template/en/default/hook/reports/components-component_footer.html.tmpl @@ -0,0 +1,10 @@ +[%# 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. + #%] + +<button disabled type="button" class="minor component-watching" data-product="[% product.name FILTER html %]" + data-component="[% comp.name FILTER html %]" data-source="Component Description">Watch</button> diff --git a/extensions/ComponentWatching/template/en/default/hook/reports/components-product_header.html.tmpl b/extensions/ComponentWatching/template/en/default/hook/reports/components-product_header.html.tmpl new file mode 100644 index 000000000..bc7120b4e --- /dev/null +++ b/extensions/ComponentWatching/template/en/default/hook/reports/components-product_header.html.tmpl @@ -0,0 +1,10 @@ +[%# 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. + #%] + +<button disabled type="button" class="minor component-watching" data-product="[% product.name FILTER html %]" + data-source="Component Description">Watch</button> diff --git a/extensions/ComponentWatching/template/en/default/hook/reports/components-start.html.tmpl b/extensions/ComponentWatching/template/en/default/hook/reports/components-start.html.tmpl new file mode 100644 index 000000000..76cf6bc08 --- /dev/null +++ b/extensions/ComponentWatching/template/en/default/hook/reports/components-start.html.tmpl @@ -0,0 +1,12 @@ +[%# 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. + #%] + +[% + javascript_urls.push('extensions/ComponentWatching/web/js/overlay.js'); + generate_api_token = 1; +%] diff --git a/extensions/ComponentWatching/web/js/overlay.js b/extensions/ComponentWatching/web/js/overlay.js new file mode 100644 index 000000000..c0c540257 --- /dev/null +++ b/extensions/ComponentWatching/web/js/overlay.js @@ -0,0 +1,218 @@ +/* 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 || {}; + +/** + * Implement the one-click Component Watching functionality that can be added to any page. + * @abstract + */ +Bugzilla.ComponentWatching = class ComponentWatching { + /** + * Initialize a new ComponentWatching instance. Since constructors can't be async, use a separate function to move on. + */ + constructor() { + this.buttons = document.querySelectorAll('button.component-watching'); + + this.init(); + } + + /** + * Send a REST API request, and return the results in a Promise. + * @param {Object} [request={}] Request data. If omitted, all the current watches will be returned. + * @param {String} [path=''] Optional path to be appended to the request URL. + * @returns {Promise<Object|String>} Response data or error message. + */ + async fetch(request = {}, path = '') { + request.url = `/rest/component-watching${path}`; + + return new Promise((resolve, reject) => bugzilla_ajax(request, data => resolve(data), error => reject(error))); + } + + /** + * Start watching the current product or component. + * @param {String} product Product name. + * @param {String} [component=''] Component name. If omitted, all the components in the product will be watched. + * @returns {Promise<Object|String>} Response data or error message. + */ + async watch(product, component = '') { + return this.fetch({ type: 'POST', data: { product, component } }); + } + + /** + * Stop watching the current product or component. + * @param {Number} id ID of the watch to be removed. + * @returns {Promise<Object|String>} Response data or error message. + */ + async unwatch(id) { + return this.fetch({ type: 'DELETE' }, `/${id}`); + } + + /** + * Log an event with Google Analytics if possible. For privacy reasons, we don't send any specific product or + * component name. + * @param {String} source Event source that will be part of the event category. + * @param {String} action `watch` or `unwatch`. + * @param {String} type `product` or `component`. + * @param {Number} code `0` for a successful change, `1` otherwise. + * @see https://developers.google.com/analytics/devguides/collection/analyticsjs/events + */ + track_event(source, action, type, code) { + if ('ga' in window) { + ga('send', 'event', `Component Watching: ${source}`, action, type, code); + } + } + + /** + * Show a short floating message if the button is on BugModal. This code is from bug_modal.js, requiring jQuery. + * @param {String} message Message text. + */ + show_message(message) { + if (!document.querySelector('#floating-message')) { + return; + } + + $('#floating-message-text').text(message); + $('#floating-message').fadeIn(250).delay(2500).fadeOut(); + } + + /** + * Get all the component watching buttons on the current page. + * @param {String} [product] Optional product name. + * @param {String} [component] Optional component name. + * @returns {HTMLButtonElement[]} List of button elements. + */ + get_buttons(product = undefined, component = undefined) { + let buttons = [...this.buttons]; + + if (product) { + buttons = buttons.filter($button => $button.dataset.product === product); + } + + if (component) { + buttons = buttons.filter($button => $button.dataset.component === component); + } + + return buttons; + } + + /** + * Update a Watch/Unwatch button for a product or component. + * @param {HTMLButtonElement} $button Button element to be updated. + * @param {Boolean} disabled Whether the button has to be disabled. + * @param {Number} [watchId] Optional watch ID if the product or component is being watched. + */ + update_button($button, disabled, watchId = undefined) { + const { product, component } = $button.dataset; + + if (watchId) { + $button.dataset.watchId = watchId; + $button.textContent = $button.getAttribute('data-label-unwatch') || 'Unwatch'; + $button.title = component ? + `Stop watching the ${component} component` : + `Stop watching all components in the ${product} product`; + } else { + delete $button.dataset.watchId; + + $button.textContent = $button.getAttribute('data-label-watch') || 'Watch'; + $button.title = component ? + `Start watching the ${component} component` : + `Start watching all components in the ${product} product`; + } + + $button.disabled = disabled; + } + + /** + * Called whenever a Watch/Unwatch button is clicked. Send a request to update the user's watch list, and update the + * relevant buttons on the page. + * @param {HTMLButtonElement} $button Clicked button element. + */ + async button_onclick($button) { + const { product, component, watchId, source } = $button.dataset; + let message = ''; + let code = 0; + + // Disable the button until the request is complete + $button.disabled = true; + + try { + if (watchId) { + await this.unwatch(watchId); + + if (component) { + message = `You are no longer watching the ${component} component`; + + this.get_buttons(product, component).forEach($button => this.update_button($button, false)); + } else { + message = `You are no longer watching all components in the ${product} product`; + + this.get_buttons(product).forEach($button => this.update_button($button, false)); + } + } else { + const watch = await this.watch(product, component); + + if (component) { + message = `You are now watching the ${component} component`; + + this.get_buttons(product, component).forEach($button => this.update_button($button, false, watch.id)); + } else { + message = `You are now watching all components in the ${product} product`; + + this.get_buttons(product).forEach($button => { + if ($button.dataset.component) { + this.update_button($button, true); + } else { + this.update_button($button, false, watch.id); + } + }); + } + } + } catch (ex) { + message = 'Your watch list could not be updated. Please try again later.'; + code = 1; + } + + this.show_message(message); + this.track_event(source, watchId ? 'unwatch' : 'watch', component ? 'component' : 'product', code); + } + + /** + * Retrieve the current watch list, and initialize all the buttons. + */ + async init() { + try { + const all_watches = await this.fetch(); + + this.get_buttons().forEach($button => { + const { product, component } = $button.dataset; + const watches = all_watches.filter(watch => watch.product_name === product); + const product_watch = watches.find(watch => !watch.component); + + if (!component) { + // This button is for product watching + this.update_button($button, false, product_watch ? product_watch.id : undefined); + } else if (product_watch) { + // Disabled the button because all the components in the product is being watched + this.update_button($button, true); + } else { + const watch = watches.find(watch => watch.component_name === component); + + this.update_button($button, false, watch ? watch.id : undefined); + } + + $button.addEventListener('click', () => this.button_onclick($button)); + }); + } catch (ex) {} + } +}; + +window.addEventListener('DOMContentLoaded', () => new Bugzilla.ComponentWatching(), { once: true }); diff --git a/extensions/GoogleAnalytics/web/js/analytics.js b/extensions/GoogleAnalytics/web/js/analytics.js index 25f7d7527..86f1f2592 100644 --- a/extensions/GoogleAnalytics/web/js/analytics.js +++ b/extensions/GoogleAnalytics/web/js/analytics.js @@ -15,6 +15,7 @@ $(function() { ga('set', 'anonymizeIp', true); ga('set', 'location', meta.data('location')); ga('set', 'title', meta.data('title')); + ga('set', 'transport', 'beacon'); // Track page view ga('send', 'pageview'); } diff --git a/extensions/Gravatar/template/en/default/hook/bug/comments-user-image.html.tmpl b/extensions/Gravatar/template/en/default/hook/bug/comments-user-image.html.tmpl index 3788a8452..361c02d2b 100644 --- a/extensions/Gravatar/template/en/default/hook/bug/comments-user-image.html.tmpl +++ b/extensions/Gravatar/template/en/default/hook/bug/comments-user-image.html.tmpl @@ -8,11 +8,7 @@ [% IF user.settings.show_gravatars.value == 'On' %] [% IF who.last_activity_ts %] - [% IF user.id %] - <a href="user_profile?login=[% who.login FILTER uri %]"> - [% ELSE %] - <a href="user_profile?user_id=[% who.id FILTER uri %]"> - [% END %] + <a href="user_profile?user_id=[% who.id FILTER none %]"> [% END %] <img alt="User image" align="middle" src="[% who.gravatar FILTER none %]" width="32" height="32" border="0"> [% "</a>" IF who.last_activity_ts %] diff --git a/extensions/PhabBugz/lib/Feed.pm b/extensions/PhabBugz/lib/Feed.pm index c46d36c13..7d6b4e0ed 100644 --- a/extensions/PhabBugz/lib/Feed.pm +++ b/extensions/PhabBugz/lib/Feed.pm @@ -12,7 +12,7 @@ use 5.10.1; use IO::Async::Timer::Periodic; use IO::Async::Loop; use List::Util qw(first); -use List::MoreUtils qw(any); +use List::MoreUtils qw(any uniq); use Moo; use Scalar::Util qw(blessed); use Try::Tiny; @@ -322,11 +322,27 @@ sub group_query { # Make sure phab-bot also a member of the new project group so that it can # make policy changes to the private revisions - INFO("Setting project members for " . $project->name); - my $set_members = $self->get_group_members( $group ); - push @$set_members, $phab_user unless grep $_->phid eq $phab_user->phid, @$set_members; - $project->set_members( $set_members ); - $project->update(); + INFO( "Checking project members for " . $project->name ); + my $set_members = $self->get_group_members($group); + my @set_member_phids = uniq map { $_->phid } ( @$set_members, $phab_user ); + my @current_member_phids = uniq map { $_->phid } @{ $project->members }; + my ( $removed, $added ) = diff_arrays( \@current_member_phids, \@set_member_phids ); + + if (@$added) { + INFO( 'Adding project members: ' . join( ',', @$added ) ); + $project->add_member($_) foreach @$added; + } + + if (@$removed) { + INFO( 'Removing project members: ' . join( ',', @$removed ) ); + $project->remove_member($_) foreach @$removed; + } + + if (@$added || @$removed) { + my $result = $project->update(); + local Bugzilla::Logging->fields->{api_result} = $result; + INFO( "Project " . $project->name . " updated" ); + } } } @@ -424,9 +440,9 @@ sub process_revision_change { my ($timestamp) = Bugzilla->dbh->selectrow_array("SELECT NOW()"); INFO('Checking for revision attachment'); - my $attachment = create_revision_attachment($bug, $revision, $timestamp, $revision->author->bugzilla_user); - INFO('Attachment ' . $attachment->id . ' created or already exists.'); - + my $rev_attachment = create_revision_attachment($bug, $revision, $timestamp, $revision->author->bugzilla_user); + INFO('Attachment ' . $rev_attachment->id . ' created or already exists.'); + # ATTACHMENT OBSOLETES # fixup attachments on current bug @@ -567,7 +583,7 @@ sub process_revision_change { # Email changes for this revisions bug and also for any other # bugs that previously had these revision attachments foreach my $bug_id ($revision->bug_id, keys %other_bugs) { - Bugzilla::BugMail::Send($bug_id, { changer => Bugzilla->user }); + Bugzilla::BugMail::Send($bug_id, { changer => $rev_attachment->attacher }); } Bugzilla->set_user($old_user); diff --git a/extensions/PhabBugz/lib/Project.pm b/extensions/PhabBugz/lib/Project.pm index c52e1a661..b93a6eb9e 100644 --- a/extensions/PhabBugz/lib/Project.pm +++ b/extensions/PhabBugz/lib/Project.pm @@ -26,7 +26,7 @@ has id => ( is => 'ro', isa => Int ); has phid => ( is => 'ro', isa => Str ); has type => ( is => 'ro', isa => Str ); has name => ( is => 'ro', isa => Str ); -has description => ( is => 'ro', isa => Str ); +has description => ( is => 'ro', isa => Maybe[Str] ); has creation_ts => ( is => 'ro', isa => Str ); has modification_ts => ( is => 'ro', isa => Str ); has view_policy => ( is => 'ro', isa => Str ); @@ -307,7 +307,7 @@ sub set_policy { ############ sub _build_members { - my ($self) = @_; + my ( $self ) = @_; return [] unless $self->members_raw; my @phids; @@ -317,13 +317,11 @@ sub _build_members { return [] if !@phids; - my $users = Bugzilla::Extension::PhabBugz::User->match( + return Bugzilla::Extension::PhabBugz::User->match( { phids => \@phids } ); - - return [ map { $_->bugzilla_user } @$users ]; } 1; diff --git a/extensions/PhabBugz/lib/Revision.pm b/extensions/PhabBugz/lib/Revision.pm index 900454220..4e82fa500 100644 --- a/extensions/PhabBugz/lib/Revision.pm +++ b/extensions/PhabBugz/lib/Revision.pm @@ -93,6 +93,8 @@ sub new_from_query { : ""; return $class->new($result); } + + return undef; } sub BUILDARGS { diff --git a/extensions/PhabBugz/lib/User.pm b/extensions/PhabBugz/lib/User.pm index 9d4e9eef4..1bf1a842d 100644 --- a/extensions/PhabBugz/lib/User.pm +++ b/extensions/PhabBugz/lib/User.pm @@ -131,14 +131,18 @@ sub match { attachments => { 'external-accounts' => 1 } }; + # We can only fetch 100 users at a time so we need to do this in lumps my $phab_users = []; - my $result = request( 'user.search', $data ); - - if ( exists $result->{result}{data} && @{ $result->{result}{data} } ) { - foreach my $user ( @{ $result->{result}{data} } ) { - push @$phab_users, $class->new($user); + my $result; + do { + $result = request( 'user.search', $data ); + if ( exists $result->{result}{data} && @{ $result->{result}{data} } ) { + foreach my $user ( @{ $result->{result}{data} } ) { + push @$phab_users, $class->new($user); + } } - } + $data->{after} = $result->{cursor}->{after}; + } while ($result->{cursor}->{after}); return $phab_users; } diff --git a/extensions/PhabBugz/lib/Util.pm b/extensions/PhabBugz/lib/Util.pm index d25f62f68..091475718 100644 --- a/extensions/PhabBugz/lib/Util.pm +++ b/extensions/PhabBugz/lib/Util.pm @@ -146,9 +146,10 @@ sub get_attachment_revisions { my @revisions; foreach my $revision_id (@revision_ids) { - push @revisions, Bugzilla::Extension::PhabBugz::Revision->new_from_query({ + my $revision = Bugzilla::Extension::PhabBugz::Revision->new_from_query({ ids => [ $revision_id ] }); + push @revisions, $revision if $revision; } return \@revisions; diff --git a/extensions/PhabBugz/lib/WebService.pm b/extensions/PhabBugz/lib/WebService.pm index 0239ccf74..fa9306667 100644 --- a/extensions/PhabBugz/lib/WebService.pm +++ b/extensions/PhabBugz/lib/WebService.pm @@ -14,6 +14,7 @@ use warnings; use base qw(Bugzilla::WebService); use Bugzilla::Constants; +use Bugzilla::Error; use Bugzilla::User; use Bugzilla::Util qw(detaint_natural datetime_from time_ago trick_taint); use Bugzilla::WebService::Constants; @@ -29,34 +30,46 @@ use List::MoreUtils qw(any); use MIME::Base64 qw(decode_base64); use constant READ_ONLY => qw( + check_user_enter_bug_permission check_user_permission_for_bug needs_review ); use constant PUBLIC_METHODS => qw( + check_user_enter_bug_permission check_user_permission_for_bug needs_review set_build_target ); -sub check_user_permission_for_bug { - my ($self, $params) = @_; - - my $user = Bugzilla->login(LOGIN_REQUIRED); - +sub _check_phabricator { # Ensure PhabBugz is on ThrowUserError('phabricator_not_enabled') unless Bugzilla->params->{phabricator_enabled}; +} + +sub _validate_phab_user { + my ($self, $user) = @_; + + $self->_check_phabricator(); # Validate that the requesting user's email matches phab-bot ThrowUserError('phabricator_unauthorized_user') unless $user->login eq PHAB_AUTOMATION_USER; +} + +sub check_user_permission_for_bug { + my ($self, $params) = @_; + + my $user = Bugzilla->login(LOGIN_REQUIRED); + + $self->_validate_phab_user($user); # Validate that a bug id and user id are provided ThrowUserError('phabricator_invalid_request_params') unless ($params->{bug_id} && $params->{user_id}); - # Validate that the user and bug exist + # Validate that the user exists my $target_user = Bugzilla::User->check({ id => $params->{user_id}, cache => 1 }); # Send back an object which says { "result": 1|0 } @@ -65,10 +78,32 @@ sub check_user_permission_for_bug { }; } +sub check_user_enter_bug_permission { + my ($self, $params) = @_; + + my $user = Bugzilla->login(LOGIN_REQUIRED); + + $self->_validate_phab_user($user); + + # Validate that a product name and user id are provided + ThrowUserError('phabricator_invalid_request_params') + unless ($params->{product} && $params->{user_id}); + + # Validate that the user exists + my $target_user = Bugzilla::User->check({ id => $params->{user_id}, cache => 1 }); + + # Send back an object with the attribute "result" set to 1 if the user + # can enter bugs into the given product, or 0 if not. + return { + result => $target_user->can_enter_product($params->{product}) ? 1 : 0 + }; +} + sub needs_review { my ($self, $params) = @_; - ThrowUserError('phabricator_not_enabled') - unless Bugzilla->params->{phabricator_enabled}; + + $self->_check_phabricator(); + my $user = Bugzilla->login(LOGIN_REQUIRED); my $dbh = Bugzilla->dbh; @@ -169,13 +204,7 @@ sub set_build_target { my $user = Bugzilla->login(LOGIN_REQUIRED); - # Ensure PhabBugz is on - ThrowUserError('phabricator_not_enabled') - unless Bugzilla->params->{phabricator_enabled}; - - # Validate that the requesting user's email matches phab-bot - ThrowUserError('phabricator_unauthorized_user') - unless $user->login eq PHAB_AUTOMATION_USER; + $self->_validate_phab_user($user); my $revision_id = $params->{revision_id}; my $build_target = $params->{build_target}; @@ -204,13 +233,13 @@ sub rest_resources { POST => { method => 'set_build_target', params => sub { - return { + return { revision_id => $_[0], build_target => $_[1] }; } } - }, + }, # Bug permission checks qr{^/phabbugz/check_bug/(\d+)/(\d+)$}, { GET => { @@ -220,6 +249,14 @@ sub rest_resources { } } }, + qr{^/phabbugz/check_enter_bug/([^/]+)/(\d+)$}, { + GET => { + method => 'check_user_enter_bug_permission', + params => sub { + return { product => $_[0], user_id => $_[1] }; + }, + }, + }, # Review requests qw{^/phabbugz/needs_review$}, { GET => { diff --git a/extensions/Review/Extension.pm b/extensions/Review/Extension.pm index 72f16e3b6..a918a5ca5 100644 --- a/extensions/Review/Extension.pm +++ b/extensions/Review/Extension.pm @@ -25,6 +25,8 @@ use Bugzilla::Search; use Bugzilla::User; use Bugzilla::User::Setting; use Bugzilla::Util qw(clean_text datetime_from diff_arrays); +use Bugzilla::WebService::Util qw(filter_wants); +use Scalar::Util qw(blessed); use constant UNAVAILABLE_RE => qr/\b(?:unavailable|pto|away)\b/i; use constant MENTOR_LIMIT => 10; @@ -1068,4 +1070,51 @@ sub config_modify_panels { }; } +# +# hooks +# + +sub webservice_user_get { + my ($self, $args) = @_; + my ($webservice, $params, $users) = @$args{qw(webservice params users)}; + + return unless filter_wants($params, 'requests'); + + my $ids = [ + map { blessed($_->{id}) ? $_->{id}->value : $_->{id} } + grep { exists $_->{id} } + @$users + ]; + + return unless @$ids; + + my %user_map = map { $_->id => $_ } @{ Bugzilla::User->new_from_list($ids) }; + + foreach my $user (@$users) { + my $id = blessed($user->{id}) ? $user->{id}->value : $user->{id}; + my $user_obj = $user_map{$id}; + + $user->{requests} = { + review => { + blocked => $webservice->type('boolean', $user_obj->reviews_blocked), + pending => $webservice->type('int', $user_obj->{review_request_count}), + }, + feedback => { + # reviews_blocked includes feedback as well + blocked => $webservice->type('boolean', $user_obj->reviews_blocked), + pending => $webservice->type('int', $user_obj->{feedback_request_count}), + }, + needinfo => { + blocked => $webservice->type('boolean', $user_obj->needinfo_blocked), + pending => $webservice->type('int', $user_obj->{needinfo_request_count}), + }, + }; + } +} + +sub webservice_user_suggest { + my ($self, $args) = @_; + $self->webservice_user_get($args); +} + __PACKAGE__->NAME; diff --git a/extensions/UserProfile/template/en/default/hook/account/prefs/account-start.html.tmpl b/extensions/UserProfile/template/en/default/hook/account/prefs/account-start.html.tmpl index f2e3aad01..b3a2fc5ea 100644 --- a/extensions/UserProfile/template/en/default/hook/account/prefs/account-start.html.tmpl +++ b/extensions/UserProfile/template/en/default/hook/account/prefs/account-start.html.tmpl @@ -6,6 +6,6 @@ # defined by the Mozilla Public License, v. 2.0. #%] -<a href="user_profile?login=[% user.login FILTER uri %]"> +<a href="user_profile?user_id=[% user.id FILTER none %]"> [% terms.Bugzilla %] User Profile </a><br><hr> |