Skip to content
Merged
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
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
53 changes: 53 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 @@ -1148,6 +1179,14 @@ sub object_start_of_update {
my ($new_bug, $old_bug) = @$args{qw( object old_object )};
return unless $new_bug->isa('Bugzilla::Bug');

# Allow unrelated edits even if an existing URL lacks a signature.
if (any { ($new_bug->$_ // '') ne ($old_bug->$_ // '') }
qw(cf_crash_signature cf_crash_data_url))
{
_check_crash_data_url_has_signature($new_bug->cf_crash_signature,
$new_bug->cf_crash_data_url);
}

# if either the assignee or status has changed
return
unless $old_bug->assigned_to->id != $new_bug->assigned_to->id
Expand Down Expand Up @@ -1463,6 +1502,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 @@ -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 @@ -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 %]
17 changes: 7 additions & 10 deletions extensions/BMO/web/js/firefox-crash-table.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,19 +131,16 @@ window.addEventListener('DOMContentLoaded', () => {
});

const extraSocorroArgs = []
const baseUrl = "https://crash-stats.mozilla.org/search/";
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]));
}
const crashDataUrl = document.querySelector("#field-value-cf_crash_data_url [data-crash-data-url]")?.dataset.crashDataUrl;
Comment thread
dklawren marked this conversation as resolved.
if (crashDataUrl?.startsWith(baseUrl)) {
for (const [key, value] of new URL(crashDataUrl).searchParams) {
if (!sayNo.has(key)) {
extraSocorroArgs.push(encodeURIComponent(key) + "=" + encodeURIComponent(value));
}
}
});
}
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
22 changes: 20 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,23 @@ function getSignaturesFromURL(search, match) {
return result;
}

// Signatures come from the bug; dates use the graph's fixed range.
var IGNORED_SEARCH_PARAMS = ["signature", "date", "channel", "product", "version", "build_id"];

function getExtraParamsFromURL(search) {
var url = (new URLSearchParams(search)).get('url');
if (!url || !url.startsWith("https://crash-stats.mozilla.org/search/?")) {
return "";
}
var extra = "";
(new URL(url)).searchParams.forEach(function (value, key) {
if (!key.startsWith("_") && !IGNORED_SEARCH_PARAMS.includes(key)) {
extra = extra + "&" + encodeURIComponent(key) + "=" + encodeURIComponent(value);
}
});
return extra;
}

function draw() {
var channel = document.querySelector('select[name="channel"]').selectedOptions[0].value;
items = MG.convert.date(items, 'date');
Expand Down Expand Up @@ -304,6 +321,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 +353,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