зеркало из https://github.com/mozilla/treeherder.git
merged in job-classification branch from mauro
This commit is contained in:
Родитель
f1d7c9f4aa
Коммит
66b94a6a0d
|
@ -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>
|
||||
|
|
16
ui/js/app.js
16
ui/js/app.js
|
@ -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);
|
||||
}
|
||||
);
|
||||
|
|
Загрузка…
Ссылка в новой задаче