Skip to content

Commit f8199d6

Browse files
committed
Merge branch 'master' into development
* master: Bumped version to 20251021.1 Bug 1991543 - Improve Bugzilla homepage layout Bug 1994219 - Bugs updated in the last 24-48 hours are marked as "Updated 1 days ago" [skip ci] Bug 1971551 - With `google-noto-sans-mono-fonts-20250301` at 13 px, on 100%-scale, ⪅ 2560 × 1440 px display, adding a URI of > 138 characters causes it to overflow the event form. Bug 1936614 - The paste-to-create-attachment submits in-progress comment as file comment which isn't expected Bug 1993167 - Email Reminders should have linked bugs as links rather than text Bug 1960047 - Primary buttons like "Submit Bug" have no keyboard focus indicator Bug 1956751 - status reset to '---' for 'status-firefox138' on bug creation page if 'Status' field changed Bug 1993332 - Create extension that allows for dynamically adding whiteboard tags to webhook payloads if cerftain criteria are matched
2 parents abbd050 + 3ca87d2 commit f8199d6

File tree

20 files changed

+670
-212
lines changed

20 files changed

+670
-212
lines changed

Bugzilla.pm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use warnings;
1313

1414
use Bugzilla::Logging;
1515

16-
our $VERSION = '20251008.1';
16+
our $VERSION = '20251021.1';
1717

1818
use Bugzilla::Auth;
1919
use Bugzilla::Auth::Persist::Cookie;

Bugzilla/Util.pm

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -649,25 +649,26 @@ sub time_ago {
649649

650650
# DateTime object or seconds
651651
my $ss = ref($param) ? time() - $param->epoch : $param;
652-
my $mm = round($ss / 60);
653-
my $hh = round($ss / (60 * 60));
654-
my $dd = round($ss / (60 * 60 * 24));
652+
# Use floor for intermediate calculations to avoid rounding issues
653+
my $mm = floor($ss / 60);
654+
my $hh = floor($ss / (60 * 60));
655+
my $dd = floor($ss / (60 * 60 * 24));
655656
# They are not the best definition of month and year,
656657
# but they should be good enough to be used here.
657-
my $mo = round($ss / (60 * 60 * 24 * 30));
658-
my $yy = round($ss / (60 * 60 * 24 * 365.2422));
658+
my $mo = floor($ss / (60 * 60 * 24 * 30));
659+
my $yy = floor($ss / (60 * 60 * 24 * 365.2422));
659660

660661
return 'Just now' if $ss < 10;
661662
return $ss . ' seconds ago' if $ss < 45;
662-
return '1 minute ago' if $ss < 90;
663+
return '1 minute ago' if $mm < 2;
663664
return $mm . ' minutes ago' if $mm < 45;
664-
return '1 hour ago' if $mm < 90;
665+
return '1 hour ago' if $hh < 2;
665666
return $hh . ' hours ago' if $hh < 24;
666-
return '1 day ago' if $hh < 36;
667+
return '1 day ago' if $dd < 2;
667668
return $dd . ' days ago' if $dd < 30;
668-
return '1 month ago' if $dd < 45;
669+
return '1 month ago' if $mo < 2;
669670
return $mo . ' months ago' if $mo < 12;
670-
return '1 year ago' if $mo < 18;
671+
return '1 year ago' if $yy < 2;
671672
return $yy . ' years ago';
672673
}
673674

@@ -976,7 +977,7 @@ sub extract_nicks {
976977

977978
# This routine is used to determine the latest versions for a
978979
# given product from an external system. We need to fail gracefully
979-
# if the external system is down for any reason. If the call fails,
980+
# if the external system is down for any reason. If the call fails,
980981
# then return undefined and let the caller decide what to do.
981982
sub fetch_product_versions {
982983
my ($product) = @_;

extensions/BugModal/web/bug_modal.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -980,6 +980,7 @@ td.flag-requestee {
980980
clear: both;
981981
margin-top: 8px;
982982
box-shadow: var(--primary-region-box-shadow);
983+
overflow-wrap: break-word;
983984
}
984985

985986
.change-set:target {

extensions/BugModal/web/bug_modal.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1546,7 +1546,6 @@ $(function() {
15461546
file_name: 'pasted.txt',
15471547
summary,
15481548
content_type: 'text/plain',
1549-
comment: event.target.value.trim(),
15501549
});
15511550

15521551
// Reload the page once upload is complete
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# This Source Code Form is subject to the terms of the Mozilla Public
2+
# License, v. 2.0. If a copy of the MPL was not distributed with this
3+
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
#
5+
# This Source Code Form is "Incompatible With Secondary Licenses", as
6+
# defined by the Mozilla Public License, v. 2.0.
7+
8+
package Bugzilla::Extension::JiraWebhookSync;
9+
10+
use 5.10.1;
11+
use strict;
12+
use warnings;
13+
14+
use constant NAME => 'JiraWebhookSync';
15+
16+
use constant REQUIRED_MODULES => [];
17+
18+
use constant OPTIONAL_MODULES => [];
19+
20+
__PACKAGE__->NAME;
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# This Source Code Form is subject to the terms of the Mozilla Public
2+
# License, v. 2.0. If a copy of the MPL was not distributed with this
3+
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
#
5+
# This Source Code Form is "Incompatible With Secondary Licenses", as
6+
# defined by the Mozilla Public License, v. 2.0.
7+
8+
package Bugzilla::Extension::JiraWebhookSync;
9+
10+
use 5.10.1;
11+
use strict;
12+
use warnings;
13+
14+
use base qw(Bugzilla::Extension);
15+
16+
use Bugzilla::Logging;
17+
use Bugzilla::Util qw(trim);
18+
19+
use JSON::MaybeXS qw(decode_json);
20+
use List::Util qw(uniq);
21+
use Mojo::URL;
22+
use Mojo::Util qw(dumper);
23+
24+
# Adds the JiraWebhookSync configuration panel to the admin interface.
25+
# This hook allows administrators to configure Jira webhook synchronization settings.
26+
sub config_add_panels {
27+
my ($self, $args) = @_;
28+
my $modules = $args->{panel_modules};
29+
$modules->{JiraWebhookSync} = 'Bugzilla::Extension::JiraWebhookSync::Config';
30+
}
31+
32+
# Modifies webhook payload before sending by adding configured whiteboard tags.
33+
# Checks if the bug's product/component matches any configuration rules and
34+
# automatically adds the corresponding whiteboard tag to the payload if matched.
35+
sub webhook_before_send {
36+
my ($self, $args) = @_;
37+
my $webhook = $args->{webhook};
38+
my $payload = $args->{payload};
39+
my $params = Bugzilla->params;
40+
41+
my $hostname = $params->{jira_webhook_sync_hostname};
42+
return if !$hostname;
43+
44+
# Only process webhooks destined for the configured Jira hostname
45+
my $uri = Mojo::URL->new($webhook->url);
46+
return if $uri->host ne $hostname;
47+
48+
# Make copy of the current whiteboard value
49+
my $whiteboard = $payload->{bug}->{whiteboard};
50+
51+
my $config = decode_json($params->{jira_webhook_sync_config});
52+
53+
my @new_tags;
54+
foreach my $whiteboard_tag (keys %{$config}) {
55+
INFO('Processing whiteboard tag in config: ' . $whiteboard_tag);
56+
57+
if (_bug_matches_rule($payload->{bug}, $config->{$whiteboard_tag})) {
58+
INFO('Bug matches rule for tag: ' . $whiteboard_tag);
59+
push @new_tags, $whiteboard_tag;
60+
}
61+
}
62+
63+
$payload->{bug}->{whiteboard}
64+
= _add_whiteboard_tags($whiteboard, \@new_tags);
65+
}
66+
67+
# Adds a whiteboard tag to the whiteboard string if it doesn't already exist.
68+
# Returns the whiteboard value with the tag in [brackets] format.
69+
# If the tag already exists, returns the whiteboard unchanged.
70+
sub _add_whiteboard_tags {
71+
my ($whiteboard, $new_tags) = @_;
72+
73+
$new_tags = [uniq @{$new_tags}]; # Remove duplicates
74+
75+
foreach my $new_tag (@{$new_tags}) {
76+
INFO("whiteboard merge: $whiteboard, $new_tag");
77+
next if $whiteboard =~ /\[\Q$new_tag\E\]/; # Whiteboard already has tag
78+
$whiteboard .= " [$new_tag]"; # Append new tag to the end
79+
}
80+
81+
return trim($whiteboard); # Trim whitespace before returning
82+
}
83+
84+
# Checks if a bug matches the criteria defined in a rule.
85+
sub _bug_matches_rule {
86+
my ($bug, $rule) = @_;
87+
88+
return 0 unless exists $rule->{product};
89+
return 0 unless $rule->{product} eq $bug->{product};
90+
91+
INFO('Product matched: ' . $rule->{product});
92+
93+
return 1 if !exists $rule->{component};
94+
95+
if ($rule->{component} eq $bug->{component}) {
96+
INFO('Component matched: ' . $rule->{component});
97+
return 1;
98+
}
99+
100+
return 0;
101+
}
102+
103+
__PACKAGE__->NAME;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# This Source Code Form is subject to the terms of the Mozilla Public
2+
# License, v. 2.0. If a copy of the MPL was not distributed with this
3+
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
#
5+
# This Source Code Form is "Incompatible With Secondary Licenses", as
6+
# defined by the Mozilla Public License, v. 2.0.
7+
8+
package Bugzilla::Extension::JiraWebhookSync::Config;
9+
10+
use 5.10.1;
11+
use strict;
12+
use warnings;
13+
14+
use Bugzilla::Config::Common;
15+
use JSON::MaybeXS qw(decode_json encode_json);
16+
17+
our $sortkey = 1300;
18+
19+
sub get_param_list {
20+
my ($class) = @_;
21+
22+
my @params = (
23+
{name => 'jira_webhook_sync_hostname', type => 't', default => ''},
24+
{
25+
name => 'jira_webhook_sync_config',
26+
type => 'l',
27+
default => '{}',
28+
checker => \&check_config,
29+
},
30+
);
31+
32+
return @params;
33+
}
34+
35+
sub check_config {
36+
my $config = shift;
37+
my $val = eval { decode_json($config) };
38+
return 'failed to parse JSON' unless defined $val;
39+
return 'value is not HASH' unless ref $val && ref $val eq 'HASH';
40+
return '';
41+
}
42+
43+
1;
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#!/usr/bin/env perl
2+
# This Source Code Form is subject to the terms of the Mozilla Public
3+
# License, v. 2.0. If a copy of the MPL was not distributed with this
4+
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
5+
#
6+
# This Source Code Form is "Incompatible With Secondary Licenses", as
7+
# defined by the Mozilla Public License, v. 2.0.
8+
9+
use 5.10.1;
10+
use strict;
11+
use warnings;
12+
use lib qw(. lib local/lib/perl5 qa/t/lib);
13+
14+
use Bugzilla;
15+
BEGIN { Bugzilla->extensions }
16+
17+
use Mojo::Util qw(dumper);
18+
use QA::Util;
19+
use Test::More "no_plan";
20+
use Test::Mojo;
21+
22+
my ($sel, $config) = get_selenium();
23+
24+
log_in($sel, $config, 'admin');
25+
set_parameters(
26+
$sel,
27+
{
28+
'Webhooks' => {
29+
'webhooks_enabled-on' => undef,
30+
'webhooks_group' => {type => 'select', value => 'editbugs'},
31+
},
32+
'Jira Webhook Sync' => {
33+
'jira_webhook_sync_hostname' => {type => 'text', value => 'externalapi.test'},
34+
'jira_webhook_sync_config' => {
35+
type => 'text',
36+
value => '{"BZFF": {"product": "Firefox", "component": "General"}}'
37+
},
38+
},
39+
}
40+
);
41+
42+
# Enable global push daemon support
43+
go_to_admin($sel);
44+
$sel->click_ok('link=Configuration');
45+
$sel->title_is('Push Administration: Configuration', 'Push configuration');
46+
$sel->select_ok('global_enabled', 'label=Enabled');
47+
$sel->click_ok('//input[@type="submit" and @value="Submit Changes"]');
48+
$sel->is_text_present_ok('Changes to the configuration have been saved');
49+
logout($sel);
50+
51+
# Login as editbugs user add a new webhook
52+
log_in($sel, $config, 'editbugs');
53+
$sel->click_ok('header-account-menu-button');
54+
$sel->click_ok("//a[./span[contains(text(), 'Preferences')]]");
55+
$sel->wait_for_page_to_load_ok(WAIT_TIME);
56+
$sel->title_is('User Preferences', 'User preferences');
57+
$sel->click_ok('link=Webhooks');
58+
$sel->type_ok('name', 'Jira Sync Webhook');
59+
$sel->type_ok('url', 'http://externalapi.test:8001/webhooks/store_payload');
60+
$sel->check_ok('create_event');
61+
$sel->select_ok('product', 'value=Firefox');
62+
$sel->click_ok('add_webhook');
63+
$sel->is_text_present_ok('Jira Sync Webhook');
64+
$sel->is_text_present_ok('create');
65+
66+
# File a new bug in the Firefox product and General component
67+
# The BZFF whiteboard tag should be added to the bug.
68+
file_bug_in_product($sel, 'Firefox');
69+
my $bug_summary = 'Test bug for webhooks 1';
70+
$sel->select_ok('component', 'value=General');
71+
$sel->type_ok('short_desc', $bug_summary);
72+
$sel->type_ok('comment', $bug_summary);
73+
my $bug_id_1 = create_bug($sel, $bug_summary);
74+
75+
# Give run push extension to pick up the new events
76+
Bugzilla->push_ext->push();
77+
78+
# Call the endpoint to get back the json that was sent
79+
my $t = Test::Mojo->new();
80+
$t->get_ok('http://externalapi.test:8001/webhooks/last_payload')
81+
->status_is(200)
82+
->json_is('/event/routing_key', 'bug.create')
83+
->json_is('/bug/id', $bug_id_1)
84+
->json_is('/bug/summary', $bug_summary)
85+
->json_is('/bug/product', 'Firefox')
86+
->json_is('/bug/component', 'General')
87+
->json_is('/bug/whiteboard', '[BZFF]');
88+
89+
# File a new bug in the Firefox product and Install component
90+
# The BZFF whiteboard tag should not be added to the bug.
91+
file_bug_in_product($sel, 'Firefox');
92+
$bug_summary = 'Test bug for webhooks 2';
93+
$sel->select_ok('component', 'value=Installer');
94+
$sel->type_ok('short_desc', $bug_summary);
95+
$sel->type_ok('comment', $bug_summary);
96+
my $bug_id_2 = create_bug($sel, $bug_summary);
97+
logout($sel);
98+
99+
# Give run push extension to pick up the new events
100+
Bugzilla->push_ext->push();
101+
102+
# Call the endpoint to get back the json that was sent
103+
$t->get_ok('http://externalapi.test:8001/webhooks/last_payload')
104+
->status_is(200)
105+
->json_is('/event/routing_key', 'bug.create')
106+
->json_is('/bug/id', $bug_id_2)
107+
->json_is('/bug/summary', $bug_summary)
108+
->json_is('/bug/product', 'Firefox')
109+
->json_is('/bug/component', 'Installer')
110+
->json_is('/bug/whiteboard', '');
111+
112+
# Turn off webhooks
113+
log_in($sel, $config, 'admin');
114+
set_parameters($sel, {'Webhooks' => {'webhooks_enabled-off' => undef,}});
115+
logout($sel);
116+
117+
done_testing();
118+
119+
1;

0 commit comments

Comments
 (0)