Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Bugzilla/Bug.pm
Original file line number Diff line number Diff line change
Expand Up @@ -2740,6 +2740,11 @@ sub set_all {
# do that here, because if they *did* change the assignee, qa, or CC,
# then we don't want to check the original ones, only the new ones.
$self->_check_strict_isolation() if $product_changed;

# BMO - allow extensions to validate the final values. Unlike
# object_end_of_set_all, this runs after custom fields have been set.
Bugzilla::Hook::process('bug_end_of_set_all',
{bug => $self, params => $params});
}

# Helper for set_all that helps with fields that have an "add/remove"
Expand Down
11 changes: 9 additions & 2 deletions Bugzilla/Template.pm
Original file line number Diff line number Diff line change
Expand Up @@ -1047,16 +1047,23 @@ sub create {
'default_authorizer' => sub { return Bugzilla::Auth->new() },

'socorro_lens_url' => sub {
my ($sigs) = @_;
my ($sigs, $crash_data_url) = @_;

# strip [@ ] from sigs
my @sigs = map {/^\[\@\s*(.+?)\s*\]$/} @$sigs;

return '' unless @sigs;

my @query = ('id' => 'socorro-lens.html', 's' => join("\\", @sigs));

# Pass the search URL to socorro-lens for filtering.
push @query, 'url' => $crash_data_url
if defined $crash_data_url
&& $crash_data_url =~ m{^https://crash-stats\.mozilla\.org/search/\?};

# use a URI object to encode the query string part.
my $uri = URI->new(Bugzilla->localconfig->urlbase . 'page.cgi');
$uri->query_form({'id' => 'socorro-lens.html', 's' => join("\\", @sigs)});
$uri->query_form(\@query);
return $uri;
},
},
Expand Down
70 changes: 70 additions & 0 deletions extensions/BMO/Extension.pm
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,34 @@ sub object_columns {
);
}

sub object_validators {
my ($self, $args) = @_;
my ($class, $validators) = @$args{qw(class validators)};
return unless $class->isa('Bugzilla::Bug');
$validators->{cf_crash_data_url} = \&_check_crash_data_url;
}

sub _check_crash_data_url {
my ($invocant, $url) = @_;
my $prefix = 'https://crash-stats.mozilla.org/search/?';
$url = defined $url ? trim($url) : '';
return $url if $url eq '' || index($url, $prefix) == 0;
ThrowUserError('crash_data_url_invalid', {url => $url, prefix => $prefix});
}

# Check after both fields have been validated so simultaneous edits work.
sub _check_crash_data_url_has_signature {
my ($signature, $url) = @_;
ThrowUserError('crash_data_url_without_signature')
if ($url // '') ne '' && ($signature // '') eq '';
}

sub webservice_error_codes {
my ($self, $args) = @_;
$args->{error_map}->{crash_data_url_invalid} = 112; # Bug URL errors
$args->{error_map}->{crash_data_url_without_signature} = 112;
}

sub object_update_columns {
my ($self, $args) = @_;
return unless $args->{object}->isa('Bugzilla::Product');
Expand Down Expand Up @@ -1111,6 +1139,9 @@ sub bug_end_of_create_validators {
my ($self, $args) = @_;
my $params = $args->{params};

_check_crash_data_url_has_signature($params->{cf_crash_signature},
$params->{cf_crash_data_url});

# silently drop changes to the triaged keyword from users without permissions
# to triage
return if Bugzilla->user->can_triage;
Expand Down Expand Up @@ -1141,6 +1172,31 @@ sub db_sanitize {
Bugzilla->dbh->do("TRUNCATE TABLE bug_user_agent");
}

sub bug_start_of_set_all {
my ($self, $args) = @_;
my $bug = $args->{bug};

# Remember the crash fields so bug_end_of_set_all can tell if they changed.
# The accessor only exists once checksetup.pl has created the field.
$bug->{_crash_fields_before_set_all}
= [map { $bug->$_ // '' } qw(cf_crash_signature cf_crash_data_url)]
if $bug->can('cf_crash_data_url');
}

# Check here rather than in update() so that a mass edit is rejected before
# any bug is written.
sub bug_end_of_set_all {
my ($self, $args) = @_;
my $bug = $args->{bug};
my $before = delete $bug->{_crash_fields_before_set_all} or return;
my ($signature, $url)
= map { $bug->$_ // '' } qw(cf_crash_signature cf_crash_data_url);

# Allow unrelated edits even if an existing URL lacks a signature.
return if $signature eq $before->[0] && $url eq $before->[1];
_check_crash_data_url_has_signature($signature, $url);
}

# bugs in an ASSIGNED state must be assigned to a real person
# reset bugs to NEW if the assignee is nobody/.bugs$
sub object_start_of_update {
Expand Down Expand Up @@ -1463,6 +1519,20 @@ sub install_update_db {
});
}

# Use MEDIUMTEXT to support search URLs longer than 255 characters.
unless (Bugzilla::Field->new({name => 'cf_crash_data_url'})) {
Bugzilla::Field->create({
name => 'cf_crash_data_url',
description => 'Crash Data URL',
type => FIELD_TYPE_TEXTAREA,
mailhead => 0,
enter_bug => 1,
obsolete => 0,
custom => 1,
buglist => 0,
});
}

# Add default security group id column
if (!$dbh->bz_column_info('products', 'security_group_id')) {
$dbh->bz_add_column(
Expand Down
2 changes: 1 addition & 1 deletion extensions/BMO/lib/Data.pm
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ tie(
"Infrastructure & Operations" => ["RelOps", "RelOps: Puppet", "DCOps",],
},
qr/^cf_office$/ => {"mozilla.org" => ["Server Operations: Desktop Issues"],},
qr/^cf_crash_signature$/ => {
qr/^cf_crash_(?:signature|data_url)$/ => {
"Add-on SDK" => [],
"addons.mozilla.org" => [],
"Android Background Services" => [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

[% style_urls.push('extensions/BMO/web/styles/edit_bug.css') %]
[% javascript_urls.push('extensions/BMO/web/js/edit_bug.js') %]
[% javascript_urls.push("extensions/BMO/web/js/crash-data-url.js") %]
[% javascript_urls.push("extensions/BMO/web/js/firefox-crash-table.js") %]
[% IF user.in_group('bounty-team') %]
[% javascript_urls.push('extensions/BMO/web/js/attachment_bounty_form.js') %]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,46 @@
[% END %]
</div>
[% END %]
[%# socorro_lens_url() is defined in Bugzilla/Template.pm %]
[% SET lens_url = socorro_lens_url(split_cf_crash_signature) %]
[% IF lens_url %]
<iframe src="[% lens_url FILTER html %]" style="border:0; width:300px; height:200px; max-width:600px; max-height:300px; float:left;" id="chart" scrolling="no"></iframe>
[% END %]
[% ELSIF value %]
<span class="multiline-value">[% value FILTER html FILTER html_line_break %]</span>
[% ELSE%]
<em>None</em>
[% END %]
[% END %]

[%
rendered_custom_fields.push('cf_crash_data_url');
WRAPPER bug_modal/field.html.tmpl
label = "URL"
help = "https://wiki.mozilla.org/BMO/UserGuide/BugFields#crash_data_url"
field = bug_fields.cf_crash_data_url
field_type = constants.FIELD_TYPE_FREETEXT
hide_on_view = bug.cf_crash_data_url == ""
default_value = "https://crash-stats.mozilla.org/search/?…"
%]
[%# firefox-crash-table.js reads this attribute independently of the link. %]
<span data-crash-data-url="[% bug.cf_crash_data_url FILTER html %]">
[%# is_safe_url() rejects valid URLs ending in ")". %]
[% IF bug.cf_crash_data_url.match('^https://crash-stats\.mozilla\.org/search/\?') %]
<a href="[% bug.cf_crash_data_url FILTER html %]" target="_blank" rel="nofollow noreferrer"
title="[% bug.cf_crash_data_url FILTER truncate(256) FILTER html %]">
[%~ bug.cf_crash_data_url FILTER pretty_url FILTER truncate(40) FILTER html ~%]
</a>
[% ELSE %]
[% bug.cf_crash_data_url FILTER html %]
[% END %]
</span>
[% END %]

[% IF split_cf_crash_signature.size %]
[%# socorro_lens_url() is defined in Bugzilla/Template.pm %]
[% SET lens_url = socorro_lens_url(split_cf_crash_signature, bug.cf_crash_data_url) %]
[% IF lens_url %]
<div class="field edit-hide" id="field-crash-graph">
<div class="name"></div>
<div class="value">
<iframe src="[% lens_url FILTER html %]" style="border:0; width:300px; height:200px; max-width:600px; max-height:300px; display:block;" id="chart" scrolling="no"></iframe>
</div>
</div>
[% END %]
[% END %]
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@
#%]

[% style_urls.push("extensions/BMO/web/styles/bug_modal.css") %]
[% javascript_urls.push("extensions/BMO/web/js/crash-data-url.js") %]
[% javascript_urls.push("extensions/BMO/web/js/firefox-crash-table.js") %]
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,13 @@
[% title = "Missing Bug Severity" %]
Severity of S1-S4 is required to mark a [% terms.bug %] as triaged.

[% ELSIF error == "crash_data_url_invalid" %]
[% title = "Invalid Crash Data URL" %]
<code>[% url FILTER html %]</code> is not a valid Crash Data URL: it must be a
Socorro search URL starting with <code>[% prefix FILTER html %]</code>.

[% ELSIF error == "crash_data_url_without_signature" %]
[% title = "Missing Crash Signature" %]
A Crash Data URL can only be set on [% terms.abug %] with a crash signature.

[% END %]
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<script [% script_nonce FILTER none %] src="[% 'js/jquery/jquery-min.js"' FILTER version %]"></script>
<script [% script_nonce FILTER none %] src="[% 'extensions/BMO/web/socorro-lens/d3.min.js' FILTER version %]" charset="utf-8"></script>
<script [% script_nonce FILTER none %] src="[% 'extensions/BMO/web/socorro-lens/metricsgraphics.min.js' FILTER version %]"></script>
<script [% script_nonce FILTER none %] src="[% 'extensions/BMO/web/js/crash-data-url.js' FILTER version %]"></script>
<script [% script_nonce FILTER none %] src="[% 'extensions/BMO/web/socorro-lens/main.js' FILTER version %]"></script>
</head>
<body>
Expand Down
37 changes: 37 additions & 0 deletions extensions/BMO/web/js/crash-data-url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/* 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/. */

/**
* Extract the Socorro filters of a Crash Data URL.
* Shared by the crash graph (socorro-lens) and the crash table so that both
* apply the same filters.
*/
var CrashDataUrl = (function () {
"use strict";

var SEARCH_URL = "https://crash-stats.mozilla.org/search/?";

// Signatures come from the bug, and each widget sets its own date range,
// channels and versions. Parameters starting with "_" are display options.
var IGNORED_PARAMS = ["signature", "date", "channel", "product", "version", "build_id"];

// Return the filters as an array of encoded "key=value" strings.
function getFilters(url) {
var filters = [];
if (!url || !url.startsWith(SEARCH_URL)) {
return filters;
}
(new URL(url)).searchParams.forEach(function (value, key) {
if (!key.startsWith("_") && !IGNORED_PARAMS.includes(key)) {
filters.push(encodeURIComponent(key) + "=" + encodeURIComponent(value));
}
});
return filters;
}

return {
SEARCH_URL: SEARCH_URL,
getFilters: getFilters,
};
})();
22 changes: 8 additions & 14 deletions extensions/BMO/web/js/firefox-crash-table.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,20 +130,14 @@ window.addEventListener('DOMContentLoaded', () => {
productDetails = getMajors(data);
});

const extraSocorroArgs = []
const baseUrl = "https://crash-stats.mozilla.org/search/";
const sayNo = new Set(["_columns", "_facets", "_facets_size", "_sort", "_results_number", "date", "channel", "product", "version", "build_id"]);
const urlSelector = oldWay ? "bz_url_edit_container" : "field-value-bug_file_loc";
document.querySelectorAll("#" + urlSelector + " a").forEach(a => {
if (a.href.startsWith(baseUrl)) {
const params = new URLSearchParams(a.href.slice(baseUrl.length));
for (let p of params) {
if (!sayNo.has(p[0])) {
extraSocorroArgs.push(p[0] + "=" + encodeURIComponent(p[1]));
}
}
}
});
let crashDataUrl = document.querySelector("#field-value-cf_crash_data_url [data-crash-data-url]")?.dataset.crashDataUrl;
if (!crashDataUrl) {
// Older bugs have their Socorro search URL in the URL field.
const urlSelector = oldWay ? "bz_url_edit_container" : "field-value-bug_file_loc";
crashDataUrl = [...document.querySelectorAll("#" + urlSelector + " a")]
.find(a => a.href.startsWith(CrashDataUrl.SEARCH_URL))?.href;
}
const extraSocorroArgs = CrashDataUrl.getFilters(crashDataUrl);
const hgurlPattern = new RegExp("^http[s]?://hg\\.mozilla\\.org/(?:releases/)?mozilla-([^/]*)/rev/([0-9a-f]+)$");
const esrPattern = new RegExp("^esr[0-9]+$");
const repos = new Set(["central", "beta", "release"]);
Expand Down
12 changes: 10 additions & 2 deletions extensions/BMO/web/socorro-lens/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,13 @@ function getSignaturesFromURL(search, match) {
return result;
}

function getExtraParamsFromURL(search) {
var url = (new URLSearchParams(search)).get('url');
return CrashDataUrl.getFilters(url).map(function (filter) {
return "&" + filter;
}).join("");
}

function draw() {
var channel = document.querySelector('select[name="channel"]').selectedOptions[0].value;
items = MG.convert.date(items, 'date');
Expand Down Expand Up @@ -304,6 +311,7 @@ function redraw(channel) {
function loadGraph(search, match = 'exact') {
// Get all signatures from the Bugzilla page
var signatures = getSignaturesFromURL(search, match);
var extra = getExtraParamsFromURL(search);
// Initialize chart data
// NOTE: Our Socorro query will request 179 days' worth of data: starting
// 180 calendar-days ago, ending at (**and not including**) yesterday.
Expand Down Expand Up @@ -335,9 +343,9 @@ function loadGraph(search, match = 'exact') {
var processed_data = 0;
// Set the report URL, this will be used to load the data report on click
if (!globals.url[i]) globals.url[i] = "";
globals.url[i] = globals.url_base + signatures[i];
globals.url[i] = globals.url_base + signatures[i] + extra;
// Iterate through the Socorro data and create the chart data object
var url = "https://crash-stats.mozilla.org/api/SuperSearch/?" + signatures[i] + "&date=%3E%3D" + start_date + "&date=%3C%3D" + end_date + "&_histogram.date=release_channel&_histogram_interval=1d&_results_number=0";
var url = "https://crash-stats.mozilla.org/api/SuperSearch/?" + signatures[i] + extra + "&date=%3E%3D" + start_date + "&date=%3C%3D" + end_date + "&_histogram.date=release_channel&_histogram_interval=1d&_results_number=0";
url = url.replace("/?&signature", "/?signature");
d3.json(url, function (data) {
if (data.total > 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -640,7 +640,7 @@
[%
FOREACH field IN custom_fields;
NEXT IF field.type != constants.FIELD_TYPE_TEXTAREA;
NEXT IF field.name == "cf_crash_signature"; # Rendered below
NEXT IF field.name == "cf_crash_signature" || field.name == "cf_crash_data_url"; # Rendered below
Hook.process('custom_field-' _ field.name);
NEXT IF rendered_custom_fields.exists(field.name);
INCLUDE bug_modal/field.html.tmpl
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1255,7 +1255,7 @@
[%
FOREACH field IN custom_fields;
NEXT IF field.type != constants.FIELD_TYPE_TEXTAREA;
NEXT IF field.name == "cf_crash_signature"; # Rendered below
NEXT IF field.name == "cf_crash_signature" || field.name == "cf_crash_data_url"; # Rendered below
Hook.process('custom_field-' _ field.name);
NEXT IF rendered_custom_fields.exists(field.name);
INCLUDE bug_modal/field.html.tmpl
Expand Down
Loading
Loading