Merge pull request #1219 from wlach/1234621

Bug 1234621 - Add basic client-side filtering to perfherder alerts
This commit is contained in:
autolander 2015-12-22 12:43:37 -08:00
Родитель d66d83ea6f 8e4cd5d851
Коммит 696476c268
5 изменённых файлов: 231 добавлений и 167 удалений

Просмотреть файл

@ -21,6 +21,9 @@ class TestOptionsSerializer(serializers.JSONField):
class PerformanceSignatureSerializer(serializers.ModelSerializer):
framework_id = serializers.SlugRelatedField(
slug_field="id", source="framework",
queryset=PerformanceFramework.objects.all())
option_collection_hash = serializers.SlugRelatedField(
read_only=True, slug_field="option_collection_hash",
source="option_collection")
@ -32,9 +35,9 @@ class PerformanceSignatureSerializer(serializers.ModelSerializer):
class Meta:
model = PerformanceSignature
fields = ['signature_hash', 'machine_platform', 'suite', 'test',
'lower_is_better', 'option_collection_hash',
'test_options']
fields = ['framework_id', 'signature_hash', 'machine_platform',
'suite', 'test', 'lower_is_better',
'option_collection_hash', 'test_options']
class PerformanceDecimalField(serializers.DecimalField):

Просмотреть файл

@ -83,16 +83,16 @@ perf.controller(
perf.controller('AlertsCtrl', [
'$state', '$stateParams', '$scope', '$rootScope', '$http', '$q', '$modal',
'thUrl', 'ThRepositoryModel', 'ThOptionCollectionModel', 'ThResultSetModel',
'thDefaultRepo', 'PhSeries', 'PhAlerts', 'phTimeRanges', 'phDefaultTimeRangeValue',
'phAlertResolutionMap', 'dateFilter', 'thDateFormat',
'thUrl', 'ThOptionCollectionModel', 'ThResultSetModel',
'PhFramework', 'PhSeries', 'PhAlerts', 'phTimeRanges',
'phDefaultTimeRangeValue', 'phAlertResolutionMap', 'dateFilter',
'thDateFormat',
function AlertsCtrl($state, $stateParams, $scope, $rootScope, $http, $q,
$modal,
thUrl, ThRepositoryModel, ThOptionCollectionModel,
ThResultSetModel, thDefaultRepo, PhSeries, PhAlerts,
phTimeRanges, phDefaultTimeRangeValue,
phAlertResolutionMap, dateFilter, thDateFormat) {
$modal, thUrl, ThOptionCollectionModel,
ThResultSetModel, PhFramework,
PhSeries, PhAlerts, phTimeRanges,
phDefaultTimeRangeValue, phAlertResolutionMap,
dateFilter, thDateFormat) {
$scope.alertSummaries = [];
$scope.getMoreAlertSummariesHref = null;
$scope.getCappedMagnitude = function(percent) {
@ -102,7 +102,6 @@ perf.controller('AlertsCtrl', [
};
$scope.phAlertResolutionMap = phAlertResolutionMap;
// these methods
$scope.changeAlertSummaryStatus = function(alertSummary, status) {
PhAlerts.changeAlertSummaryStatus(
alertSummary.id, status).then(function() {
@ -110,6 +109,41 @@ perf.controller('AlertsCtrl', [
});
};
function updateAlertVisibility() {
_.forEach($scope.alertSummaries, function(alertSummary) {
_.forEach(alertSummary.alerts, function(alert) {
// only show alert if it passes all filter criteria
alert.visible =
(alert.series_signature.framework_id === $scope.filterOptions.framework.id) &&
(!$scope.filterOptions.hideImprovements || alert.is_regression) &&
_.every($scope.filterOptions.filter.split(' '),
function(matchText) {
return !matchText ||
alert.title.toLowerCase().indexOf(
matchText.toLowerCase()) > (-1);
});
});
alertSummary.anyVisible = _.any(alertSummary.alerts,
'visible');
});
$scope.numFilteredAlertSummaries = _.filter($scope.alertSummaries, { anyVisible: false }).length;
}
$scope.filtersUpdated = function() {
$state.transitionTo('alerts', {
framework: $scope.filterOptions.framework.id,
filter: $scope.filterOptions.filter,
hideImprovements: Boolean($scope.filterOptions.hideImprovements) ? undefined : 0,
}, {
location: true,
inherit: true,
relative: $state.$current,
notify: false
});
updateAlertVisibility();
};
// these methods handle the business logic of alert selection and
// unselection
$scope.anySelected = function(alerts) {
@ -274,6 +308,7 @@ perf.controller('AlertsCtrl', [
})).then(function() {
$scope.alertSummaries = _.union($scope.alertSummaries,
alertSummaries);
updateAlertVisibility();
});
}
@ -284,27 +319,33 @@ perf.controller('AlertsCtrl', [
});
};
ThRepositoryModel.get_list().then(function(response) {
$scope.projects = response.data;
$scope.selectedProject = _.findWhere($scope.projects, {
name: thDefaultRepo ? thDefaultRepo : thDefaultRepo
});
ThOptionCollectionModel.get_map().then(
function(optionCollectionMap) {
$scope.optionCollectionMap = optionCollectionMap;
if ($stateParams.id) {
PhAlerts.getAlertSummary($stateParams.id).then(
function(data) {
addAlertSummaries([data], null);
});
} else {
PhAlerts.getAlertSummaries().then(function(data) {
addAlertSummaries(data.results, data.next);
});
}
});
});
$q.all([PhFramework.getFrameworkList().then(
function(frameworks) {
$scope.frameworks = frameworks.data;
}),
ThOptionCollectionModel.get_map().then(
function(optionCollectionMap) {
$scope.optionCollectionMap = optionCollectionMap;
})]
).then(function() {
$scope.filterOptions = {
framework: _.find($scope.frameworks, {
id: parseInt($stateParams.framework)
}) || $scope.frameworks[0],
filter: $stateParams.filter || "",
hideImprovements: $stateParams.hideImprovements === undefined ||
parseInt($stateParams.hideImprovements)
};
if ($stateParams.id) {
PhAlerts.getAlertSummary($stateParams.id).then(
function(data) {
addAlertSummaries([data], null);
});
} else {
PhAlerts.getAlertSummaries().then(function(data) {
addAlertSummaries(data.results, data.next);
});
}
});
}
]);

Просмотреть файл

@ -12,7 +12,7 @@ perf.config(function($compileProvider, $httpProvider, $stateProvider, $urlRouter
$stateProvider.state('alerts', {
title: 'Perfherder Alerts',
templateUrl: 'partials/perf/alertsctrl.html',
url: '/alerts?id',
url: '/alerts?id&framework&filter&hideImprovements',
controller: 'AlertsCtrl'
}).state('graphs', {
title: 'Perfherder Graphs',

Просмотреть файл

@ -1,144 +1,164 @@
<div class="container-fluid">
<div class="alert alert-danger" role="alert">
Perfherder alerts are experimental and not to be trusted (yet)
</div>
<div class="alert alert-warning" ng-show="!user.is_staff" role="alert">
You must be logged into perfherder/treeherder and be a sheriff to make changes
</div>
<hr/>
<div class="panel panel-default alert-summary" ng-repeat="alertSummary in alertSummaries">
<div class="panel-heading">
<a href="#/alerts?id={{alertSummary.id}}" ng-class="{'alert-title-invalid': alertSummary.status==4}" class="alert-title">
Alert #{{alertSummary.id}} - {{alertSummary.title}} <span class="fa fa-external-link icon-superscript"/>
</a>
<ul class="list-unstyled alert-metadata">
<li>{{alertSummary.platforms.join(' / ')}}</li>
<li>{{alertSummary.resultSetMetadata.dateStr}}</li>
<li ng-if="alertSummary.pushURL">
<a href="{{alertSummary.pushURL}}">{{alertSummary.resultSetMetadata.revision}}…{{alertSummary.prevResultSetMetadata.revision}}</a>
</li>
<li ng-if="!alertSummary.pushURL">
<!-- This shouldn't happen in production, only in development -->
<span class="text-danger">Unknown revision(s)</span>
</li>
</ul>
<form class="form-inline">
<div class="form-group">
<select ng-model="filterOptions.framework"
ng-options="framework.name for framework in frameworks track by framework.id"
ng-change="filtersUpdated()"/>
</div>
<div class="panel-body">
<h4>Detected changes</h4>
<table class="table table-striped compare-table">
<tr>
<th style="width:16px;">
<input type="checkbox" ng-disabled="!user.is_staff" ng-model="alertSummary.allSelected" ng-change="selectNoneOrSelectAll(alertSummary)"/><!-- select 'em all checkbox --></th>
<!-- Manually specify table widths because it's just easier this way -->
<th class="test-title">
<span style=" word-wrap: break-word;">
Test
</span>
</th>
<th style="width: 140px;">Previous</th>
<th style="width: 30px;"><!-- less than / greater than --></th>
<th style="width: 140px;">New</th>
<th style="width: 80px;">Delta</th>
<th style="width: 120px"><!-- Graphical difference --></th>
<th style="width: 100px;">Confidence</th>
</tr>
<tr ng-repeat="alert in alertSummary.alerts">
<td>
<input type="checkbox" ng-disabled="!user.is_staff" ng-model="alert.selected" ng-change="alertSelected(alertSummary)"/>
</td>
<td class="test-title">
<span ng-class="{'alert-strike': (alert.revised_summary_id || alert.status === phAlertResolutionMap.INVALID)}">
{{alert.title}}
</span>
<span ng-show="alert.status === phAlertResolutionMap.INVALID">
&nbsp;(<span class="alert-invalid">invalid</span>)
</span>
<span ng-show="alert.status === phAlertResolutionMap.UNTRIAGED">
&nbsp;(<span class="alert-untriaged">untriaged</span>)
</span>
<span ng-show="alert.bug_number">
&nbsp;(<a href="https://bugzilla.mozilla.org/show_bug.cgi?id={{alert.bug_number}}">bug #{{alert.bug_number}}</a>)
</span>
<span ng-show="alert.revised_summary_id">
&nbsp;(see
<a href="#/alerts?id={{alert.revised_summary_id}}">alert #{{alert.revised_summary_id}}</a>)
</span>&nbsp;&nbsp;
<span class="result-links">
<a href="#/graphs?timerange={{alertSummary.resultSetMetadata.timeRange}}&series=[{{alertSummary.repository}},{{alert.series_signature.signature_hash}},1]&highlightedRevisions={{alertSummary.resultSetMetadata.revision}}">graph</a>
</span>
</td>
<td>{{alert.prev_value}}</td>
<td>
<span ng-class="{'compare-improvement': !alert.is_regression, 'compare-regression': alert.is_regression}">
<span ng-if="alert.prev_value < alert.new_value">
&lt;
</span>
<span ng-if="alert.prev_value > alert.new_value">
&gt;
</span>
</span>
</td>
<td>{{alert.new_value}}</td>
<td><span class="detail-hint" tooltip="Absolute difference: {{alert.amount_abs}}">{{alert.amount_pct}}%</span></td>
<td>
<div ng-if="alert.is_regression" style="margin: auto; width: 80%;"
tooltip="Relative magnitude of change (scale from 0 - 20%+)">
<div class="bar bar-scale"
style="width: {{100 - getCappedMagnitude(alert.amount_pct)}}%; height: 1em; float: left;">
</div>
<div class="bar bar-regression"
style="width: {{getCappedMagnitude(alert.amount_pct)}}%; float: left;">
</div>
</div>
<div ng-if="!alert.is_regression" style="margin: auto; width: 80%;"
tooltip="Relative magnitude of change (scale from 0 - 20%+)">
<div class="bar bar-improvement"
style="width: {{getCappedMagnitude(alert.amount_pct)}}%; float: left;">
</div>
<div class="bar bar-scale"
style="width: {{100 - getCappedMagnitude(alert.amount_pct)}}%; float: left; ">
</div>
</div>
</td>
&nbsp;
<div class="form-group">
<input id="filter" type="text" class="form-control" ng-model="filterOptions.filter" placeholder="filter text e.g. linux tp5o" ng-change="filtersUpdated()"/>
</div>
<div class="checkbox">
<label>
<input type="checkbox" ng-model="filterOptions.hideImprovements" ng-change="filtersUpdated()"/>
Hide improvements
</label>
</div>
</form>
<hr/>
<div class="panel panel-default alert-summary" ng-repeat="alertSummary in alertSummaries" ng-if="alertSummary.anyVisible">
<div class="panel-heading">
<a href="#/alerts?id={{alertSummary.id}}" ng-class="{'alert-title-invalid': alertSummary.status==4}" class="alert-title">
Alert #{{alertSummary.id}} - {{alertSummary.title}} <span class="fa fa-external-link icon-superscript"/>
</a>
<ul class="list-unstyled alert-metadata">
<li>{{alertSummary.platforms.join(' / ')}}</li>
<li>{{alertSummary.resultSetMetadata.dateStr}}</li>
<li ng-if="alertSummary.pushURL">
<a href="{{alertSummary.pushURL}}">{{alertSummary.resultSetMetadata.revision}}…{{alertSummary.prevResultSetMetadata.revision}}</a>
</li>
<li ng-if="!alertSummary.pushURL">
<!-- This shouldn't happen in production, only in development -->
<span class="text-danger">Unknown revision(s)</span>
</li>
</ul>
</div>
<div class="panel-body">
<h4>Detected changes</h4>
<td>
<span class="detail-hint"
tooltip="Confidence value as calculated by Perfherder alerts. Note that this is NOT the same as the calculation used in the compare view"
tooltip-placement="left">
{{alert.t_value}}
</span>
</td>
</tr>
</table>
<div style="padding-left: 8px;" ng-show="anySelected(alertSummary.alerts)">
<div ng-if="!anySelectedAndTriaged(alertSummary.alerts)" class="btn-group" role="group" aria-label="alert-actions">
<button class="btn btn-default" role="button"
ng-click="markAlertsInvalid(alertSummary)" title="Mark as invalid">
<span class="fa fa-ban"></span>
</button>
<button class="btn btn-default" role="button"
ng-click="fileBug(alertSummary)" title="File bug for selected alerts">
<span class="fa fa-bug"></span>
</button>
<button class="btn btn-default" role="button"
ng-click="addBugNumberToAlerts(alertSummary)" title="Link alerts to existing bug">
<span class="fa fa-link"></span>
</button>
<button class="btn btn-default" role="button"
ng-click="reassignAlerts(alertSummary)"
title="Associate with another alert summary">
<span class="fa fa-share"></span>
<table class="table table-striped compare-table">
<tr>
<th style="width:16px;">
<input type="checkbox" ng-disabled="!user.is_staff" ng-model="alertSummary.allSelected" ng-change="selectNoneOrSelectAll(alertSummary)"/><!-- select 'em all checkbox --></th>
<!-- Manually specify table widths because it's just easier this way -->
<th class="test-title">
<span style=" word-wrap: break-word;">
Test
</span>
</th>
<th style="width: 140px;">Previous</th>
<th style="width: 30px;"><!-- less than / greater than --></th>
<th style="width: 140px;">New</th>
<th style="width: 80px;">Delta</th>
<th style="width: 120px"><!-- Graphical difference --></th>
<th style="width: 100px;">Confidence</th>
</tr>
<tr ng-repeat="alert in alertSummary.alerts" ng-show="alert.visible">
<td>
<input type="checkbox" ng-disabled="!user.is_staff" ng-model="alert.selected" ng-change="alertSelected(alertSummary)"/>
</td>
<td class="test-title">
<span ng-class="{'alert-strike': (alert.revised_summary_id || alert.status === phAlertResolutionMap.INVALID)}">
{{alert.title}}
</span>
<span ng-show="alert.status === phAlertResolutionMap.INVALID">
&nbsp;(<span class="alert-invalid">invalid</span>)
</span>
<span ng-show="alert.status === phAlertResolutionMap.UNTRIAGED">
&nbsp;(<span class="alert-untriaged">untriaged</span>)
</span>
<span ng-show="alert.bug_number">
&nbsp;(<a href="https://bugzilla.mozilla.org/show_bug.cgi?id={{alert.bug_number}}">bug #{{alert.bug_number}}</a>)
</span>
<span ng-show="alert.revised_summary_id">
&nbsp;(see
<a href="#/alerts?id={{alert.revised_summary_id}}">alert #{{alert.revised_summary_id}}</a>)
</span>&nbsp;&nbsp;
<span class="result-links">
<a href="#/graphs?timerange={{alertSummary.resultSetMetadata.timeRange}}&series=[{{alertSummary.repository}},{{alert.series_signature.signature_hash}},1]&highlightedRevisions={{alertSummary.resultSetMetadata.revision}}">graph</a>
</span>
</td>
<td>{{alert.prev_value}}</td>
<td>
<span ng-class="{'compare-improvement': !alert.is_regression, 'compare-regression': alert.is_regression}">
<span ng-if="alert.prev_value < alert.new_value">
&lt;
</span>
<span ng-if="alert.prev_value > alert.new_value">
&gt;
</span>
</span>
</td>
<td>{{alert.new_value}}</td>
<td><span class="detail-hint" tooltip="Absolute difference: {{alert.amount_abs}}">{{alert.amount_pct}}%</span></td>
<td>
<div ng-if="alert.is_regression" style="margin: auto; width: 80%;"
tooltip="Relative magnitude of change (scale from 0 - 20%+)">
<div class="bar bar-scale"
style="width: {{100 - getCappedMagnitude(alert.amount_pct)}}%; height: 1em; float: left;">
</div>
<div class="bar bar-regression"
style="width: {{getCappedMagnitude(alert.amount_pct)}}%; float: left;">
</div>
</div>
<div ng-if="!alert.is_regression" style="margin: auto; width: 80%;"
tooltip="Relative magnitude of change (scale from 0 - 20%+)">
<div class="bar bar-improvement"
style="width: {{getCappedMagnitude(alert.amount_pct)}}%; float: left;">
</div>
<div class="bar bar-scale"
style="width: {{100 - getCappedMagnitude(alert.amount_pct)}}%; float: left; ">
</div>
</div>
</td>
<td>
<span class="detail-hint"
tooltip="Confidence value as calculated by Perfherder alerts. Note that this is NOT the same as the calculation used in the compare view"
tooltip-placement="left">
{{alert.t_value}}
</span>
</td>
</tr>
</table>
<div style="padding-left: 8px;" ng-show="anySelected(alertSummary.alerts)">
<div ng-if="!anySelectedAndTriaged(alertSummary.alerts)" class="btn-group" role="group" aria-label="alert-actions">
<button class="btn btn-default" role="button"
ng-click="markAlertsInvalid(alertSummary)" title="Mark as invalid">
<span class="fa fa-ban"></span>
</button>
<button class="btn btn-default" role="button"
ng-click="fileBug(alertSummary)" title="File bug for selected alerts">
<span class="fa fa-bug"></span>
</button>
<button class="btn btn-default" role="button"
ng-click="addBugNumberToAlerts(alertSummary)" title="Link alerts to existing bug">
<span class="fa fa-link"></span>
</button>
<button class="btn btn-default" role="button"
ng-click="reassignAlerts(alertSummary)"
title="Associate with another alert summary">
<span class="fa fa-share"></span>
</button>
</div>
<button ng-if="anySelectedAndTriaged(alertSummary.alerts)" class="btn btn-warning" role="button"
ng-click="resetAlerts(alertSummary)" title="Reset selected alerts to untriaged">
Reset
</button>
</div>
<button ng-if="anySelectedAndTriaged(alertSummary.alerts)" class="btn btn-warning" role="button"
ng-click="resetAlerts(alertSummary)" title="Reset selected alerts to untriaged">
Reset
</button>
</div>
</div>
</div>
<p class="text-muted" ng-if="numFilteredAlertSummaries > 0">
{{numFilteredAlertSummaries}} alerts not displayed because they had no changes matching filter criteria
</p>
</div>
</div>
</div>
<div class="well" ng-show="getMoreAlertSummariesHref">
<div class="btn btn-default btn-sm"
ng-click="getMoreAlertSummaries(count)">

Просмотреть файл

@ -22,7 +22,7 @@
</div>
&nbsp;
<div class="form-group">
<input id="filter" type="text" class="form-control" ng-model="filterOptions.filter" placeholder="e.g. linux tp5o" ng-change="updateFilters()"/>
<input id="filter" type="text" class="form-control" ng-model="filterOptions.filter" placeholder="filter text e.g. linux tp5o" ng-change="updateFilters()"/>
</div>
<div class="checkbox" tooltip="Non-trivial changes (1.5%+)">
<label>