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

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

@ -83,16 +83,16 @@ perf.controller(
perf.controller('AlertsCtrl', [ perf.controller('AlertsCtrl', [
'$state', '$stateParams', '$scope', '$rootScope', '$http', '$q', '$modal', '$state', '$stateParams', '$scope', '$rootScope', '$http', '$q', '$modal',
'thUrl', 'ThRepositoryModel', 'ThOptionCollectionModel', 'ThResultSetModel', 'thUrl', 'ThOptionCollectionModel', 'ThResultSetModel',
'thDefaultRepo', 'PhSeries', 'PhAlerts', 'phTimeRanges', 'phDefaultTimeRangeValue', 'PhFramework', 'PhSeries', 'PhAlerts', 'phTimeRanges',
'phAlertResolutionMap', 'dateFilter', 'thDateFormat', 'phDefaultTimeRangeValue', 'phAlertResolutionMap', 'dateFilter',
'thDateFormat',
function AlertsCtrl($state, $stateParams, $scope, $rootScope, $http, $q, function AlertsCtrl($state, $stateParams, $scope, $rootScope, $http, $q,
$modal, $modal, thUrl, ThOptionCollectionModel,
thUrl, ThRepositoryModel, ThOptionCollectionModel, ThResultSetModel, PhFramework,
ThResultSetModel, thDefaultRepo, PhSeries, PhAlerts, PhSeries, PhAlerts, phTimeRanges,
phTimeRanges, phDefaultTimeRangeValue, phDefaultTimeRangeValue, phAlertResolutionMap,
phAlertResolutionMap, dateFilter, thDateFormat) { dateFilter, thDateFormat) {
$scope.alertSummaries = []; $scope.alertSummaries = [];
$scope.getMoreAlertSummariesHref = null; $scope.getMoreAlertSummariesHref = null;
$scope.getCappedMagnitude = function(percent) { $scope.getCappedMagnitude = function(percent) {
@ -102,7 +102,6 @@ perf.controller('AlertsCtrl', [
}; };
$scope.phAlertResolutionMap = phAlertResolutionMap; $scope.phAlertResolutionMap = phAlertResolutionMap;
// these methods
$scope.changeAlertSummaryStatus = function(alertSummary, status) { $scope.changeAlertSummaryStatus = function(alertSummary, status) {
PhAlerts.changeAlertSummaryStatus( PhAlerts.changeAlertSummaryStatus(
alertSummary.id, status).then(function() { 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 // these methods handle the business logic of alert selection and
// unselection // unselection
$scope.anySelected = function(alerts) { $scope.anySelected = function(alerts) {
@ -274,6 +308,7 @@ perf.controller('AlertsCtrl', [
})).then(function() { })).then(function() {
$scope.alertSummaries = _.union($scope.alertSummaries, $scope.alertSummaries = _.union($scope.alertSummaries,
alertSummaries); alertSummaries);
updateAlertVisibility();
}); });
} }
@ -284,27 +319,33 @@ perf.controller('AlertsCtrl', [
}); });
}; };
ThRepositoryModel.get_list().then(function(response) { $q.all([PhFramework.getFrameworkList().then(
$scope.projects = response.data; function(frameworks) {
$scope.selectedProject = _.findWhere($scope.projects, { $scope.frameworks = frameworks.data;
name: thDefaultRepo ? thDefaultRepo : thDefaultRepo }),
}); ThOptionCollectionModel.get_map().then(
ThOptionCollectionModel.get_map().then( function(optionCollectionMap) {
function(optionCollectionMap) { $scope.optionCollectionMap = optionCollectionMap;
$scope.optionCollectionMap = optionCollectionMap; })]
if ($stateParams.id) { ).then(function() {
PhAlerts.getAlertSummary($stateParams.id).then( $scope.filterOptions = {
function(data) { framework: _.find($scope.frameworks, {
addAlertSummaries([data], null); id: parseInt($stateParams.framework)
}); }) || $scope.frameworks[0],
} else { filter: $stateParams.filter || "",
PhAlerts.getAlertSummaries().then(function(data) { hideImprovements: $stateParams.hideImprovements === undefined ||
addAlertSummaries(data.results, data.next); 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', { $stateProvider.state('alerts', {
title: 'Perfherder Alerts', title: 'Perfherder Alerts',
templateUrl: 'partials/perf/alertsctrl.html', templateUrl: 'partials/perf/alertsctrl.html',
url: '/alerts?id', url: '/alerts?id&framework&filter&hideImprovements',
controller: 'AlertsCtrl' controller: 'AlertsCtrl'
}).state('graphs', { }).state('graphs', {
title: 'Perfherder Graphs', title: 'Perfherder Graphs',

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

@ -1,144 +1,164 @@
<div class="container-fluid"> <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"> <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 You must be logged into perfherder/treeherder and be a sheriff to make changes
</div> </div>
<hr/> <form class="form-inline">
<div class="panel panel-default alert-summary" ng-repeat="alertSummary in alertSummaries"> <div class="form-group">
<div class="panel-heading"> <select ng-model="filterOptions.framework"
<a href="#/alerts?id={{alertSummary.id}}" ng-class="{'alert-title-invalid': alertSummary.status==4}" class="alert-title"> ng-options="framework.name for framework in frameworks track by framework.id"
Alert #{{alertSummary.id}} - {{alertSummary.title}} <span class="fa fa-external-link icon-superscript"/> ng-change="filtersUpdated()"/>
</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>
<div class="panel-body"> &nbsp;
<h4>Detected changes</h4> <div class="form-group">
<table class="table table-striped compare-table"> <input id="filter" type="text" class="form-control" ng-model="filterOptions.filter" placeholder="filter text e.g. linux tp5o" ng-change="filtersUpdated()"/>
<tr> </div>
<th style="width:16px;"> <div class="checkbox">
<input type="checkbox" ng-disabled="!user.is_staff" ng-model="alertSummary.allSelected" ng-change="selectNoneOrSelectAll(alertSummary)"/><!-- select 'em all checkbox --></th> <label>
<!-- Manually specify table widths because it's just easier this way --> <input type="checkbox" ng-model="filterOptions.hideImprovements" ng-change="filtersUpdated()"/>
<th class="test-title"> Hide improvements
<span style=" word-wrap: break-word;"> </label>
Test </div>
</span> </form>
</th> <hr/>
<th style="width: 140px;">Previous</th> <div class="panel panel-default alert-summary" ng-repeat="alertSummary in alertSummaries" ng-if="alertSummary.anyVisible">
<th style="width: 30px;"><!-- less than / greater than --></th> <div class="panel-heading">
<th style="width: 140px;">New</th> <a href="#/alerts?id={{alertSummary.id}}" ng-class="{'alert-title-invalid': alertSummary.status==4}" class="alert-title">
<th style="width: 80px;">Delta</th> Alert #{{alertSummary.id}} - {{alertSummary.title}} <span class="fa fa-external-link icon-superscript"/>
<th style="width: 120px"><!-- Graphical difference --></th> </a>
<th style="width: 100px;">Confidence</th> <ul class="list-unstyled alert-metadata">
</tr> <li>{{alertSummary.platforms.join(' / ')}}</li>
<tr ng-repeat="alert in alertSummary.alerts"> <li>{{alertSummary.resultSetMetadata.dateStr}}</li>
<td> <li ng-if="alertSummary.pushURL">
<input type="checkbox" ng-disabled="!user.is_staff" ng-model="alert.selected" ng-change="alertSelected(alertSummary)"/> <a href="{{alertSummary.pushURL}}">{{alertSummary.resultSetMetadata.revision}}…{{alertSummary.prevResultSetMetadata.revision}}</a>
</td> </li>
<td class="test-title"> <li ng-if="!alertSummary.pushURL">
<span ng-class="{'alert-strike': (alert.revised_summary_id || alert.status === phAlertResolutionMap.INVALID)}"> <!-- This shouldn't happen in production, only in development -->
{{alert.title}} <span class="text-danger">Unknown revision(s)</span>
</span> </li>
<span ng-show="alert.status === phAlertResolutionMap.INVALID"> </ul>
&nbsp;(<span class="alert-invalid">invalid</span>) </div>
</span> <div class="panel-body">
<span ng-show="alert.status === phAlertResolutionMap.UNTRIAGED"> <h4>Detected changes</h4>
&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> <table class="table table-striped compare-table">
<span class="detail-hint" <tr>
tooltip="Confidence value as calculated by Perfherder alerts. Note that this is NOT the same as the calculation used in the compare view" <th style="width:16px;">
tooltip-placement="left"> <input type="checkbox" ng-disabled="!user.is_staff" ng-model="alertSummary.allSelected" ng-change="selectNoneOrSelectAll(alertSummary)"/><!-- select 'em all checkbox --></th>
{{alert.t_value}} <!-- Manually specify table widths because it's just easier this way -->
</span> <th class="test-title">
</td> <span style=" word-wrap: break-word;">
</tr> Test
</table> </span>
<div style="padding-left: 8px;" ng-show="anySelected(alertSummary.alerts)"> </th>
<div ng-if="!anySelectedAndTriaged(alertSummary.alerts)" class="btn-group" role="group" aria-label="alert-actions"> <th style="width: 140px;">Previous</th>
<button class="btn btn-default" role="button" <th style="width: 30px;"><!-- less than / greater than --></th>
ng-click="markAlertsInvalid(alertSummary)" title="Mark as invalid"> <th style="width: 140px;">New</th>
<span class="fa fa-ban"></span> <th style="width: 80px;">Delta</th>
</button> <th style="width: 120px"><!-- Graphical difference --></th>
<button class="btn btn-default" role="button" <th style="width: 100px;">Confidence</th>
ng-click="fileBug(alertSummary)" title="File bug for selected alerts"> </tr>
<span class="fa fa-bug"></span> <tr ng-repeat="alert in alertSummary.alerts" ng-show="alert.visible">
</button> <td>
<button class="btn btn-default" role="button" <input type="checkbox" ng-disabled="!user.is_staff" ng-model="alert.selected" ng-change="alertSelected(alertSummary)"/>
ng-click="addBugNumberToAlerts(alertSummary)" title="Link alerts to existing bug"> </td>
<span class="fa fa-link"></span> <td class="test-title">
</button> <span ng-class="{'alert-strike': (alert.revised_summary_id || alert.status === phAlertResolutionMap.INVALID)}">
<button class="btn btn-default" role="button" {{alert.title}}
ng-click="reassignAlerts(alertSummary)" </span>
title="Associate with another alert summary"> <span ng-show="alert.status === phAlertResolutionMap.INVALID">
<span class="fa fa-share"></span> &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> </button>
</div> </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>
</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>
<div class="well" ng-show="getMoreAlertSummariesHref"> <div class="well" ng-show="getMoreAlertSummariesHref">
<div class="btn btn-default btn-sm" <div class="btn btn-default btn-sm"
ng-click="getMoreAlertSummaries(count)"> ng-click="getMoreAlertSummaries(count)">

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

@ -22,7 +22,7 @@
</div> </div>
&nbsp; &nbsp;
<div class="form-group"> <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>
<div class="checkbox" tooltip="Non-trivial changes (1.5%+)"> <div class="checkbox" tooltip="Non-trivial changes (1.5%+)">
<label> <label>