merged in job-classification branch from mauro

This commit is contained in:
Cameron Dawson 2014-02-27 11:32:56 -08:00
Родитель f1d7c9f4aa
Коммит 66b94a6a0d
28 изменённых файлов: 819 добавлений и 405 удалений

0
.jshintrc Normal file
Просмотреть файл

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

@ -291,7 +291,7 @@ div.navbar-fixed-bottom{
}
div.navbar-fixed-bottom .tab-content{
height:300px;
overflow-y: scroll;
overflow-y: auto;
}
div.navbar-fixed-bottom dt,
@ -303,11 +303,6 @@ div.navbar-fixed-bottom dt {
#bottom_center_panel {margin:0 110px 0 242px;}
#bottom_menu{position: absolute; top: 5px; right:5px;}
#open_bugs_accordion,
#closed_bugs_accordion{
margin-top: 5px;
}
.panel-body{ padding:5px;}
.timestamp-name {
@ -860,3 +855,12 @@ fieldset[disabled] .btn-repo.active {
.waiter-small{ background: url("../img/waiter16.png") no-repeat center center; width: 16px; height: 16px; display: inline-block;}
.waiter-medium{ background: url("../img/waiter32.png") no-repeat center center; width: 32px; height: 32px; display: inline-block;}
.waiter-large{ background: url("../img/waiter64.png") no-repeat center center; width: 64px; height: 64px; display: inline-block;}
.deleted{ text-decoration: line-through;}
.plugin_controls{ margin: 5px;}
.btn-similar-jobs{ margin: 5px;}
#notification_box{ position:fixed; top:720px; right:10px; height:680px; width:300px;}
#notification_box p{position:relative;}
#notification_box a.close{position:absolute; top:5px; right:5px; z-index: 999;}

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

@ -32,6 +32,8 @@
<div ng-include src="'plugins/pluginpanel.html'"></div>
</div>
<th-notification-box></th-notification-box>
<script src="vendor/angular/angular.js"></script>
<script src="vendor/angular/angular-route.js"></script>
<script src="vendor/angular/angular-resource.js"></script>
@ -49,8 +51,12 @@
<script src="js/directives.js"></script>
<script src="js/services/main.js"></script>
<script src="js/services/jobfilters.js"></script>
<script src="js/services/models/repository.js"></script>
<script src="js/services/models/resultsets.js"></script>
<script src="js/services/models/job_artifact.js"></script>
<script src="js/services/models/repository.js"></script>
<script src="js/services/models/bug_job_map.js"></script>
<script src="js/services/models/note.js"></script>
<script src="js/services/models/job.js"></script>
<script src="js/controllers/main.js"></script>
<script src="js/controllers/repository.js"></script>
<script src="js/controllers/filters.js"></script>
@ -60,8 +66,8 @@
<script src="plugins/controller.js"></script>
<script src="plugins/notes/controller.js"></script>
<script src="plugins/tinderbox/controller.js"></script>
<script src="plugins/open_bugs_suggestions/controller.js"></script>
<script src="plugins/closed_bugs_suggestions/controller.js"></script>
<script src="plugins/bugs_suggestions/controller.js"></script>
<script src="plugins/similar_jobs/controller.js"></script>
<script src="js/filters.js"></script>
<script src="vendor/Config.js"></script>
<script src="https://login.persona.org/include.js"></script>

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

@ -42,3 +42,19 @@ treeherder.config(function($routeProvider, $httpProvider, $logProvider) {
}).
otherwise({redirectTo: '/jobs'});
});
var logViewer = angular.module('logViewer',['treeherder']);
treeherder.config(function($httpProvider, $logProvider) {
// enable or disable debug messages using $log.
// comment out the next line to enable them
$logProvider.debugEnabled(false);
// needed to avoid CORS issue when getting the logs from the ftp site
// @@@ hack for now to get it to work in the short-term
$httpProvider.defaults.useXDomain = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];
$httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
$httpProvider.defaults.xsrfCookieName = 'csrftoken';
});

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

@ -1,23 +1,23 @@
'use strict';
treeherder.controller('LogviewerCtrl',
function Logviewer($anchorScroll, $scope, $rootScope, $location, $routeParams, $http, $timeout, thArtifact) {
logViewer.controller('LogviewerCtrl',
function Logviewer($anchorScroll, $scope, $log, $rootScope, $location, $http, $timeout, ThJobArtifactModel) {
if ($location.$$search.hasOwnProperty("repo") &&
$location.$$search.repo !== "") {
$rootScope.repoName = $location.$$search.repo;
var query_string = $location.search();
if (query_string.repo != "") {
$rootScope.repoName = query_string.repo;
}
if ($location.$$search.hasOwnProperty("id") &&
$location.$$search.id !== "") {
$scope.lvArtifactId= $location.$$search.id;
if (query_string.job_id != "") {
$scope.job_id= query_string.job_id;
}
$scope.scrollTo = function(step, linenumber) {
$location.hash('lv-line-'+linenumber);
$anchorScroll();
};
// @@@ it may be possible to do this with the angular date filter?
$scope.formatTime = function(sec) {
var h = Math.floor(sec/3600);
@ -85,17 +85,18 @@ treeherder.controller('LogviewerCtrl',
// $scope.sliceLog(data.split("\n"));
// });
// });
$log.log(ThJobArtifactModel.get_uri());
ThJobArtifactModel.get_list({job_id: $scope.job_id, name: "Structured Log"})
.then(function(artifact_list){
if(artifact_list.length > 0){
$scope.artifact = artifact_list[0].blob;
return $http.get($scope.artifact.logurl)
.success(function(data){
$scope.sliceLog(data.split("\n"));
});
}
thArtifact.getArtifact($scope.lvArtifactId).
success(function(data) {
$scope.artifact = data.blob;
$scope.logurl = data.blob.logurl;
$http.get($scope.logurl).
success(function(data) {
$scope.sliceLog(data.split("\n"));
});
});
});
};
}
);

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

@ -763,7 +763,7 @@ treeherder.directive('personaButtons', function($http, $q, $log, $rootScope, loc
// if the user.email value is null, it means that he's not logged in
scope.user.email = scope.user.email || localStorageService.get('user.email');
scope.user.loggedin = scope.user.email == null ? false : true;
scope.login = function(){
/*
* BrowserID.login returns a promise of the verification.
@ -788,7 +788,7 @@ treeherder.directive('personaButtons', function($http, $q, $log, $rootScope, loc
});
};
navigator.id.watch({
/*
* loggedinUser is all that we know about the user before
@ -808,9 +808,9 @@ treeherder.directive('personaButtons', function($http, $q, $log, $rootScope, loc
BrowserId.requestDeferred.resolve(assertion);
}
},
/*
* Resolve BrowserId.logoutDeferred once the user is logged out from persona
* Resolve BrowserId.logoutDeferred once the user is logged out from persona
*/
onlogout: function(){
if (BrowserId.logoutDeferred) {
@ -822,3 +822,49 @@ treeherder.directive('personaButtons', function($http, $q, $log, $rootScope, loc
templateUrl: 'partials/persona_buttons.html'
};
});
treeherder.directive('thSimilarJobs', function(ThJobModel, $log){
return {
restrict: "E",
templateUrl: "partials/similar_jobs.html",
link: function(scope, element, attr) {
scope.$watch('job', function(newVal, oldVal){
$log.log(newVal);
if(newVal){
scope.update_similar_jobs(newVal);
}
});
scope.similar_jobs = []
scope.similar_jobs_filters = {
"machine_id": true,
"job_type_id": true,
"build_platform_id": true
}
scope.update_similar_jobs = function(job){
$log.log("updating similar jobs")
var options = {result_set_id__ne: job.result_set_id};
angular.forEach(scope.similar_jobs_filters, function(elem, key){
if(elem){
options[key] = job[key];
}
});
ThJobModel.get_list(options).then(function(data){
scope.similar_jobs = data;
});
};
}
}
});
treeherder.directive('thNotificationBox', function($log, thNotify){
return {
restrict: "E",
template: '<div id="notification_box" ng-class="notify.current.severity" ng-if="notify.current.message">' +
'<p>{{notify.current.message}}' +
'<a ng-click="notify.clear()" ng-if="notify.current.sticky" title="close" class="close">x</a></p>' +
'</div>',
link: function(scope, element, attr) {
scope.notify = thNotify;
}
}
});

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

@ -1,94 +1,27 @@
'use strict';
/* Services */
treeherder.factory('thUrl',
['$rootScope', 'thServiceDomain',
function($rootScope, thServiceDomain) {
return {
treeherder.factory('thUrl',['$rootScope', 'thServiceDomain', '$log', function($rootScope, thServiceDomain, $log) {
var thUrl = {
getRootUrl: function(uri) {
return thServiceDomain + "/api" + uri;
},
getProjectUrl: function(uri) {
return thServiceDomain + "/api/project/" + $rootScope.repoName + uri;
},
getLogViewerUrl: function(artifactId) {
return "logviewer.html#?id=" + artifactId + "&repo=" + $rootScope.repoName;
getLogViewerUrl: function(job_id) {
return "logviewer.html#?job_id=" + job_id + "&repo=" + $rootScope.repoName;
},
getSocketEventUrl: function() {
var port = thServiceDomain.indexOf("https:") !== -1 ? 443 :80;
return thServiceDomain + ':' + port + '/events';
}
};
return thUrl;
};
return thUrl;
}]);
treeherder.factory('thArtifact',
['$http', 'thUrl',
function($http, thUrl) {
// get the artifacts for this tree
return {
getArtifact: function(id) {
return $http.get(thUrl.getProjectUrl(
"/artifact/" + id + "/"));
}
}
}]);
treeherder.factory('thJobs',
['$http', 'thUrl',
function($http, thUrl) {
return {
getJobs: function(offset, count, joblist) {
offset = typeof offset == 'undefined'? 0: offset;
count = typeof count == 'undefined'? 10: count;
var params = {
offset: offset,
count: count,
format: "json"
}
if (joblist) {
_.extend(params, {
offset: 0,
count: joblist.length,
id__in: joblist.join()
})
}
return $http.get(thUrl.getProjectUrl("/jobs/"),
{params: params}
);
}
}
}]);
treeherder.factory('thJobNote', function($resource, $http, thUrl) {
return {
get: function() {
var JobNote = $resource(thUrl.getProjectUrl("/note/"));
// Workaround to the fact that $resource strips trailing slashes
// out of urls. This causes a 301 redirect on POST because it does a
// preflight OPTIONS call. Tastypie gives up on the POST after this
// and nothing happens. So this alternative "thSave" command avoids
// that by using the trailing slash directly in a POST call.
// @@@ This may be fixed in later versions of Angular. Or perhaps there's
// a better way?
JobNote.prototype.thSave = function() {
$http.post(thUrl.getProjectUrl("/note/"), {
job_id: this.job_id,
note: this.note,
who: this.who,
failure_classification_id: this.failure_classification_id
});
};
return JobNote;
}
};
});
treeherder.factory('thSocket', function ($rootScope, $log, thUrl) {
var socket = io.connect(thUrl.getSocketEventUrl());
socket.on('connect', function () {
@ -156,6 +89,27 @@ treeherder.factory('thCloneHtml', function($interpolate) {
});
treeherder.factory('ThPaginator', function(){
//dead-simple implementation of an in-memory paginator
var ThPaginator = function(data, limit){
this.data = data;
this.length = data.length;
this.limit = limit;
};
ThPaginator.prototype.get_page = function(n){
return this.data.slice(n * limit - limit, n * limit);
}
ThPaginator.prototype.get_all = function(){
return data
};
return ThPaginator
});
treeherder.factory('BrowserId', function($http, $q, $log, thServiceDomain){
/*
@ -228,3 +182,46 @@ treeherder.factory('BrowserId', function($http, $q, $log, thServiceDomain){
}
return browserid;
});
treeherder.factory('thNotify', function($log){
//a growl-like notification system
var thNotify = {
// message queue
notifications: [],
// the currently displayed message
current: {},
/*
* send a message to the notification queue
* @severity can be one of success|info|warning|danger
* @sticky is a boolean indicating if you want to message to disappear
* after a while or not
*/
send: function(message, severity, sticky){
$log.log("received message");
$log.log(message);
var severity = severity || 'info';
var sticky = sticky || false;
thNotify.notifications.push({
message: message,
severity: severity,
sticky: sticky
});
thNotify.fetch();
},
fetch: function(){
thNotify.current=thNotify.notifications.shift() || {};
if(thNotify.current.message && !thNotify.current.sticky){
window.setTimeout(thNotify.remove, 5000);
}
},
remove: function(){
thNotify.current = {};
thNotify.fetch();
}
}
return thNotify;
});

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

@ -0,0 +1,43 @@
treeherder.factory('ThBugJobMapModel', function($http, thUrl) {
// ThBugJobMap is a class which we can use for retrieving and
// updating data on the server
var ThBugJobMapModel = function(data) {
angular.extend(this, data);
};
ThBugJobMapModel.get_uri = function(){return thUrl.getProjectUrl("/bug-job-map/");}
// a static method to retrieve a list of ThBugJobMap
// the options parameter is used to filter/limit the list of objects
ThBugJobMapModel.get_list = function(options) {
var query_string = $.param(options)
return $http.get(ThBugJobMapModel.get_uri()+"?"+query_string).then(function(response) {
var item_list = [];
angular.each(response.data, function(elem){
item_list.push(new ThBugJobMapModel(elem));
});
return item_list;
});
};
// a static method to retrieve a single instance of ThBugJobMap
ThBugJobMapModel.get = function(pk) {
return $http.get(ThBugJobMapModel.get_uri()+pk).then(function(response) {
return new ThBugJobMapModel(response.data);
});
};
// an instance method to create a new ThBugJobMap
ThBugJobMapModel.prototype.create = function() {
var bug_job_map = this;
return $http.post(ThBugJobMapModel.get_uri(), bug_job_map);
};
// an instance method to delete a ThBugJobMap object
ThBugJobMapModel.prototype.delete = function(){
var pk = this.job_id+"-"+this.bug_id;
return $http.delete(ThBugJobMapModel.get_uri()+pk);
};
return ThBugJobMapModel;
});

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

@ -0,0 +1,33 @@
treeherder.factory('ThJobModel', ['$http', '$log', 'thUrl', function($http, $log, thUrl) {
// ThJobArtifactModel is the js counterpart of job_artifact
var ThJobModel = function(data) {
// creates a new instance of ThJobArtifactModel
// using the provided properties
angular.extend(this, data);
};
ThJobModel.get_uri = function(){return thUrl.getProjectUrl("/jobs/");}
ThJobModel.get_list = function(options) {
// a static method to retrieve a list of ThJobModel
var query_string = $.param(options)
return $http.get(ThJobModel.get_uri()+"?"+query_string)
.then(function(response) {
var item_list = [];
angular.forEach(response.data, function(elem){
item_list.push(new ThJobModel(elem));
});
return item_list;
});
};
ThJobModel.get = function(pk) {
// a static method to retrieve a single instance of ThJobModel
return $http.get(ThJobModel.get_uri()+pk).then(function(response) {
return new ThJobModel(response.data);
});
};
return ThJobModel;
}]);

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

@ -0,0 +1,33 @@
treeherder.factory('ThJobArtifactModel', ['$http', '$log', 'thUrl', function($http, $log, thUrl) {
// ThJobArtifactModel is the js counterpart of job_artifact
var ThJobArtifactModel = function(data) {
// creates a new instance of ThJobArtifactModel
// using the provided properties
angular.extend(this, data);
};
ThJobArtifactModel.get_uri = function(){return thUrl.getProjectUrl("/artifact/");}
ThJobArtifactModel.get_list = function(options) {
// a static method to retrieve a list of ThJobArtifactModel
var query_string = $.param(options)
return $http.get(ThJobArtifactModel.get_uri()+"?"+query_string)
.then(function(response) {
var item_list = [];
angular.forEach(response.data, function(elem){
item_list.push(new ThJobArtifactModel(elem));
});
return item_list;
});
};
ThJobArtifactModel.get = function(pk) {
// a static method to retrieve a single instance of ThJobArtifactModel
return $http.get(ThJobArtifactModel.get_uri()+pk).then(function(response) {
return new ThJobArtifactModel(response.data);
});
};
return ThJobArtifactModel;
}]);

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

@ -0,0 +1,72 @@
treeherder.factory('ThJobNoteModel', ['$http', '$log', 'thUrl', function($http, $log, thUrl) {
// ThJobNoteModel is the js counterpart of note
var ThJobNoteModel = function(data) {
// creates a new instance of ThJobArtifactModel
// using the provided properties
angular.extend(this, data);
};
ThJobNoteModel.get_uri = function(){return thUrl.getProjectUrl("/note/");}
ThJobNoteModel.get_list = function(options) {
// a static method to retrieve a list of ThJobNoteModel
var query_string = $.param(options)
return $http.get(ThJobNoteModel.get_uri()+"?"+query_string)
.then(function(response) {
var item_list = [];
angular.forEach(response.data, function(elem){
item_list.push(new ThJobNoteModel(elem));
});
return item_list;
});
};
ThJobNoteModel.get = function(pk) {
// a static method to retrieve a single instance of ThJobNoteModel
return $http.get(ThJobNoteModel.get_uri()+pk).then(function(response) {
return new ThJobNoteModel(response.data);
});
};
// an instance method to create a new ThJobNoteModel
ThJobNoteModel.prototype.create = function() {
var note = this;
return $http.post(ThJobNoteModel.get_uri(), note)
};
// an instance method to delete a ThJobNoteModel object
ThJobNoteModel.prototype.delete = function(){
return $http.delete(ThJobNoteModel.get_uri()+this.id);
};
return ThJobNoteModel;
}]);
//treeherder.factory('thJobNote', function($resource, $http, thUrl) {
// return {
// get: function() {
// var JobNote = $resource(thUrl.getProjectUrl("/note/"));
// // Workaround to the fact that $resource strips trailing slashes
// // out of urls. This causes a 301 redirect on POST because it does a
// // preflight OPTIONS call. Tastypie gives up on the POST after this
// // and nothing happens. So this alternative "thSave" command avoids
// // that by using the trailing slash directly in a POST call.
// // @@@ This may be fixed in later versions of Angular. Or perhaps there's
// // a better way?
// JobNote.prototype.thSave = function() {
// $http.post(thUrl.getProjectUrl("/note/"), {
// job_id: this.job_id,
// note: this.note,
// who: this.who,
// failure_classification_id: this.failure_classification_id
// });
// };
// return JobNote;
// }
// };
//});

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

@ -30,6 +30,7 @@ treeherder.factory('thReposModel',
return null;
};
// get by category
var byGroup = function() {
var groupedRepos = {};
@ -67,6 +68,7 @@ treeherder.factory('thReposModel',
if (name) {
$rootScope.currentRepo = byName(name);
}
});
},

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

@ -33,8 +33,8 @@ treeherder.factory('thResultSets',
}]);
treeherder.factory('thResultSetModel',
['$log', '$rootScope', 'thResultSets', 'thSocket', 'thJobs', 'thEvents', 'thPlatformElements',
function($log, $rootScope, thResultSets, thSocket, thJobs, thEvents, thPlatformElements) {
['$log', '$rootScope', 'thResultSets', 'thSocket', 'thJobModel', 'thEvents', 'thPlatformElements',
function($log, $rootScope, thResultSets, thSocket, thJobModel, thEvents, thPlatformElements) {
/******
* Handle updating the resultset datamodel based on a queue of jobs
@ -349,9 +349,11 @@ treeherder.factory('thResultSetModel',
// make an ajax call to get the job details
thJobs.getJobs(0, jobFetchList.length, jobFetchList).
success(updateJobs).
error(function(data) {
ThJobModel.get_list({
id__in: jobFetchList.join()
}).then(
updateJobs,
function(data) {
$log.error("Error fetching jobUpdateQueue: " + data);
});
}

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

@ -0,0 +1,3 @@
/**
* Created by camd on 2/27/14.
*/

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

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html ng-app="treeherder">
<html ng-app="logViewer">
<head>
<title>Treeherder Log Viewer </title>
<link href="css/treeherder.css" rel="stylesheet" type="text/css">
@ -58,19 +58,15 @@
<script src="vendor/angular/angular-resource.js"></script>
<script src="vendor/angular/angular-cookies.js"></script>
<script src="vendor/angular-local-storage.min.js"></script>
<script src="vendor/ui-bootstrap-tpls-0.6.0.js"></script>
<script src="vendor/ui-bootstrap-tpls-0.10.0.js"></script>
<script src="vendor/jquery-2.0.3.js"></script>
<script src="vendor/bootstrap.js"></script>
<script src="vendor/angular/angular-sanitize.min.js"></script>
<script src="js/config/local.conf.js"></script>
<script src="js/app.js"></script>
<script src="js/services/main.js"></script>
<script src="js/services/models/job_artifact.js"></script>
<script src="js/providers.js"></script>
<script src="js/controllers/main.js"></script>
<script src="js/controllers/jobs.js"></script>
<script src="js/controllers/machines.js"></script>
<script src="js/controllers/timeline.js"></script>
<script src="js/filters.js"></script>
<script src="js/controllers/logviewer.js"></script>
</body>
</html>

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

@ -0,0 +1,123 @@
"use strict";
treeherder.controller('BugClassificationCtrl',
function BugClassificationCtrl($scope, ThBugJobMapModel, $modalInstance){
$scope.failure_classification_id = null;
$scope.comment = "";
angular.forEach($scope.selected_bugs, function(bug_id, selected){
if(selected){
if($scope.comment != ""){
$scope.comment += ",";
}
$scope.comment += "Bug #"+bug_id;
}
});
$scope.custom_bug = null;
$scope.ok = function () {
angular.forEach($scope.selected_bugs, function(bug_id, selected){
if(selected){
var bug_job_map = new ThBugJobMapModel({
bug_id: bug_id,
job_id: $scope.job.id
});
bug_job_map.create();
}
});
$modalInstance.close();
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
})
treeherder.controller('BugsPluginCtrl',
function BugsPluginCtrl($scope, $rootScope, $log, ThJobArtifactModel, ThBugJobMapModel, ThJobNoteModel, thNotify) {
$log.debug("bugs plugin initialized");
$scope.classify = function(bug_list){
var modalInstance = $modal.open({
templateUrl: 'bug_classification.html',
controller: 'BugClassificationCtrl',
resolve: {'result':'ok'},
scope: $scope
});
};
$scope.message = ""
$scope.quick_submit = function(){
angular.forEach($scope.selected_bugs, function(v, k){
if(v){
var bjm = new ThBugJobMapModel({
bug_id : k,
job_id: $scope.job.id,
type: 'annotation'
});
bjm.create()
}
})
var note = new ThJobNoteModel({
job_id:$scope.job.id,
who: $scope.user ? $scope.user.email : "",
failure_classification_id: $scope.classification,
note_timestamp: new Date().getTime(),
note: ""
});
note.create()
.then(
function(){
thNotify.send({
message: "Note successfully created",
severity: "success",
sticky: false
})
},
function(){
thNotify.send({
message: "Note creation failed",
severity: "danger",
sticky: true
})
}
);
}
var update_bugs = function(newValue, oldValue){
$scope.bugs = {};
$scope.visible = "open";
$scope.show_all = false;
$scope.selected_bugs = {}
$scope.classification = null;
// fetch artifacts only if the job is finished
if(newValue){
$scope.tabs.bugs_suggestions.is_loading = true;
var data = ThJobArtifactModel.get_list({
name__in: "Open bugs,Closed bugs",
"type": "json",
job_id: newValue
})
.then(function(response){
// iterate to retrieve the total num of suggestions
angular.forEach(response, function(artifact){
var open_closed = artifact.name == "Open bugs" ? "open" : "closed";
angular.forEach(artifact.blob, function(suggestions, error){
if(!_.has($scope.bugs, error)){
$scope.bugs[error] = {'open':[], 'closed':[]};
}
$scope.bugs[error][open_closed] = suggestions;
});
});
})
.finally(function(){
$scope.tabs.bugs_suggestions.is_loading = false;
});
}
};
$scope.$watch("job.id", update_bugs, true);
}
);

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

@ -0,0 +1,73 @@
<div ng-controller="BugsPluginCtrl">
<p class="plugin_controls">
<input type="radio" ng-click="visible='open'" ng-checked="visible=='open'" />
Open bugs
<input type="radio" ng-click="visible='closed'" ng-checked="visible=='closed'" />
Closed bugs
</p>
<p>
Classification:
<select ng-model="classification">
<option ng-repeat="(value, star) in starTypes" value="{{value}}">{{star.name}}</option>
</select>
<button type="button" class="btn btn-default btn-sm" ng-click="quick_submit()">Quick submit</button>
<!-- <button type="button" class="btn btn-default btn-sm" ng-click="classify(selected_bugs)">Classify</button> -->
</p>
<ul class="list-group" class="bg-info">
<li class="list-group-item" ng-repeat="(error, bug_list) in bugs">
<p><strong>{{error}}</strong></p>
<table class="table-super-condensed table-hover">
<tbody>
<tr ng-repeat="bug in bug_list[visible] | orderBy:bug.relevance:reverse" ng-show="$index<3 || show_all">
<td>
<input type="checkbox" ng-model="selected_bugs[bug.id]" />
</td>
<td><a href="https://bugzilla.mozilla.org/show_bug.cgi?id={{bug.id}}">{{bug.id}}</a></td>
<td ng-class="{'deleted': bug.resolution!=''}">{{bug.summary}}</td>
<td>{{bug.os}}</td>
<td>{{bug.relevance| number:0}}</td>
</tr>
<tr>
<td colspan="4">
<a ng-click="show_all=true" ng-show="show_all != true">Show more</a>
<a ng-click="show_all=false" ng-show="show_all == true">Show less</a>
</td>
</tr>
</tbody>
</table>
</li>
</ul>
</div>
<script type="text/ng-template" id="bug_classification.html">
<div class="modal-header">
<h3>Job failure classification</h3>
</div>
<div class="modal-body">
<ul>
<li ng-repeat="(bug_id, selected) in selected_bugs">
<input type="checkbox" ng-model="selected_bugs[bug_id]" />
<label>{{bug_id}}</label>
</li>
</ul>
<label>Bug id:</label><input type="text" value=""/>
<textarea>
{{comment}}
</textarea>
</div>
<div class="modal-footer">
<button class="btn btn-primary" ng-click="ok()">OK</button>
<button class="btn btn-warning" ng-click="cancel()">Cancel</button>
</div>
</script>

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

@ -1,27 +0,0 @@
"use strict";
treeherder.controller('ClosedBugsPluginCtrl',
function ClosedBugsPluginCtrl($scope, $rootScope, $log) {
$log.debug("closed bugs plugin initialized");
$scope.$watch('artifacts', function(newValue, oldValue){
$scope.closed_bugs = [];
$scope.bugs_count= 0;
if (newValue && newValue.hasOwnProperty('Closed bugs') && newValue['Closed bugs'].blob){
$scope.closed_bugs = newValue['Closed bugs'].blob;
// set the item count on the tab header
angular.forEach($scope.closed_bugs, function(value, key){
this.bugs_count +=value.length;
}, $scope);
}
for(var tab=0; tab<$scope.tabs.length; tab++){
if ($scope.tabs[tab].id == 'closed-bugs'){
$scope.tabs[tab].num_items = $scope.bugs_count;
}
}
}, true);
}
);

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

@ -1,31 +0,0 @@
<div ng-controller="ClosedBugsPluginCtrl">
<div id="closed_bugs_accordion" class="panel-group">
<div class="panel panel-default" ng-repeat="(error_line, bugs) in closed_bugs">
<div class="panel-heading">
<span class="badge pull-right">{{bugs.length}}</span>
<a data-toggle="collapse" data-parent="#closed_bugs_accordion" href="#closed_bug_{{$index}}">
{{error_line}}
</a>
</div>
<div id="closed_bug_{{$index}}" class="panel-collapse collapse collapsed">
<div class="panel-body">
<table class="table-super-condensed table-hover">
<thead>
<tr><th>Bug ID</th><th>Summary</th><th>Crash signature</th><th>Keywords</th><th>OS</th><th>Relevance</th></tr>
</thead>
<tbody>
<tr ng-repeat="bug in bugs">
<td><a href="https://bugzilla.mozilla.org/show_bug.cgi?id={{bug.id}}">{{bug.id}}</a></td>
<td>{{bug.summary}}</td>
<td>{{bug.crash_signature}}</td>
<td>{{bug.keywords}}</td>
<td>{{bug.os}}</td>
<td>{{bug.relevance| number:0}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>

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

@ -4,14 +4,22 @@
treeherder.controller('PluginCtrl',
function PluginCtrl($scope, $rootScope, $resource, $http,
thServiceDomain, thUrl, thJobNote, thStarTypes,
thEvents, $log) {
thJobModel, thEvents, $log) {
var JobNote = null;
$scope.job = {};
var selectJob = function(newValue, oldValue) {
// preferred way to get access to the selected job
if (newValue) {
$scope.job = newValue;
// get the details of the current job
ThJobModel.get($scope.job.id).then(function(data){
_.extend($scope.job, data);
$scope.logs = data.logs;
});
$scope.artifacts = {};
var undef = "---undefined---";
@ -27,34 +35,11 @@ treeherder.controller('PluginCtrl',
};
$scope.tab_loading = true;
$scope.lvUrl = thUrl.getLogViewerUrl($scope.job.id);
$http.get(thServiceDomain + $scope.job.resource_uri).
success(function(data) {
_.extend($scope.job, data);
$scope.logs = data.logs;
data.artifacts.forEach(function(artifact) {
if (artifact.name !== "Structured Log") {
// we don't return the blobs with job, just
// resource_uris to them. For the Job Artifact,
// we want that blob, so we need to fetch the
// detail to get the blob which has the
// tinderbox_printlines, etc.
$scope.artifacts[artifact.name] =$resource(
thServiceDomain + artifact.resource_uri).get();
} else {
// for the structured log, we don't need the blob
// here, we have everything we need in the artifact
// as is, so just save it.
$scope.lvUrl = thUrl.getLogViewerUrl(artifact.id);
}
});
$scope.tab_loading = false;
});
JobNote = thJobNote.get();
$scope.updateNotes();
}
};
@ -70,7 +55,9 @@ treeherder.controller('PluginCtrl',
// load the list of existing notes (including possibly a new one just
// added).
$scope.updateNotes = function() {
$scope.notes = JobNote.query({job_id: $scope.job.job_id});
ThJobNoteModel.get_list({job_id: $scope.job.id}).then(function(response){
$scope.notes = response;
});
};
// when notes comes in, then set the latest note for the job
$scope.$watch('notes', function(newValue, oldValue) {
@ -85,8 +72,8 @@ treeherder.controller('PluginCtrl',
if ($scope.notes && $scope.notes.length > 0) {
fci = $scope.notes[0].failure_classification_id;
}
$scope.newNote = new JobNote({
job_id: $scope.job.job_id,
$scope.newNote = new ThJobNoteModel({
job_id: $scope.job.id,
note: "",
who: $scope.username,
failure_classification_id: fci
@ -101,35 +88,31 @@ treeherder.controller('PluginCtrl',
// save the note and hide the form
$scope.saveNote = function() {
$scope.newNote.thSave();
$scope.updateNotes();
$scope.clearNewNote();
$scope.newNote.create()
.then(function(response){
$scope.updateNotes();
$scope.clearNewNote();
});
};
$scope.tabs = [
{
id: "tinderbox",
$scope.tabs = {
"tinderbox": {
title: "Job Details",
content: "plugins/tinderbox/main.html"
},
{
id: "notes",
"notes": {
title: "Notes",
content: "plugins/notes/main.html"
},
{
id: "open-bugs",
title: "Open Bugs",
content: "plugins/open_bugs_suggestions/main.html"
"bugs_suggestions": {
title: "Bugs suggestions",
content: "plugins/bugs_suggestions/main.html"
},
{
id: "closed-bugs",
title: "Closed Bugs",
content: "plugins/closed_bugs_suggestions/main.html"
"similar_jobs": {
title: "Similar jobs",
content: "plugins/similar_jobs/main.html"
}
];
$scope.tab_loading = false;
};
}
);

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

@ -5,15 +5,8 @@ treeherder.controller('NotesPluginCtrl',
$log.debug("notes plugin initialized");
$scope.$watch('notes', function(newValue, oldValue){
for(var tab=0; tab<$scope.tabs.length; tab++){
if ($scope.tabs[tab].id == 'notes'){
if(newValue){
$scope.tabs[tab].num_items = $scope.notes.length;
}else{
$scope.tabs[tab].num_items = 0;
}
}
}
$scope.tabs.notes.num_items = newValue ? $scope.notes.length : 0;
}, true);
}
);

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

@ -1,5 +1,6 @@
<div ng-controller="NotesPluginCtrl">
<table class="table-condensed table-hover">
{{notes}}
<table ng-show="notes.length > 0" class="table-condensed table-hover">
<thead>
<tr><th>Datetime</th><th>Author</th><th>Failure type</th><th>Note</th></tr>
</thead>

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

@ -1,27 +0,0 @@
"use strict";
treeherder.controller('OpenBugsPluginCtrl',
function OpenBugsPluginCtrl($scope, $log) {
$log.debug("open bugs plugin initialized");
$scope.$watch('artifacts', function(newValue, oldValue){
$scope.open_bugs = [];
$scope.bugs_count= 0;
if (newValue && newValue.hasOwnProperty('Open bugs') && newValue['Open bugs'].blob){
$scope.open_bugs = newValue['Open bugs'].blob;
// set the item count on the tab header
angular.forEach($scope.open_bugs, function(value, key){
this.bugs_count +=value.length;
}, $scope);
}
for(var tab=0; tab<$scope.tabs.length; tab++){
if ($scope.tabs[tab].id == 'open-bugs'){
$scope.tabs[tab].num_items = $scope.bugs_count;
}
}
}, true);
}
);

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

@ -1,31 +0,0 @@
<div ng-controller="OpenBugsPluginCtrl">
<div id="open_bugs_accordion" class="panel-group">
<div class="panel panel-default" ng-repeat="(error_line, bugs) in open_bugs">
<div class="panel-heading">
<span class="badge pull-right">{{bugs.length}}</span>
<a data-toggle="collapse" data-parent="#open_bugs_accordion" href="#open_bug_{{$index}}">
{{error_line}}
</a>
</div>
<div id="open_bug_{{$index}}" class="panel-collapse collapse collapsed">
<div class="panel-body">
<table class="table-super-condensed table-hover">
<thead>
<tr><th>Bug ID</th><th>Summary</th><th>Crash signature</th><th>Keywords</th><th>OS</th><th>Relevance</th></tr>
</thead>
<tbody>
<tr ng-repeat="bug in bugs">
<td><a href="https://bugzilla.mozilla.org/show_bug.cgi?id={{bug.id}}">{{bug.id}}</a></td>
<td>{{bug.summary}}</td>
<td>{{bug.crash_signature}}</td>
<td>{{bug.keywords}}</td>
<td>{{bug.os}}</td>
<td>{{bug.relevance| number:0}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>

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

@ -1,61 +1,61 @@
<div ng-controller="PluginCtrl">
<div id="bottom_left_panel">
<div class="panel shadowed-panel">
<div class="panel-head">
<table class="table-super-condensed table-striped">
<tr>
<th class="small">Machine name</th>
<td class="small">
<a target="_blank" href="https://secure.pub.build.mozilla.org/builddata/reports/slave_health/slave.html?name={{ job.machine_name }}">{{ job.machine_name }}</a>
</td>
</tr>
<tr ng-repeat="(label, value) in visibleFields"><th>{{label}}</th><td>{{ value | limitTo:12 }}</td></tr>
</table>
<div id="bottom_left_panel">
<div class="panel shadowed-panel">
<div class="panel-head">
<table class="table-super-condensed table-striped">
<tr>
<th class="small">Machine name</th>
<td class="small">
<a target="_blank" href="https://secure.pub.build.mozilla.org/builddata/reports/slave_health/slave.html?name={{ job.machine_name }}">{{ job.machine_name }}</a>
</td>
</tr>
<tr ng-repeat="(label, value) in visibleFields"><th>{{label}}</th><td>{{ value | limitTo:12 }}</td></tr>
</table>
<div ng-show="newNote">
<form ng-submit="saveNote()" class="form-inline">
<select ng-model="newNote.failure_classification_id">
<option ng-repeat="(value, star) in starTypes" value="{{value}}">{{star.name}}</option>
</select>
<input ng-model="newNote.note" focus-me="focusInput"/>
<input class="btn btn-primary" type="submit" value="save">
<a class="btn btn-default" ng-click="clearNewNote()">cancel</a>
</form>
</div>
<div ng-show="newNote">
<form ng-submit="saveNote()" class="form-inline">
<select ng-model="newNote.failure_classification_id">
<option ng-repeat="(value, star) in starTypes" value="{{value}}">{{star.name}}</option>
</select>
<input ng-model="newNote.note" focus-me="focusInput"/>
<input class="btn btn-primary" type="submit" value="save">
<a class="btn btn-default" ng-click="clearNewNote()">cancel</a>
</form>
</div>
</div>
</div>
<div id="bottom_center_panel">
<div class="panel shadowed-panel">
<div class="panel-body">
<tabset class="tabs-below">
<tab ng-repeat="tab in tabs" active="tab.active" disabled="tab.disabled">
<tab-heading>{{ tab.title }}
<span ng-show="!tab_loading" class="badge">{{tab.num_items}}</span>
<span class="waiter-small" ng-show="tab_loading"></span>
</tab-heading>
<ng-include src="tab.content"></ng-include>
</tab>
</tabset>
</div>
</div>
<div id="bottom_center_panel">
<div class="panel shadowed-panel">
<div class="panel-body resizable">
<tabset class="tabs-below">
<tab ng-repeat="(tab_id, tab) in tabs" active="tab.active" disabled="tab.disabled">
<tab-heading>{{ tab.title }}
<span ng-show="!tab.is_loading" class="badge">{{tab.num_items}}</span>
<span class="waiter-small" ng-show="tab.is_loading"></span>
</tab-heading>
<ng-include src="tab.content"></ng-include>
</tab>
</tabset>
</div>
</div>
<div id="bottom_menu">
<div class="btn-group-vertical">
<button type="button" class="btn btn-xs btn-default close-btn" ng-click="clearJob()">
<span class="glyphicon glyphicon-remove"></span> Close
</button>
<button class="btn btn-default btn-xs" title="add note" ng-click="addNote()">
<span class="glyphicon glyphicon-comment"></span> Add note
</button>
<div class="btn btn-default btn-xs"
ng-disabled="artifacts.length>0">
<a target="_blank" href="{{ lvUrl }}">Structured log</a>
</div>
<div class="btn btn-default btn-xs"
ng-repeat="joblog in logs">
<a target="_blank" href="{{ joblog.url }}">Raw log</a>
</div>
</div>
<div id="bottom_menu">
<div class="btn-group-vertical">
<button type="button" class="btn btn-xs btn-default close-btn" ng-click="clearJob()">
<span class="glyphicon glyphicon-remove"></span> Close
</button>
<button class="btn btn-default btn-xs" title="add note" ng-click="addNote()">
<span class="glyphicon glyphicon-comment"></span> Add note
</button>
<div class="btn btn-default btn-xs"
ng-disabled="artifacts.length>0">
<a target="_blank" href="{{ lvUrl }}">Structured log</a>
</div>
<div class="btn btn-default btn-xs"
ng-repeat="joblog in logs">
<a target="_blank" href="{{ joblog.url }}">Raw log</a>
</div>
</div>
</div>
</div>

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

@ -0,0 +1,50 @@
"use strict";
treeherder.controller('SimilarJobsPluginCtrl',
function SimilarJobsPluginCtrl($scope, $log, ThJobModel, thResultStatusInfo) {
$log.debug("similar jobs plugin initialized");
$scope.update_similar_jobs = function(newValue){
if(newValue){$scope.similar_jobs_count = 20;}
if($scope.job.id){
var options = {
count: $scope.similar_jobs_count
};
angular.forEach($scope.similar_jobs_filters, function(value, key){
if(value){
options[key] = $scope.job[key];
}
});
$log.log(options);
ThJobModel.get_list(options).then(function(data){
$scope.similar_jobs = data;
});
};
}
$scope.result_status_info = thResultStatusInfo
$scope.$watch('job.job_guid', $scope.update_similar_jobs, true);
$scope.similar_jobs = [];
$scope.similar_jobs_filters = {
"machine_id": false,
"job_type_id": true,
"build_platform_id": false
};
$scope.button_class = function(job){
var resultState = job.result;
if (job.state != "completed") {
resultState = job.state;
}
return thResultStatusInfo(resultState).btnClass
}
$scope.show_more = function(){
$scope.similar_jobs_count += 20;
$scope.update_similar_jobs()
};
}
);

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

@ -0,0 +1,34 @@
<div ng-controller="SimilarJobsPluginCtrl">
<h5>Similar jobs</h5>
<form role="form">
<div class="checkbox">
<label>
<input ng-change="update_similar_jobs()" type="checkbox" ng-model="similar_jobs_filters.machine_id"/>
Same machine: {{ job.machine_name }}
</label>
</div>
<div class="checkbox">
<label>
<input ng-change="update_similar_jobs()" type="checkbox" ng-model="similar_jobs_filters.job_type_id"/>
Same job type: {{ job.job_group_name }} {{ job.job_type_name }} ({{ job.job_type_symbol }})
</label>
</div>
<div class="checkbox">
<label>
<input ng-change="update_similar_jobs()" type="checkbox" ng-model="similar_jobs_filters.build_platform_id"/>
Same platform: {{ job.platform }}
</label>
</div>
</form>
<p>
<button class="btn btn-similar-jobs btn-xs" ng-class="button_class(job)"
ng-repeat="job in similar_jobs | orderBy: -submit_timestamp">
{{job.job_type_symbol}}
</button>
</p>
<a ng-click="show_more()">Show more</a>
</div>

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

@ -1,73 +1,92 @@
"use strict";
treeherder.controller('TinderboxPluginCtrl',
function TinderboxPluginCtrl($scope, $rootScope, $log) {
function TinderboxPluginCtrl($scope, $rootScope, $log, ThJobArtifactModel) {
$log.debug("Tinderbox plugin initialized");
$scope.$watch('artifacts', function(newValue, oldValue){
var update_job_info = function(newValue, oldValue){
$scope.tinderbox_lines = [];
$scope.tinderbox_lines_parsed = []
// ``artifacts`` is set as a result of a promise, so we must have
// the watch have ``true`` as the last param to watch the value,
// not just the reference. We also must check for ``blob`` in ``Job Info``
// because ``Job Info`` can exist without the blob as the promise is
// fulfilled.
if (newValue && newValue.hasOwnProperty('Job Info') && newValue['Job Info'].hasOwnProperty('blob')){
$scope.tinderbox_lines = newValue['Job Info'].blob.tinderbox_printlines;
for(var i=0; i<$scope.tinderbox_lines.length; i++){
var line = $scope.tinderbox_lines[i];
if(line.indexOf("<a href='http://graphs.mozilla.org") == 0){
continue;
}
var title = line;
var value = "";
var link = "";
var type = ""
$scope.tinderbox_lines_parsed = [];
$scope.tabs.tinderbox.num_items = 0;
if(newValue){
$scope.tabs.tinderbox.is_loading = true;
ThJobArtifactModel.get_list({
name: "Job Info",
"type": "json",
job_id: newValue
})
.then(function(data){
// ``artifacts`` is set as a result of a promise, so we must have
// the watch have ``true`` as the last param to watch the value,
// not just the reference. We also must check for ``blob`` in ``Job Info``
// because ``Job Info`` can exist without the blob as the promise is
// fulfilled.
if (data.length == 1 && _.has(data[0], 'blob')){
var seps = [": ", "<br/>"];
var sep = false;
$scope.tinderbox_lines = data[0].blob.tinderbox_printlines;
for(var i=0; i<$scope.tinderbox_lines.length; i++){
var line = $scope.tinderbox_lines[i];
if(line.indexOf("<a href='http://graphs.mozilla.org") == 0){
continue;
}
var title = line;
var value = "";
var link = "";
var type = "";
for(var j=0; j<seps.length; j++){
if(line.indexOf(seps[j]) !== -1){
sep = seps[j];
var seps = [": ", "<br/>"];
var sep = false;
for(var j=0; j<seps.length; j++){
if(line.indexOf(seps[j]) !== -1){
sep = seps[j];
}
}
if(sep){
var chunks = line.split(sep);
title = chunks[0];
value = chunks.slice(1).join(sep);
if(title.indexOf("link") !== -1){
link = value;
type = "link";
}
if(title == "TalosResult"){
type = "TalosResult";
// unescape the json string
value = value.replace(/\\/g, '');
value = angular.fromJson(value);
}
if(sep == "<br/>" || sep.indexOf("<") !== -1){
type="raw_html";
}
}else{
var uploaded_to_regexp = /\s*Uploaded\s+([A-Za-z\/\.0-9\-_]+)\s+to\s+(http:\/\/[A-Za-z\/\.0-9\-_]+)\s*/g;
var uploaded_to_chunks = uploaded_to_regexp.exec(title);
if(uploaded_to_chunks != null){
title = "artifact uploaded"
value = uploaded_to_chunks[1];
link = uploaded_to_chunks[2];
type = "link";
}
}
$scope.tinderbox_lines_parsed.push({
title:title,
value:value,
link:link,
type: type
});
}
}
if(sep){
var chunks = line.split(sep);
title = chunks[0];
value = chunks.slice(1).join(sep);
if(title.indexOf("link") !== -1){
link = value;
type = "link"
}
if(title == "TalosResult"){
type = "TalosResult";
// unescape the json string
value = value.replace(/\\/g, '')
value = angular.fromJson(value);
}
if(sep == "<br/>" || sep.indexOf("<") !== -1){
type="raw_html"
}
}
$scope.tinderbox_lines_parsed.push({
title:title,
value:value,
link:link,
type: type
});
}
// set the item count on the tab header
$scope.tabs.tinderbox.num_items = $scope.tinderbox_lines_parsed.length;
})
.finally(function(){
$scope.tabs.tinderbox.is_loading = false;
});
}
for(var tab=0; tab<$scope.tabs.length; tab++){
if ($scope.tabs[tab].id == 'tinderbox'){
$scope.tabs[tab].num_items = $scope.tinderbox_lines_parsed.length;
}
}
}, true);
};
$scope.$watch("job.id", update_job_info, true);
}
);