зеркало из https://github.com/mozilla/treeherder.git
merged changes from master
This commit is contained in:
Коммит
6b0c56eb29
|
@ -1,12 +1,12 @@
|
|||
body {
|
||||
padding-top: 64px;
|
||||
padding-top: 61px;
|
||||
padding-bottom: 500px;
|
||||
min-width: 800px;
|
||||
min-width: 970px;
|
||||
width: auto !important;
|
||||
width: 1300px;
|
||||
}
|
||||
|
||||
.nav, .pagination, .carousel, .panel-title a {
|
||||
.pagination, .carousel, .panel-title a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
@ -15,12 +15,16 @@ body {
|
|||
visibility: hidden;
|
||||
}
|
||||
|
||||
.full-height {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.th-navbar {
|
||||
min-width: 800px;
|
||||
min-width: 970px;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
min-width: 800px;
|
||||
min-width: 970px;
|
||||
min-height: 36px;
|
||||
}
|
||||
|
||||
|
@ -38,9 +42,19 @@ body {
|
|||
|
||||
.th-global-navbar {
|
||||
border-bottom: 1px solid black;
|
||||
min-width: 800px;
|
||||
min-width: 970px;
|
||||
}
|
||||
|
||||
.watched-repo-dropdown-item {
|
||||
margin: 0px 10px;
|
||||
}
|
||||
.watched-repo-dropdown-item > a {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.watched-repo-navbar {
|
||||
overflow: visible;
|
||||
}
|
||||
.th-username {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
@ -51,14 +65,34 @@ body {
|
|||
|
||||
.th-context-navbar {
|
||||
background-color: #354048;
|
||||
height: 30px;
|
||||
min-width: 800px;
|
||||
min-width: 970px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.treeClosed {
|
||||
color: rgb(161, 52, 53);
|
||||
}
|
||||
|
||||
.treeOpen {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.treeApproval {
|
||||
color: #fb9910;
|
||||
}
|
||||
|
||||
.treeUnavailable {
|
||||
color: lightgray;
|
||||
}
|
||||
|
||||
#platform-job-text-search-field {
|
||||
height:28px;
|
||||
}
|
||||
|
||||
.th-content {
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
min-width: 800px;
|
||||
min-width: 970px;
|
||||
width: 100%;
|
||||
padding-bottom: 101px;
|
||||
white-space: nowrap;
|
||||
|
@ -76,8 +110,8 @@ body {
|
|||
background-color: lightgray;
|
||||
padding: 10px;
|
||||
border: 1px solid black;
|
||||
max-height: 500px;
|
||||
overflow: auto;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.th-top-nav-options-panel .th-option-heading {
|
||||
|
@ -149,6 +183,15 @@ body {
|
|||
min-width: 225px;
|
||||
}
|
||||
|
||||
.th-repo-group-items .dropdown-menu {
|
||||
top: inherit;
|
||||
left: inherit;
|
||||
}
|
||||
|
||||
.th-repo-group-items .dropdown-toggle {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/**
|
||||
Failures
|
||||
*/
|
||||
|
@ -241,8 +284,10 @@ body {
|
|||
|
||||
.revision-link {
|
||||
padding-top: 5px;
|
||||
width: 115px;
|
||||
width: 130px;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.revision-button {
|
||||
|
@ -262,6 +307,7 @@ body {
|
|||
.result-set .job-list-nopad {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.selected-job {
|
||||
|
@ -313,10 +359,14 @@ div.navbar-fixed-bottom{
|
|||
}
|
||||
|
||||
div.navbar-fixed-bottom .tab-content{
|
||||
height:160px;
|
||||
height: calc(100% - 22px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.bottom-panel-tabs {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.bottom-panel-tabs .nav li a {
|
||||
padding: 1px 10px 2px 10px;
|
||||
}
|
||||
|
@ -325,34 +375,55 @@ div.navbar-fixed-bottom dt,
|
|||
div.navbar-fixed-bottom dt {
|
||||
font-size:.8em;
|
||||
}
|
||||
#bottom-left-panel {width:242px; position: absolute; left:5px;}
|
||||
#bottom-left-panel .panel{padding:5px;}
|
||||
#bottom-left-panel {
|
||||
width:242px;
|
||||
position: absolute;
|
||||
left:5px;
|
||||
bottom: 5px;
|
||||
}
|
||||
#bottom-left-panel .panel{
|
||||
padding:5px;
|
||||
}
|
||||
|
||||
#bottom-left-panel .panel-head{
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#bottom-left-panel .table-super-condensed {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#bottom-center-panel {
|
||||
margin-right: 90px;
|
||||
margin-right: 98px;
|
||||
margin-left: 242px;
|
||||
}
|
||||
#bottom-menu {position: absolute; right:-5px; width: 100px}
|
||||
#bottom-menu {position: absolute; right:0; width: 104px}
|
||||
|
||||
.result-status-shading-success {background-color: rgba(120, 196, 192, 0.54);}
|
||||
.result-status-shading-testfailed {background-color: rgba(221, 102, 2, 0.56);}
|
||||
.result-status-shading-busted {background-color: rgba(144, 0, 0, 0.60);}
|
||||
.result-status-shading-exception {background-color: rgba(61, 2, 85, 0.50);}
|
||||
.result-status-shading-retry {background-color: #263fc3;}
|
||||
.result-status-shading-usercancel {background-color: rgba(250, 115, 172, 0.63)}
|
||||
.result-status-shading-success {background-color: rgba(2, 131, 44, 0.24);}
|
||||
.result-status-shading-testfailed {background-color: rgba(221, 102, 2, 0.25);}
|
||||
.result-status-shading-busted {background-color: rgba(144, 0, 0, 0.25);}
|
||||
.result-status-shading-exception {background-color: rgba(61, 2, 85, 0.25);}
|
||||
.result-status-shading-retry {background-color: rgba(38, 63, 195, 0.25);}
|
||||
.result-status-shading-usercancel {background-color: rgba(250, 115, 172, 0.25)}
|
||||
.result-status-shading-pending {background-color: white;}
|
||||
.result-status-shading-running {background-color: white;}
|
||||
|
||||
.bottom-shadowed-panel {
|
||||
height: 195px;
|
||||
.bottom-shadowed-panel-with-pinboard {
|
||||
height: calc(100% - 75px);
|
||||
}
|
||||
|
||||
.bottom-shadowed-panel-without-pinboard {
|
||||
height: calc(100% - 20px);
|
||||
}
|
||||
|
||||
.bottom-menu-group {
|
||||
width: 90px;
|
||||
width: 98px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
width: 73px;
|
||||
width: 81px;
|
||||
}
|
||||
|
||||
.save-btn-dropdown {
|
||||
|
@ -407,12 +478,12 @@ div.navbar-fixed-bottom dt {
|
|||
}
|
||||
|
||||
.pinboard-classification-comment {
|
||||
width: 185px;
|
||||
width: 177px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.pinboard-classification-select {
|
||||
width: 185px;
|
||||
width: 177px;
|
||||
}
|
||||
|
||||
.pinned-job {
|
||||
|
@ -427,7 +498,10 @@ div.navbar-fixed-bottom dt {
|
|||
margin-top: 0.3em;
|
||||
}
|
||||
|
||||
.panel-body{ padding:5px;}
|
||||
.panel-body{
|
||||
padding:5px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.timestamp-name {
|
||||
overflow: hidden;
|
||||
|
@ -502,13 +576,16 @@ div.navbar-fixed-bottom dt {
|
|||
width: 69px;
|
||||
}
|
||||
|
||||
.click-able-icon, .nav-tabs li {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* CUSTOM BUTTONS
|
||||
*/
|
||||
|
||||
.btn-view-nav {
|
||||
background-color: rgba(63, 74, 81, 0.56);
|
||||
background-color: rgba(75, 86, 93, 0.56);
|
||||
border-color: #22282d;
|
||||
color: lightgray;
|
||||
border-radius: 0;
|
||||
|
@ -1015,7 +1092,7 @@ fieldset[disabled] .btn-repo.active {
|
|||
position:fixed;
|
||||
top:70px;
|
||||
right:10px;
|
||||
z-index: 9000;
|
||||
z-index: 1100;
|
||||
}
|
||||
|
||||
#notification_box div.alert{
|
||||
|
@ -1044,4 +1121,6 @@ fieldset[disabled] .btn-repo.active {
|
|||
.form-group-inline>.form-group{
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
|
||||
div.logviewer-step{ padding:6px 6px;}
|
||||
|
|
22
ui/help.html
22
ui/help.html
|
@ -54,12 +54,8 @@
|
|||
<div class="panel-heading"><h3>Keyboard shortcuts</h3></div>
|
||||
<div class="panel-body">
|
||||
<table id="shortcuts">
|
||||
<tr><th>space</th>
|
||||
<td>Select/deselect active build or changeset</td></tr>
|
||||
<tr><th>?</th>
|
||||
<td>Show the help box</td></tr>
|
||||
<tr><th>c</th>
|
||||
<td>Show the comment box</td></tr>
|
||||
<tr><th>s</th>
|
||||
<td>Add selected job to the pin board</td></tr>
|
||||
<tr><th>j</th>
|
||||
<td>Highlight next unstarred failure</td></tr>
|
||||
<tr><th>k</th>
|
||||
|
@ -69,15 +65,9 @@
|
|||
<tr><th>p</th>
|
||||
<td>Highlight previous unstarred failure</td></tr>
|
||||
<tr><th>u</th>
|
||||
<td>Toggle showing only unstarred failures</td></tr>
|
||||
<tr><th>Click</th>
|
||||
<td>Choose an active build and display its details</td></tr>
|
||||
<tr><th>Ctrl/Cmd-Click</th>
|
||||
<td>Select/deselect build or changeset</td></tr>
|
||||
<tr><th>Drag</th>
|
||||
<td>Add build or changeset to comment</td></tr>
|
||||
<tr><th>Ctrl/Cmd-Enter</th>
|
||||
<td>Submit the comment form</td></tr>
|
||||
<td>Show only unstarred failures</td></tr>
|
||||
<tr><th>Shift-Click</th>
|
||||
<td>Add job to the pinboard</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -317,4 +307,4 @@
|
|||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -13,16 +13,11 @@
|
|||
<link href="css/persona-buttons.css" rel="stylesheet" media="screen">
|
||||
|
||||
</head>
|
||||
<body ng-controller="MainCtrl">
|
||||
<th-global-top-nav-panel></th-global-top-nav-panel>
|
||||
<body ng-controller="MainCtrl" ng-keydown="processKeyboardInput($event)">
|
||||
<ng-include id="th-global-top-nav-panel" src="'partials/thGlobalTopNavPanel.html'"></ng-include>
|
||||
<div class="th-content">
|
||||
|
||||
<span class="th-view-content" ng-cloak>
|
||||
<div class="alert"
|
||||
ng-bind="statusMsg"
|
||||
ng-show="statusMsg"
|
||||
ng-class="{'alert-success': (statusColor=='green'), 'alert-error': (statusColor=='red')}">
|
||||
</div>
|
||||
<ng-view ></ng-view>
|
||||
</span>
|
||||
|
||||
|
@ -31,7 +26,7 @@
|
|||
<!-- Footer -->
|
||||
<div class="nav navbar navbar-default navbar-fixed-bottom bottom-panel" ng-show="selectedJob">
|
||||
<resizable-panel></resizable-panel>
|
||||
<div ng-include src="'plugins/pluginpanel.html'"></div>
|
||||
<div class="full-height" ng-include src="'plugins/pluginpanel.html'"></div>
|
||||
</div>
|
||||
|
||||
<th-notification-box></th-notification-box>
|
||||
|
@ -47,26 +42,39 @@
|
|||
<script src="vendor/socket.io.js"></script>
|
||||
<script src="vendor/angular-local-storage.min.js"></script>
|
||||
<script src="vendor/underscore-min.js"></script>
|
||||
<script src="js/config/local.conf.js"></script>
|
||||
|
||||
<script src="js/app.js"></script>
|
||||
<script src="js/services/log.js"></script>
|
||||
<script src="js/config/local.conf.js"></script>
|
||||
<script src="js/providers.js"></script>
|
||||
<script src="js/directives.js"></script>
|
||||
<!-- Directives -->
|
||||
<script src="js/directives/main.js"></script>
|
||||
<script src="js/directives/clonejobs.js"></script>
|
||||
<script src="js/directives/persona.js"></script>
|
||||
<script src="js/directives/resultsets.js"></script>
|
||||
<script src="js/directives/top_nav_bar.js"></script>
|
||||
<script src="js/directives/bottom_nav_panel.js"></script>
|
||||
<!-- Main services -->
|
||||
<script src="js/services/main.js"></script>
|
||||
<script src="js/services/jobfilters.js"></script>
|
||||
<script src="js/services/classifications.js"></script>
|
||||
<script src="js/services/pinboard.js"></script>
|
||||
<script src="js/services/resultsets.js"></script>
|
||||
<script src="js/services/models/resultsets.js"></script>
|
||||
<script src="js/services/models/job_artifact.js"></script>
|
||||
<script src="js/services/models/job_filter.js"></script>
|
||||
<script src="js/services/models/exclusion_profile.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/build_platform.js"></script>
|
||||
<script src="js/services/models/job_type.js"></script>
|
||||
<script src="js/services/models/classification.js"></script>
|
||||
<script src="js/services/models/option.js"></script>
|
||||
<script src="js/services/models/job.js"></script>
|
||||
<script src="js/services/models/user.js"></script>
|
||||
<script src="js/services/treestatus.js"></script>
|
||||
<!-- Model services -->
|
||||
<script src="js/models/resultsets.js"></script>
|
||||
<script src="js/models/job_artifact.js"></script>
|
||||
<script src="js/models/repository.js"></script>
|
||||
<script src="js/models/bug_job_map.js"></script>
|
||||
<script src="js/models/classification.js"></script>
|
||||
<script src="js/models/job.js"></script>
|
||||
<script src="js/models/job_filter.js"></script>
|
||||
<script src="js/models/exclusion_profile.js"></script>
|
||||
<script src="js/models/build_platform.js"></script>
|
||||
<script src="js/models/job_type.js"></script>
|
||||
<script src="js/models/option.js"></script>
|
||||
<script src="js/models/user.js"></script>
|
||||
<!-- Controllers -->
|
||||
<script src="js/controllers/main.js"></script>
|
||||
<script src="js/controllers/sheriff.js"></script>
|
||||
<script src="js/controllers/settings.js"></script>
|
||||
|
@ -75,12 +83,14 @@
|
|||
<script src="js/controllers/jobs.js"></script>
|
||||
<script src="js/controllers/machines.js"></script>
|
||||
<script src="js/controllers/timeline.js"></script>
|
||||
<!-- Plugins -->
|
||||
<script src="plugins/controller.js"></script>
|
||||
<script src="plugins/pinboard.js"></script>
|
||||
<script src="plugins/annotations/controller.js"></script>
|
||||
<script src="plugins/tinderbox/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>
|
||||
|
@ -147,9 +157,28 @@
|
|||
|
||||
<!-- Job Btn span -->
|
||||
<script type="'text/ng-template'" id="jobBtnClone.html">
|
||||
<span style="margin-right:1px;" class="btn job-btn btn-xs {{ btnClass }}" data-jmkey="{{ key }}" title="{{ title }}">{{ value }}</span>
|
||||
<span style="margin-right:1px;" class="btn job-btn btn-xs {{ btnClass }} {{ key }}" data-jmkey="{{ key }}" title="{{ title }}">{{ value }}</span>
|
||||
</script>
|
||||
|
||||
|
||||
<!-- Tooltip for job info-->
|
||||
<script type="'text/ng-template'" id="jobInfoTooltip.html">
|
||||
<div>
|
||||
<table class="table-super-condensed table-striped">
|
||||
<tr>
|
||||
<th class="small">Result</th>
|
||||
<td class="small {{ resultStatusClass }}">{{ job.result }}</td>
|
||||
</tr>
|
||||
<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 }}</td></tr>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
14
ui/js/app.js
14
ui/js/app.js
|
@ -43,18 +43,4 @@ 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,6 +1,34 @@
|
|||
'use strict';
|
||||
|
||||
// mozilla hosted service
|
||||
//window.thServiceDomain = "http://dev.treeherder.mozilla.org";
|
||||
|
||||
// local vagrant instance of service
|
||||
window.thServiceDomain = "http://local.treeherder.mozilla.org";
|
||||
|
||||
treeherder.config(['$logProvider', 'ThLogConfigProvider',
|
||||
function($logProvider, ThLogConfigProvider) {
|
||||
|
||||
// enable or disable debug messages using $log.
|
||||
// comment out the next line to enable them
|
||||
$logProvider.debugEnabled(true);
|
||||
|
||||
// add classes to the blacklist. all debug messages except
|
||||
// these will print
|
||||
ThLogConfigProvider.setBlacklist([
|
||||
// 'thRepoDropDown',
|
||||
// 'RepositoryPanelCtrl',
|
||||
// 'thWatchedRepo'
|
||||
// ...
|
||||
]);
|
||||
|
||||
// add classes to the whitelist. Only debug messages with
|
||||
// these classes will print
|
||||
ThLogConfigProvider.setWhitelist([
|
||||
// 'thRepoDropDown',
|
||||
// 'RepositoryPanelCtrl',
|
||||
// 'thWatchedRepo',
|
||||
// ...
|
||||
]);
|
||||
|
||||
}]);
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
"use strict";
|
||||
|
||||
treeherder.controller('StatusFilterPanelCtrl',
|
||||
function StatusFilterPanelCtrl($scope, $rootScope, $routeParams, $location, $log,
|
||||
localStorageService, thResultStatusList, thEvents, thJobFilters) {
|
||||
treeherder.controller('FilterPanelCtrl',
|
||||
function FilterPanelCtrl($scope, $rootScope, $routeParams, $location, ThLog,
|
||||
localStorageService, thResultStatusList, thEvents,
|
||||
thJobFilters) {
|
||||
var $log = new ThLog(this.constructor.name);
|
||||
|
||||
$scope.filterOptions = thResultStatusList;
|
||||
|
||||
|
@ -29,8 +31,10 @@ treeherder.controller('StatusFilterPanelCtrl',
|
|||
|
||||
/**
|
||||
* Handle checking the "all" button for a result status group
|
||||
*
|
||||
* quiet - whether or not to broadcast a message about this change.
|
||||
*/
|
||||
$scope.toggleResultStatusGroup = function(group) {
|
||||
$scope.toggleResultStatusGroup = function(group, quiet) {
|
||||
var check = function(rs) {
|
||||
$scope.resultStatusFilters[rs] = group.allChecked;
|
||||
};
|
||||
|
@ -41,9 +45,35 @@ treeherder.controller('StatusFilterPanelCtrl',
|
|||
group.resultStatuses,
|
||||
group.allChecked
|
||||
);
|
||||
$rootScope.$broadcast(thEvents.globalFilterChanged,
|
||||
{target: group, newValue: group.allChecked});
|
||||
showCheck();
|
||||
|
||||
if (!quiet) {
|
||||
$rootScope.$broadcast(thEvents.globalFilterChanged,
|
||||
{target: group, newValue: group.allChecked});
|
||||
}
|
||||
};
|
||||
|
||||
$rootScope.$on(thEvents.showUnclassifiedFailures, function() {
|
||||
$scope.showUnclassifiedFailures();
|
||||
});
|
||||
|
||||
/**
|
||||
* Handle clicking the ``unclassified failures`` button.
|
||||
*/
|
||||
$scope.showUnclassifiedFailures = function() {
|
||||
$scope.filterGroups.failures.allChecked = true;
|
||||
$scope.filterGroups.nonfailures.allChecked = false;
|
||||
$scope.filterGroups.inProgress.allChecked = false;
|
||||
$scope.classifiedFilter = false;
|
||||
$scope.unClassifiedFilter = true;
|
||||
|
||||
$scope.toggleResultStatusGroup($scope.filterGroups.failures, true);
|
||||
$scope.toggleResultStatusGroup($scope.filterGroups.nonfailures, true);
|
||||
$scope.toggleResultStatusGroup($scope.filterGroups.inProgress, true);
|
||||
|
||||
$scope.setClassificationFilter(true, $scope.classifiedFilter, true);
|
||||
$scope.setClassificationFilter(false, $scope.unClassifiedFilter, true);
|
||||
|
||||
$rootScope.$broadcast(thEvents.globalFilterChanged);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -62,7 +92,15 @@ treeherder.controller('StatusFilterPanelCtrl',
|
|||
}
|
||||
$rootScope.$broadcast(thEvents.globalFilterChanged,
|
||||
{target: filter, newValue: $scope.resultStatusFilters[filter]});
|
||||
showCheck();
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle the filters to show either unclassified or classified jobs,
|
||||
* neither or both.
|
||||
*/
|
||||
$scope.toggleClassificationFilter = function(isClassified) {
|
||||
var isChecked = !(isClassified? $scope.classifiedFilter: $scope.unClassifiedFilter);
|
||||
$scope.setClassificationFilter(isClassified, isChecked, false);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -72,19 +110,19 @@ treeherder.controller('StatusFilterPanelCtrl',
|
|||
* ``classified`` (when true) or ``unclassified``
|
||||
* (when false)
|
||||
*/
|
||||
$scope.toggleClassificationFilter = function(isClassified) {
|
||||
$scope.setClassificationFilter = function(isClassified, isChecked, quiet) {
|
||||
var field = "failure_classification_id";
|
||||
// this function is called before the checkbox value has actually
|
||||
// changed the scope model value, so change to the inverse.
|
||||
var isChecked = !(isClassified? $scope.classifiedFilter: $scope.unClassifiedFilter);
|
||||
var func = isChecked? thJobFilters.addFilter: thJobFilters.removeFilter;
|
||||
var target = isClassified? "classified": "unclassified";
|
||||
|
||||
func(field, isClassified, thJobFilters.matchType.isnull);
|
||||
func(field, isClassified, thJobFilters.matchType.bool);
|
||||
|
||||
$rootScope.$broadcast(thEvents.globalFilterChanged,
|
||||
{target: target, newValue: isChecked});
|
||||
showCheck();
|
||||
if (!quiet) {
|
||||
$rootScope.$broadcast(thEvents.globalFilterChanged,
|
||||
{target: target, newValue: isChecked});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.createFieldFilter = function() {
|
||||
|
@ -96,7 +134,7 @@ treeherder.controller('StatusFilterPanelCtrl',
|
|||
|
||||
|
||||
$scope.addFieldFilter = function() {
|
||||
$log.debug("adding filter of " + $scope.newFieldFilter.field);
|
||||
$log.debug("adding filter", $scope.newFieldFilter.field);
|
||||
if (!$scope.newFieldFilter || $scope.newFieldFilter.field === "" || $scope.newFieldFilter.value === "") {
|
||||
return;
|
||||
}
|
||||
|
@ -112,7 +150,6 @@ treeherder.controller('StatusFilterPanelCtrl',
|
|||
$rootScope.$broadcast(thEvents.globalFilterChanged,
|
||||
{target: $scope.newFieldFilter.field, newValue: $scope.newFieldFilter.value});
|
||||
$scope.newFieldFilter = null;
|
||||
showCheck();
|
||||
|
||||
};
|
||||
|
||||
|
@ -123,11 +160,10 @@ treeherder.controller('StatusFilterPanelCtrl',
|
|||
$rootScope.$broadcast(thEvents.globalFilterChanged,
|
||||
{target: "allFieldFilters", newValue: null});
|
||||
$scope.fieldFilters = [];
|
||||
showCheck();
|
||||
};
|
||||
|
||||
$scope.removeFilter = function(index) {
|
||||
$log.debug("removing index: " + index);
|
||||
$log.debug("removing index", index);
|
||||
thJobFilters.removeFilter(
|
||||
$scope.fieldFilters[index].field,
|
||||
$scope.fieldFilters[index].value
|
||||
|
@ -135,40 +171,11 @@ treeherder.controller('StatusFilterPanelCtrl',
|
|||
$rootScope.$broadcast(thEvents.globalFilterChanged,
|
||||
{target: $scope.fieldFilters[index].field, newValue: null});
|
||||
$scope.fieldFilters.splice(index, 1);
|
||||
showCheck();
|
||||
};
|
||||
|
||||
/*
|
||||
@@@ TODO: CAMD: test code, remove before merge.
|
||||
*/
|
||||
var jobs = [];
|
||||
$scope.filterGroups.inProgress.resultStatuses.forEach(function(rs) {jobs.push({
|
||||
state: rs,
|
||||
result: "unknown",
|
||||
failure_classification_id: null
|
||||
});});
|
||||
|
||||
$scope.filterGroups.failures.resultStatuses.forEach(function(rs) {jobs.push({
|
||||
state: "completed",
|
||||
result: rs,
|
||||
job_type_symbol: "A",
|
||||
job_type_name: "Apples",
|
||||
job_group_symbol: "M",
|
||||
job_group_name: "Mochitest",
|
||||
failure_classification_id: "bird"
|
||||
});});
|
||||
$scope.filterGroups.nonfailures.resultStatuses.forEach(function(rs) {jobs.push({
|
||||
state: "completed",
|
||||
result: rs
|
||||
});});
|
||||
|
||||
var showCheck = function() {
|
||||
jobs.forEach(function(job) {
|
||||
$log.debug("show job: " + JSON.stringify(job) + ": " + thJobFilters.showJob(job));
|
||||
});
|
||||
$log.debug(JSON.stringify(thJobFilters.getFilters()));
|
||||
$scope.pinAllShownJobs = function() {
|
||||
thJobFilters.pinAllShownJobs();
|
||||
};
|
||||
// END test code
|
||||
|
||||
$scope.resultStatusFilters = {};
|
||||
for (var i = 0; i < $scope.filterOptions.length; i++) {
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
"use strict";
|
||||
|
||||
treeherder.controller('JobsCtrl',
|
||||
function JobsCtrl($scope, $http, $rootScope, $routeParams, $log, $cookies,
|
||||
function JobsCtrl($scope, $http, $rootScope, $routeParams, ThLog, $cookies,
|
||||
localStorageService, thUrl, ThRepositoryModel, thSocket,
|
||||
ThResultSetModel, thResultStatusList) {
|
||||
var $log = new ThLog(this.constructor.name);
|
||||
|
||||
// load our initial set of resultsets
|
||||
// scope needs this function so it can be called directly by the user, too.
|
||||
|
@ -18,8 +19,9 @@ treeherder.controller('JobsCtrl',
|
|||
} else {
|
||||
$rootScope.repoName = "mozilla-inbound";
|
||||
}
|
||||
ThRepositoryModel.setCurrent($rootScope.repoName);
|
||||
|
||||
// load the list of repos into $rootScope, and set the current repo.
|
||||
ThRepositoryModel.load($scope.repoName);
|
||||
|
||||
ThResultSetModel.addRepository($scope.repoName);
|
||||
|
||||
|
@ -28,9 +30,6 @@ treeherder.controller('JobsCtrl',
|
|||
$scope.job_map = ThResultSetModel.getJobMap($scope.repoName);
|
||||
$scope.statusList = thResultStatusList;
|
||||
|
||||
// load the list of repos into $rootScope, and set the current repo.
|
||||
ThRepositoryModel.load($scope.repoName);
|
||||
|
||||
if(ThResultSetModel.isNotLoaded($scope.repoName)){
|
||||
// get our first set of resultsets
|
||||
$scope.fetchResultSets(10);
|
||||
|
@ -41,9 +40,11 @@ treeherder.controller('JobsCtrl',
|
|||
|
||||
|
||||
treeherder.controller('ResultSetCtrl',
|
||||
function ResultSetCtrl($scope, $rootScope, $http, $log, $location,
|
||||
function ResultSetCtrl($scope, $rootScope, $http, ThLog, $location,
|
||||
thUrl, thServiceDomain, thResultStatusInfo,
|
||||
ThResultSetModel, thEvents, thJobFilters, $route) {
|
||||
ThResultSetModel, thEvents, thJobFilters) {
|
||||
|
||||
var $log = new ThLog(this.constructor.name);
|
||||
|
||||
$scope.getCountClass = function(resultStatus) {
|
||||
return thResultStatusInfo(resultStatus).btnClass;
|
||||
|
@ -111,8 +112,8 @@ treeherder.controller('ResultSetCtrl',
|
|||
thEvents.resultSetFilterChanged, $scope.resultset
|
||||
);
|
||||
|
||||
$log.debug("toggled: " + resultStatus);
|
||||
$log.debug($scope.resultStatusFilters);
|
||||
$log.debug("toggled: ", resultStatus);
|
||||
$log.debug("resultStatusFilters", $scope.resultStatusFilters);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -125,7 +126,7 @@ treeherder.controller('ResultSetCtrl',
|
|||
};
|
||||
|
||||
$scope.revisionResultsetFilterUrl = $scope.urlBasePath + "?repo=" + $scope.repoName + "&revision=" + $scope.resultset.revision;
|
||||
$scope.authorResultsetFilterUrl = $scope.urlBasePath + "?repo=" + $scope.repoName + "&author=" + $scope.resultset.author;
|
||||
$scope.authorResultsetFilterUrl = $scope.urlBasePath + "?repo=" + $scope.repoName + "&author=" + encodeURIComponent($scope.resultset.author);
|
||||
|
||||
$scope.resultStatusFilters = thJobFilters.copyResultStatusFilters();
|
||||
|
||||
|
@ -135,7 +136,7 @@ treeherder.controller('ResultSetCtrl',
|
|||
$scope.isCollapsedRevisions = true;
|
||||
|
||||
$rootScope.$on(thEvents.jobContextMenu, function(event, job){
|
||||
$log.debug(thEvents.jobContextMenu + ' caught');
|
||||
$log.debug("caught", thEvents.jobContextMenu);
|
||||
//$scope.viewLog(job.resource_uri);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
logViewer.controller('LogviewerCtrl',
|
||||
function Logviewer($anchorScroll, $scope, $log, $rootScope, $location, $http, $timeout, ThJobArtifactModel) {
|
||||
function Logviewer($anchorScroll, $scope, ThLog, $rootScope, $location, $http, $timeout, ThJobArtifactModel) {
|
||||
|
||||
var $log = new ThLog("LogviewerCtrl");
|
||||
|
||||
var query_string = $location.search();
|
||||
if (query_string.repo !== "") {
|
||||
|
|
|
@ -1,22 +1,49 @@
|
|||
"use strict";
|
||||
|
||||
treeherder.controller('MainCtrl',
|
||||
function MainController($scope, $rootScope, $routeParams, $location, $log,
|
||||
function MainController($scope, $rootScope, $routeParams, $location, ThLog,
|
||||
localStorageService, ThRepositoryModel, thPinboard,
|
||||
ThExclusionProfileModel, thEvents) {
|
||||
$scope.query="";
|
||||
$scope.statusError = function(msg) {
|
||||
$rootScope.statusMsg = msg;
|
||||
$rootScope.statusColor = "red";
|
||||
};
|
||||
$scope.statusSuccess = function(msg) {
|
||||
$rootScope.statusMsg = msg;
|
||||
$rootScope.statusColor = "green";
|
||||
};
|
||||
thClassificationTypes, thEvents, $interval, ThExclusionProfileModel) {
|
||||
|
||||
var $log = new ThLog("MainCtrl");
|
||||
|
||||
thClassificationTypes.load();
|
||||
ThRepositoryModel.load();
|
||||
|
||||
$scope.clearJob = function() {
|
||||
// setting the selectedJob to null hides the bottom panel
|
||||
$rootScope.selectedJob = null;
|
||||
};
|
||||
$scope.processKeyboardInput = function(ev){
|
||||
|
||||
//Only listen to key commands when the body has focus. Otherwise
|
||||
//html input elements won't work correctly.
|
||||
if( (document.activeElement.nodeName !== 'BODY') ||
|
||||
(ev.keyCode === 16) ){
|
||||
return;
|
||||
}
|
||||
|
||||
if( (ev.keyCode === 74) || (ev.keyCode === 78) ){
|
||||
//Highlight next unclassified failure keys:j/n
|
||||
$rootScope.$broadcast(
|
||||
thEvents.selectNextUnclassifiedFailure
|
||||
);
|
||||
|
||||
}else if( (ev.keyCode === 75) || (ev.keyCode === 80) ){
|
||||
//Highlight previous unclassified failure keys:k/p
|
||||
$rootScope.$broadcast(
|
||||
thEvents.selectPreviousUnclassifiedFailure
|
||||
);
|
||||
|
||||
}else if(ev.keyCode === 83){
|
||||
//Select/deselect active build or changeset, keys:s
|
||||
$rootScope.$broadcast(thEvents.jobPin, $rootScope.selectedJob);
|
||||
|
||||
}else if(ev.keyCode === 85){
|
||||
//display only unclassified failures, keys:u
|
||||
$rootScope.$broadcast(thEvents.showUnclassifiedFailures);
|
||||
}
|
||||
};
|
||||
|
||||
// detect window width and put it in scope so items can react to
|
||||
// a narrow/wide window
|
||||
|
@ -30,6 +57,45 @@ treeherder.controller('MainCtrl',
|
|||
$scope.$apply();
|
||||
};
|
||||
|
||||
// the repos the user has chosen to watch
|
||||
$scope.watchedRepos = ThRepositoryModel.watchedRepos;
|
||||
|
||||
$scope.unwatchRepo = function(name) {
|
||||
ThRepositoryModel.unwatch(name);
|
||||
};
|
||||
|
||||
// update the repo status (treestatus) in an interval of every 2 minutes
|
||||
$interval(ThRepositoryModel.updateAllWatchedRepoTreeStatus, 2 * 60 * 1000);
|
||||
|
||||
$scope.getTopNavBarHeight = function() {
|
||||
return $("#th-global-top-nav-panel").find("#top-nav-main-panel").height();
|
||||
};
|
||||
|
||||
// adjust the body padding so we can see all the job/resultset data
|
||||
// if the top navbar height has changed due to window width changes
|
||||
// or adding enough watched repos to wrap.
|
||||
$rootScope.$watch($scope.getTopNavBarHeight, function(newValue) {
|
||||
$("body").css("padding-top", newValue);
|
||||
});
|
||||
|
||||
/**
|
||||
* The watched repos in the nav bar can be either on the left or the
|
||||
* right side of the screen and the drop-down menu may get cut off
|
||||
* if it pulls right while on the left side of the screen.
|
||||
* And it can change any time the user re-sized the window, so we must
|
||||
* check this each time a drop-down is invoked.
|
||||
*/
|
||||
$scope.setDropDownPull = function(event) {
|
||||
$log.debug("dropDown", event.target);
|
||||
var element = event.target.offsetParent;
|
||||
if (element.offsetLeft > $scope.getWidth() / 2) {
|
||||
$(element).find(".dropdown-menu").addClass("pull-right");
|
||||
} else {
|
||||
$(element).find(".dropdown-menu").removeClass("pull-right");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// give the page a way to determine which nav toolbar to show
|
||||
$rootScope.$on('$locationChangeSuccess', function(ev,newUrl) {
|
||||
$rootScope.locationPath = $location.path().replace('/', '');
|
||||
|
@ -37,9 +103,6 @@ treeherder.controller('MainCtrl',
|
|||
|
||||
$rootScope.urlBasePath = $location.absUrl().split('?')[0];
|
||||
|
||||
// the repos the user has chosen to watch
|
||||
$scope.watchedRepos = ThRepositoryModel.watchedRepos;
|
||||
|
||||
$scope.changeRepo = function(repo_name) {
|
||||
// hide the repo panel if they chose to load one.
|
||||
$scope.isRepoPanelShowing = false;
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
"use strict";
|
||||
|
||||
treeherder.controller('RepositoryPanelCtrl',
|
||||
function RepositoryPanelCtrl($scope, $rootScope, $routeParams, $location, $log,
|
||||
function RepositoryPanelCtrl($scope, $rootScope, $routeParams, $location, ThLog,
|
||||
localStorageService, ThRepositoryModel, thSocket) {
|
||||
|
||||
$scope.saveWatchedRepos = function() {
|
||||
ThRepositoryModel.saveWatchedRepos();
|
||||
};
|
||||
var $log = new ThLog(this.constructor.name);
|
||||
|
||||
for (var repo in $scope.watchedRepos) {
|
||||
if($scope.watchedRepos[repo]){
|
||||
|
@ -14,6 +11,9 @@ treeherder.controller('RepositoryPanelCtrl',
|
|||
$log.debug("subscribing to "+repo+".job_failure");
|
||||
}
|
||||
}
|
||||
|
||||
$scope.toggleRepo = function(repoName) {
|
||||
$scope.watchedRepos[repoName].isWatched = !$scope.watchedRepos[repoName].isWatched;
|
||||
ThRepositoryModel.watchedReposUpdated(repoName);
|
||||
};
|
||||
}
|
||||
);
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
'use strict';
|
||||
|
||||
|
||||
treeherder.directive('thPinnedJob', function (thResultStatusInfo) {
|
||||
|
||||
var getHoverText = function(job) {
|
||||
var duration = Math.round((job.end_timestamp - job.start_timestamp) / 60);
|
||||
var status = job.result;
|
||||
if (job.state !== "completed") {
|
||||
status = job.state;
|
||||
}
|
||||
return job.job_type_name + " - " + status + " - " + duration + "mins";
|
||||
};
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
link: function(scope, element, attrs) {
|
||||
var unbindWatcher = scope.$watch("job", function(newValue) {
|
||||
var resultState = scope.job.result;
|
||||
if (scope.job.state !== "completed") {
|
||||
resultState = scope.job.state;
|
||||
}
|
||||
scope.job.display = thResultStatusInfo(resultState);
|
||||
scope.hoverText = getHoverText(scope.job);
|
||||
|
||||
if (scope.job.state === "completed") {
|
||||
//Remove watchers when a job has a completed status
|
||||
unbindWatcher();
|
||||
}
|
||||
|
||||
}, true);
|
||||
},
|
||||
templateUrl: 'partials/thPinnedJob.html'
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive('thRelatedBugSaved', function () {
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
templateUrl: 'partials/thRelatedBugSaved.html'
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive('thRelatedBugQueued', function () {
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
templateUrl: 'partials/thRelatedBugQueued.html'
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive('thFailureClassification', function ($parse, thClassificationTypes) {
|
||||
return {
|
||||
scope: {
|
||||
failureId: "="
|
||||
},
|
||||
link: function(scope, element, attrs) {
|
||||
scope.$watch('failureId', function(newVal) {
|
||||
if (newVal) {
|
||||
scope.classification = thClassificationTypes.classifications[newVal];
|
||||
scope.badgeColorClass=scope.classification.star;
|
||||
scope.hoverText=scope.classification.name;
|
||||
}
|
||||
});
|
||||
},
|
||||
template: '<span class="label {{ badgeColorClass}}" ' +
|
||||
'title="{{ hoverText }}">' +
|
||||
'<i class="glyphicon glyphicon-star-empty"></i>' +
|
||||
'</span> {{ hoverText }}'
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive('resizablePanel', function($document, ThLog) {
|
||||
return {
|
||||
restrict: "E",
|
||||
link: function(scope, element, attr) {
|
||||
var startY = 0;
|
||||
var container = $(element.parent());
|
||||
|
||||
element.css({
|
||||
position: 'absolute',
|
||||
cursor:'row-resize',
|
||||
top:'-2px',
|
||||
width: '100%',
|
||||
height: '5px',
|
||||
'z-index': '100'
|
||||
|
||||
});
|
||||
|
||||
element.on('mousedown', function(event) {
|
||||
// Prevent default dragging of selected content
|
||||
event.preventDefault();
|
||||
startY = event.pageY;
|
||||
$document.on('mousemove', mousemove);
|
||||
$document.on('mouseup', mouseup);
|
||||
});
|
||||
|
||||
function mousemove(event) {
|
||||
var y = startY - event.pageY;
|
||||
startY = event.pageY;
|
||||
container.height(container.height() + y);
|
||||
}
|
||||
|
||||
function mouseup() {
|
||||
$document.unbind('mousemove', mousemove);
|
||||
$document.unbind('mouseup', mouseup);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive('thSimilarJobs', function(ThJobModel, ThLog){
|
||||
return {
|
||||
restrict: "E",
|
||||
templateUrl: "partials/similar_jobs.html",
|
||||
link: function(scope, element, attr) {
|
||||
scope.$watch('job', function(newVal, oldVal){
|
||||
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){
|
||||
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('thPinboardPanel', function(){
|
||||
return {
|
||||
restrict: "E",
|
||||
templateUrl: "partials/thPinboardPanel.html"
|
||||
};
|
||||
});
|
|
@ -2,11 +2,19 @@
|
|||
|
||||
/* Directives */
|
||||
treeherder.directive('thCloneJobs', function(
|
||||
$rootScope, $http, $log, thUrl, thCloneHtml, thServiceDomain,
|
||||
$rootScope, $http, ThLog, thUrl, thCloneHtml, thServiceDomain,
|
||||
thResultStatusInfo, thEvents, thAggregateIds, thJobFilters,
|
||||
thResultStatusObject, ThResultSetModel){
|
||||
|
||||
var lastJobElSelected = {};
|
||||
var $log = new ThLog("thCloneJobs");
|
||||
|
||||
var lastJobElSelected, lastJobObjSelected;
|
||||
|
||||
var classificationRequired = {
|
||||
"busted":1,
|
||||
"exception":1,
|
||||
"testfailed":1
|
||||
};
|
||||
|
||||
// CSS classes
|
||||
var btnCls = 'btn-xs';
|
||||
|
@ -38,15 +46,73 @@ treeherder.directive('thCloneJobs', function(
|
|||
};
|
||||
|
||||
var getHoverText = function(job) {
|
||||
var duration = Math.round((job.end_timestamp - job.submit_timestamp) / 60);
|
||||
var jobStatus = job.result;
|
||||
if (job.state != "completed") {
|
||||
if (job.state !== "completed") {
|
||||
jobStatus = job.state;
|
||||
}
|
||||
return job.job_type_name + " - " + jobStatus + " - " + duration + "mins";
|
||||
var result = job.job_type_name + " - " + jobStatus;
|
||||
$log.debug("job timestamps", job, job.end_timestamp, job.submit_timestamp);
|
||||
if (job.end_timestamp && job.submit_timestamp) {
|
||||
var duration = Math.round((job.end_timestamp - job.submit_timestamp) / 60);
|
||||
result = result + " - " + duration + "mins";
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
var selectJob = function(el){
|
||||
//Global event listeners
|
||||
$rootScope.$on(
|
||||
thEvents.selectNextUnclassifiedFailure, function(ev){
|
||||
|
||||
var jobMap = ThResultSetModel.getJobMap($rootScope.repoName);
|
||||
|
||||
var targetEl, jobKey;
|
||||
if(!_.isEmpty(lastJobElSelected)){
|
||||
jobKey = getJobMapKey(lastJobObjSelected);
|
||||
getNextUnclassifiedFailure(jobMap[jobKey].job_obj);
|
||||
|
||||
}else{
|
||||
//Select the first unclassified failure
|
||||
getNextUnclassifiedFailure({});
|
||||
}
|
||||
});
|
||||
|
||||
$rootScope.$on(
|
||||
thEvents.selectPreviousUnclassifiedFailure, function(ev){
|
||||
|
||||
var jobMap = ThResultSetModel.getJobMap($rootScope.repoName);
|
||||
|
||||
var targetEl, jobKey;
|
||||
if(!_.isEmpty(lastJobElSelected)){
|
||||
jobKey = getJobMapKey(lastJobObjSelected);
|
||||
getPreviousUnclassifiedFailure(jobMap[jobKey].job_obj);
|
||||
|
||||
}else{
|
||||
//Select the first unclassified failure
|
||||
getPreviousUnclassifiedFailure({});
|
||||
}
|
||||
|
||||
});
|
||||
$rootScope.$on(
|
||||
thEvents.selectJob, function(ev, job){
|
||||
|
||||
selectJob(job);
|
||||
|
||||
});
|
||||
|
||||
var selectJob = function(job){
|
||||
|
||||
var jobKey = getJobMapKey(job);
|
||||
var jobEl = $('.' + jobKey);
|
||||
|
||||
clickJobCb({}, jobEl, job);
|
||||
scrollToElement(jobEl);
|
||||
|
||||
lastJobElSelected = jobEl;
|
||||
lastJobObjSelected = job;
|
||||
|
||||
};
|
||||
|
||||
var setSelectJobStyles = function(el){
|
||||
|
||||
if(!_.isEmpty(lastJobElSelected)){
|
||||
lastJobElSelected.removeClass(selectedBtnCls);
|
||||
|
@ -61,7 +127,7 @@ treeherder.directive('thCloneJobs', function(
|
|||
};
|
||||
|
||||
var clickJobCb = function(ev, el, job){
|
||||
selectJob(el);
|
||||
setSelectJobStyles(el);
|
||||
$rootScope.$broadcast(thEvents.jobClick, job);
|
||||
};
|
||||
|
||||
|
@ -82,7 +148,7 @@ treeherder.directive('thCloneJobs', function(
|
|||
}
|
||||
});
|
||||
} else {
|
||||
$log.warn("Job had no artifacts: " + job_uri);
|
||||
$log.warn("Job had no artifacts: " + job.resource_uri);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -101,12 +167,12 @@ treeherder.directive('thCloneJobs', function(
|
|||
|
||||
//Set the resultState
|
||||
resultState = job.result;
|
||||
if (job.state != "completed") {
|
||||
if (job.state !== "completed") {
|
||||
resultState = job.state;
|
||||
}
|
||||
resultState = resultState || 'unknown';
|
||||
|
||||
if(job.job_coalesced_to_guid != null){
|
||||
if(job.job_coalesced_to_guid !== null){
|
||||
// Don't count or render coalesced jobs
|
||||
continue;
|
||||
}
|
||||
|
@ -123,25 +189,31 @@ treeherder.directive('thCloneJobs', function(
|
|||
//Make sure that filtering doesn't effect the resultset counts
|
||||
//displayed
|
||||
if(thJobFilters.showJob(job, resultStatusFilters) === false){
|
||||
//Keep track of visibility with this property. This
|
||||
//way down stream job consumers don't need to repeatedly
|
||||
//call showJob
|
||||
job.visible = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
jobsShown++;
|
||||
|
||||
job.visible = true;
|
||||
|
||||
hText = getHoverText(job);
|
||||
key = getJobMapKey(job);
|
||||
|
||||
jobStatus = thResultStatusInfo(resultState);
|
||||
|
||||
jobStatus['key'] = key;
|
||||
if(parseInt(job.failure_classification_id) > 1){
|
||||
jobStatus['value'] = job.job_type_symbol + '*';
|
||||
jobStatus.key = key;
|
||||
if(parseInt(job.failure_classification_id, 10) > 1){
|
||||
jobStatus.value = job.job_type_symbol + '*';
|
||||
}else{
|
||||
jobStatus['value'] = job.job_type_symbol;
|
||||
jobStatus.value = job.job_type_symbol;
|
||||
}
|
||||
|
||||
jobStatus['title'] = hText;
|
||||
jobStatus['btnClass'] = jobStatus.btnClass;
|
||||
jobStatus.title = hText;
|
||||
jobStatus.btnClass = jobStatus.btnClass;
|
||||
|
||||
jobBtn = $( jobBtnInterpolator(jobStatus) );
|
||||
|
||||
|
@ -184,6 +256,7 @@ treeherder.directive('thCloneJobs', function(
|
|||
}
|
||||
|
||||
lastJobElSelected = el;
|
||||
lastJobObjSelected = job;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -203,14 +276,14 @@ treeherder.directive('thCloneJobs', function(
|
|||
|
||||
revision = resultset.revisions[i];
|
||||
|
||||
revision['urlBasePath'] = $rootScope.urlBasePath;
|
||||
revision['currentRepo'] = $rootScope.currentRepo;
|
||||
revision.urlBasePath = $rootScope.urlBasePath;
|
||||
revision.currentRepo = $rootScope.currentRepo;
|
||||
|
||||
userTokens = revision.author.split(/[<>]+/);
|
||||
if (userTokens.length > 1) {
|
||||
revision['email'] = userTokens[1];
|
||||
revision.email = userTokens[1];
|
||||
}
|
||||
revision['name'] = userTokens[0].trim();
|
||||
revision.name = userTokens[0].trim();
|
||||
|
||||
revisionHtml = revisionInterpolator(revision);
|
||||
ulEl.append(revisionHtml);
|
||||
|
@ -229,7 +302,7 @@ treeherder.directive('thCloneJobs', function(
|
|||
var rowEl = revisionsEl.parent();
|
||||
rowEl.css('display', 'block');
|
||||
|
||||
if(revElDisplayState != 'block'){
|
||||
if(revElDisplayState !== 'block'){
|
||||
|
||||
if(jobsElDisplayState === 'block'){
|
||||
toggleRevisionsSpanOnWithJobs(revisionsEl);
|
||||
|
@ -264,7 +337,7 @@ treeherder.directive('thCloneJobs', function(
|
|||
var rowEl = revisionsEl.parent();
|
||||
rowEl.css('display', 'block');
|
||||
|
||||
if(jobsElDisplayState != 'block'){
|
||||
if(jobsElDisplayState !== 'block'){
|
||||
|
||||
if(revElDisplayState === 'block'){
|
||||
toggleJobsSpanOnWithRevisions(jobsEl);
|
||||
|
@ -343,7 +416,7 @@ treeherder.directive('thCloneJobs', function(
|
|||
jgObj = jobGroups[i];
|
||||
|
||||
jobsShown = 0;
|
||||
if(jgObj.symbol != '?'){
|
||||
if(jgObj.symbol !== '?'){
|
||||
// Job group detected, add job group symbols
|
||||
jobGroup = $( jobGroupInterpolator(jobGroups[i]) );
|
||||
|
||||
|
@ -486,7 +559,7 @@ treeherder.directive('thCloneJobs', function(
|
|||
var jobCounts = thResultStatusObject.getResultStatusObject();
|
||||
|
||||
var statusKeys = _.keys(jobCounts);
|
||||
jobCounts['total'] = 0;
|
||||
jobCounts.total = 0;
|
||||
|
||||
resultsetId = resultSets[i].id;
|
||||
|
||||
|
@ -511,7 +584,7 @@ treeherder.directive('thCloneJobs', function(
|
|||
for(k=0; k<statusKeys.length; k++){
|
||||
jobStatus = statusKeys[k];
|
||||
jobCounts[jobStatus] += statusPerPlatform[jobStatus];
|
||||
jobCounts['total'] += statusPerPlatform[jobStatus];
|
||||
jobCounts.total += statusPerPlatform[jobStatus];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -524,7 +597,7 @@ treeherder.directive('thCloneJobs', function(
|
|||
|
||||
angular.forEach(platformData, function(value, platformId){
|
||||
|
||||
if(value.resultsetId != this.resultset.id){
|
||||
if(value.resultsetId !== this.resultset.id){
|
||||
//Confirm we are the correct result set
|
||||
return;
|
||||
}
|
||||
|
@ -585,13 +658,119 @@ treeherder.directive('thCloneJobs', function(
|
|||
}, this);
|
||||
};
|
||||
|
||||
var linker = function(scope, element, attrs){
|
||||
var getNextUnclassifiedFailure = function(currentJob){
|
||||
|
||||
//Remove any jquery on() bindings
|
||||
element.off();
|
||||
var resultsets = ThResultSetModel.getResultSetsArray($rootScope.repoName);
|
||||
|
||||
//Register events callback
|
||||
element.on('mousedown', _.bind(jobMouseDown, scope));
|
||||
var startWatch = false;
|
||||
if(_.isEmpty(currentJob)){
|
||||
startWatch = true;
|
||||
}
|
||||
|
||||
var platforms, groups, jobs, r;
|
||||
superloop:
|
||||
for(r = 0; r < resultsets.length; r++){
|
||||
|
||||
platforms = resultsets[r].platforms;
|
||||
var p;
|
||||
for(p = 0; p < platforms.length; p++){
|
||||
|
||||
groups = platforms[p].groups;
|
||||
var g;
|
||||
for(g = 0; g < groups.length; g++){
|
||||
|
||||
jobs = groups[g].jobs;
|
||||
var j;
|
||||
for(j = 0; j < jobs.length; j++){
|
||||
|
||||
if(currentJob.id === jobs[j].id){
|
||||
|
||||
//This is the current selection, get the next
|
||||
startWatch = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(startWatch){
|
||||
if( (jobs[j].visible === true) &&
|
||||
(classificationRequired[jobs[j].result] === 1) &&
|
||||
( (parseInt(jobs[j].failure_classification_id, 10) === 1) ||
|
||||
(jobs[j].failure_classification_id === null) )){
|
||||
|
||||
selectJob(jobs[j]);
|
||||
|
||||
//Next test failure found
|
||||
break superloop;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var getPreviousUnclassifiedFailure = function(currentJob){
|
||||
|
||||
var resultsets = ThResultSetModel.getResultSetsArray($rootScope.repoName);
|
||||
|
||||
var startWatch = false;
|
||||
if(_.isEmpty(currentJob)){
|
||||
startWatch = true;
|
||||
}
|
||||
|
||||
var platforms, groups, jobs, r;
|
||||
|
||||
superloop:
|
||||
for(r = resultsets.length - 1; r >= 0; r--){
|
||||
|
||||
platforms = resultsets[r].platforms;
|
||||
var p;
|
||||
for(p = platforms.length - 1; p >= 0; p--){
|
||||
|
||||
groups = platforms[p].groups;
|
||||
var g;
|
||||
for(g = groups.length - 1; g >= 0; g--){
|
||||
|
||||
jobs = groups[g].jobs;
|
||||
var j;
|
||||
for(j = jobs.length - 1; j >= 0; j--){
|
||||
|
||||
if(currentJob.id === jobs[j].id){
|
||||
|
||||
//This is the current selection, get the next
|
||||
startWatch = true;
|
||||
continue;
|
||||
}
|
||||
if(startWatch){
|
||||
if( (jobs[j].visible === true) &&
|
||||
(classificationRequired[jobs[j].result] === 1) &&
|
||||
( (parseInt(jobs[j].failure_classification_id, 10) === 1) ||
|
||||
(jobs[j].failure_classification_id === null) )){
|
||||
|
||||
selectJob(jobs[j]);
|
||||
|
||||
//Previous test failure found
|
||||
break superloop;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var scrollToElement = function(el){
|
||||
|
||||
if(el.offset() !== undefined){
|
||||
//Scroll to the job element
|
||||
$('html, body').animate({
|
||||
scrollTop: el.offset().top - 250
|
||||
}, 200);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
var registerCustomEventCallbacks = function(scope, element, attrs){
|
||||
|
||||
//Register rootScope custom event listeners that require
|
||||
//access to the anguler level resultset scope
|
||||
|
@ -669,6 +848,19 @@ treeherder.directive('thCloneJobs', function(
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
};
|
||||
|
||||
var linker = function(scope, element, attrs){
|
||||
|
||||
//Remove any jquery on() bindings
|
||||
element.off();
|
||||
|
||||
//Register events callback
|
||||
element.on('mousedown', _.bind(jobMouseDown, scope));
|
||||
|
||||
registerCustomEventCallbacks(scope, element, attrs);
|
||||
|
||||
//Clone the target html
|
||||
var resultsetAggregateId = thAggregateIds.getResultsetTableId(
|
||||
$rootScope.repoName, scope.resultset.id, scope.resultset.revision
|
||||
|
@ -730,557 +922,11 @@ treeherder.directive('thCloneJobs', function(
|
|||
}
|
||||
|
||||
element.append(targetEl);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
link:linker,
|
||||
replace:true
|
||||
}
|
||||
|
||||
});
|
||||
treeherder.directive('thGlobalTopNavPanel', function () {
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
templateUrl: 'partials/thGlobalTopNavPanel.html'
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive('thWatchedRepoPanel', function () {
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
templateUrl: 'partials/thWatchedRepoPanel.html'
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive('thStatusFilterPanel', function () {
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
templateUrl: 'partials/thStatusFilterPanel.html'
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive('thRepoPanel', function () {
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
templateUrl: 'partials/thRepoPanel.html'
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive('thSheriffPanel', function () {
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
templateUrl: 'partials/thSheriffPanel.html'
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive('thSettingsPanel', function () {
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
templateUrl: 'partials/thSettingsPanel.html'
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
treeherder.directive('thFilterCheckbox', function (thResultStatusInfo) {
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
link: function(scope, element, attrs) {
|
||||
scope.checkClass = thResultStatusInfo(scope.filterName).btnClass + "-count-classified";
|
||||
},
|
||||
templateUrl: 'partials/thFilterCheckbox.html'
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive('ngRightClick', function($parse) {
|
||||
return function(scope, element, attrs) {
|
||||
var fn = $parse(attrs.ngRightClick);
|
||||
element.bind('contextmenu', function(event) {
|
||||
scope.$apply(function() {
|
||||
event.preventDefault();
|
||||
fn(scope, {$event:event});
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive('thJobButton', function (thResultStatusInfo) {
|
||||
|
||||
var getHoverText = function(job) {
|
||||
var duration = Math.round((job.end_timestamp - job.submit_timestamp) / 60);
|
||||
var status = job.result;
|
||||
if (job.state != "completed") {
|
||||
status = job.state;
|
||||
}
|
||||
return job.job_type_name + " - " + status + " - " + duration + "mins";
|
||||
};
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
link: function(scope, element, attrs) {
|
||||
var unbindWatcher = scope.$watch("job", function(newValue) {
|
||||
var resultState = scope.job.result;
|
||||
if (scope.job.state != "completed") {
|
||||
resultState = scope.job.state;
|
||||
}
|
||||
scope.job.display = thResultStatusInfo(resultState);
|
||||
scope.hoverText = getHoverText(scope.job);
|
||||
|
||||
if (scope.job.state == "completed") {
|
||||
//Remove watchers when a job has a completed status
|
||||
unbindWatcher();
|
||||
}
|
||||
|
||||
}, true);
|
||||
},
|
||||
templateUrl: 'partials/thJobButton.html'
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive('thPinnedJob', function (thResultStatusInfo) {
|
||||
|
||||
var getHoverText = function(job) {
|
||||
var duration = Math.round((job.end_timestamp - job.start_timestamp) / 60);
|
||||
var status = job.result;
|
||||
if (job.state != "completed") {
|
||||
status = job.state;
|
||||
}
|
||||
return job.job_type_name + " - " + status + " - " + duration + "mins";
|
||||
};
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
link: function(scope, element, attrs) {
|
||||
var unbindWatcher = scope.$watch("job", function(newValue) {
|
||||
var resultState = scope.job.result;
|
||||
if (scope.job.state != "completed") {
|
||||
resultState = scope.job.state;
|
||||
}
|
||||
scope.job.display = thResultStatusInfo(resultState);
|
||||
scope.hoverText = getHoverText(scope.job);
|
||||
|
||||
if (scope.job.state == "completed") {
|
||||
//Remove watchers when a job has a completed status
|
||||
unbindWatcher();
|
||||
}
|
||||
|
||||
}, true);
|
||||
},
|
||||
templateUrl: 'partials/thPinnedJob.html'
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive('thRelatedBug', function () {
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
templateUrl: 'partials/thRelatedBug.html'
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive('thActionButton', function () {
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
templateUrl: 'partials/thActionButton.html'
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive('thResultCounts', function () {
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
templateUrl: 'partials/thResultCounts.html'
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive('thResultStatusCount', function () {
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
link: function(scope, element, attrs) {
|
||||
scope.resultCountText = scope.getCountText(scope.resultStatus);
|
||||
scope.resultStatusCountClassPrefix = scope.getCountClass(scope.resultStatus)
|
||||
|
||||
// @@@ this will change once we have classifying implemented
|
||||
scope.resultCount = scope.resultset.job_counts[scope.resultStatus];
|
||||
scope.unclassifiedResultCount = scope.resultCount;
|
||||
var getCountAlertClass = function() {
|
||||
if (scope.unclassifiedResultCount) {
|
||||
return scope.resultStatusCountClassPrefix + "-count-unclassified";
|
||||
} else {
|
||||
return scope.resultStatusCountClassPrefix + "-count-classified";
|
||||
}
|
||||
}
|
||||
scope.countAlertClass = getCountAlertClass();
|
||||
|
||||
scope.$watch("resultset.job_counts", function(newValue) {
|
||||
scope.resultCount = scope.resultset.job_counts[scope.resultStatus];
|
||||
scope.unclassifiedResultCount = scope.resultCount;
|
||||
scope.countAlertClass = getCountAlertClass();
|
||||
}, true);
|
||||
|
||||
},
|
||||
templateUrl: 'partials/thResultStatusCount.html'
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
treeherder.directive('thAuthor', function () {
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
link: function(scope, element, attrs) {
|
||||
var userTokens = attrs.author.split(/[<>]+/);
|
||||
var email = "";
|
||||
if (userTokens.length > 1) {
|
||||
email = userTokens[1];
|
||||
}
|
||||
scope.authorName = userTokens[0].trim();
|
||||
scope.authorEmail = email;
|
||||
},
|
||||
template: '<span title="open resultsets for {{authorName}}: {{authorEmail}}">' +
|
||||
'<a href="{{authorResultsetFilterUrl}}" ' +
|
||||
'target="_blank">{{authorName}}</a></span>'
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
// allow an input on a form to request focus when the value it sets in its
|
||||
// ``focus-me`` directive is true. You can set ``focus-me="focusInput"`` and
|
||||
// when ``$scope.focusInput`` changes to true, it will request focus on
|
||||
// the element with this directive.
|
||||
treeherder.directive('focusMe', function($timeout) {
|
||||
return {
|
||||
link: function(scope, element, attrs) {
|
||||
scope.$watch(attrs.focusMe, function(value) {
|
||||
if(value === true) {
|
||||
$timeout(function() {
|
||||
element[0].focus();
|
||||
scope[attrs.focusMe] = false;
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive('thStar', function ($parse, thClassificationTypes) {
|
||||
return {
|
||||
scope: {
|
||||
starId: "="
|
||||
},
|
||||
link: function(scope, element, attrs) {
|
||||
scope.$watch('starId', function(newVal) {
|
||||
if (newVal !== undefined) {
|
||||
scope.starType = thClassificationTypes[newVal];
|
||||
scope.badgeColorClass=scope.starType.star;
|
||||
scope.hoverText=scope.starType.name;
|
||||
}
|
||||
});
|
||||
},
|
||||
template: '<span class="label {{ badgeColorClass}}" ' +
|
||||
'title="{{ hoverText }}">' +
|
||||
'<i class="glyphicon glyphicon-star-empty"></i>' +
|
||||
'</span> {{ hoverText }}'
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive('thShowJobs', function ($parse, thResultStatusInfo) {
|
||||
return {
|
||||
link: function(scope, element, attrs) {
|
||||
scope.$watch('resultSeverity', function(newVal) {
|
||||
if (newVal) {
|
||||
var rsInfo = thResultStatusInfo(newVal)
|
||||
scope.resultsetStateBtn = rsInfo.btnClass;
|
||||
scope.icon = rsInfo.showButtonIcon;
|
||||
}
|
||||
});
|
||||
},
|
||||
template: '<a class="btn {{ resultsetStateBtn }} th-show-jobs-button pull-left" ' +
|
||||
'ng-click="isCollapsedResults = !isCollapsedResults">' +
|
||||
'<i class="{{ icon }}"></i> ' +
|
||||
'{{ \' jobs\' | showOrHide:isCollapsedResults }}</a>'
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive('thRevision', function($parse) {
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
link: function(scope, element, attrs) {
|
||||
scope.$watch('resultset.revisions', function(newVal) {
|
||||
if (newVal) {
|
||||
scope.revisionUrl = scope.currentRepo.url + "/rev/" + scope.revision.revision;
|
||||
}
|
||||
}, true);
|
||||
},
|
||||
templateUrl: 'partials/thRevision.html'
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
treeherder.directive('resizablePanel', function($document, $log) {
|
||||
return {
|
||||
restrict: "E",
|
||||
link: function(scope, element, attr) {
|
||||
var startY = 0
|
||||
var container = $(element.parent());
|
||||
|
||||
element.css({
|
||||
position: 'absolute',
|
||||
cursor:'row-resize',
|
||||
top:'-2px',
|
||||
width: '100%',
|
||||
height: '5px',
|
||||
'z-index': '100'
|
||||
|
||||
});
|
||||
|
||||
element.on('mousedown', function(event) {
|
||||
// Prevent default dragging of selected content
|
||||
event.preventDefault();
|
||||
startY = event.pageY;
|
||||
$document.on('mousemove', mousemove);
|
||||
$document.on('mouseup', mouseup);
|
||||
});
|
||||
|
||||
function mousemove(event) {
|
||||
var y = startY - event.pageY;
|
||||
startY = event.pageY;
|
||||
container.height(container.height() + y);
|
||||
|
||||
}
|
||||
|
||||
function mouseup() {
|
||||
$document.unbind('mousemove', mousemove);
|
||||
$document.unbind('mouseup', mouseup);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive('personaButtons', function($http, $q, $log, $rootScope, localStorageService,
|
||||
thServiceDomain, BrowserId, ThUserModel) {
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
link: function(scope, element, attrs) {
|
||||
scope.user = scope.user
|
||||
|| angular.fromJson(localStorageService.get('user'))
|
||||
|| {};
|
||||
// check if already know who the current user is
|
||||
// if the user.email value is null, it means that he's not logged in
|
||||
scope.user.email = scope.user.email || null;
|
||||
scope.user.loggedin = scope.user.email == null ? false : true;
|
||||
|
||||
scope.login = function(){
|
||||
/*
|
||||
* BrowserID.login returns a promise of the verification.
|
||||
* If successful, we will find the user email in the response
|
||||
*/
|
||||
BrowserId.login()
|
||||
.then(function(response){
|
||||
scope.user.loggedin = true;
|
||||
scope.user.email = response.data.email;
|
||||
// retrieve the current user's info from the api
|
||||
// including the exclusion profile
|
||||
ThUserModel.get().then(function(user){
|
||||
angular.extend(scope.user, user);
|
||||
localStorageService.add('user', angular.toJson(scope.user));
|
||||
}, null);
|
||||
},function(){
|
||||
// logout if the verification failed
|
||||
scope.logout();
|
||||
});
|
||||
};
|
||||
scope.logout = function(){
|
||||
BrowserId.logout().then(function(response){
|
||||
scope.user = {loggedin: false, email:null};
|
||||
localStorageService.remove('user');
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
navigator.id.watch({
|
||||
/*
|
||||
* loggedinUser is all that we know about the user before
|
||||
* the interaction with persona. This value could come from a cookie to persist the authentication
|
||||
* among page reloads. If the value is null, the user is considered logged out.
|
||||
*/
|
||||
|
||||
loggedInUser: scope.user.email,
|
||||
/*
|
||||
* We need a watch call to interact with persona.
|
||||
* onLogin is called when persona provides an assertion
|
||||
* This is the only way we can know the assertion from persona,
|
||||
* so we resolve BrowserId.requestDeferred with the assertion retrieved
|
||||
*/
|
||||
onlogin: function(assertion){
|
||||
if (BrowserId.requestDeferred) {
|
||||
BrowserId.requestDeferred.resolve(assertion);
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Resolve BrowserId.logoutDeferred once the user is logged out from persona
|
||||
*/
|
||||
onlogout: function(){
|
||||
if (BrowserId.logoutDeferred) {
|
||||
BrowserId.logoutDeferred.resolve();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
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){
|
||||
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){
|
||||
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",
|
||||
templateUrl: "partials/thNotificationsBox.html",
|
||||
link: function(scope, element, attr) {
|
||||
scope.notifier = thNotify
|
||||
scope.alert_class_prefix = "alert-"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
treeherder.directive('numbersOnly', function(){
|
||||
return {
|
||||
require: 'ngModel',
|
||||
link: function(scope, element, attrs, modelCtrl) {
|
||||
modelCtrl.$parsers.push(function (inputValue) {
|
||||
// this next is necessary for when using ng-required on your input.
|
||||
// In such cases, when a letter is typed first, this parser will be called
|
||||
// again, and the 2nd time, the value will be undefined
|
||||
if (inputValue == undefined) return ''
|
||||
var transformedInput = inputValue.replace(/[^0-9]/g, '');
|
||||
if (transformedInput!=inputValue) {
|
||||
modelCtrl.$setViewValue(transformedInput);
|
||||
modelCtrl.$render();
|
||||
}
|
||||
|
||||
return transformedInput;
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive('thPinboardPanel', function(){
|
||||
return {
|
||||
restrict: "E",
|
||||
templateUrl: "partials/thPinboardPanel.html"
|
||||
}
|
||||
});
|
||||
|
||||
treeherder.directive("thMultiSelect", function($log){
|
||||
return {
|
||||
restrict: "E",
|
||||
templateUrl: "partials/thMultiSelect.html",
|
||||
scope: {
|
||||
leftList: "=",
|
||||
rightList: "="
|
||||
},
|
||||
link: function(scope, element, attrs){
|
||||
|
||||
scope.leftSelected = [];
|
||||
scope.rightSelected = [];
|
||||
// move the elements selected from one list to the other
|
||||
var move_options = function(what, from, to){
|
||||
var found;
|
||||
for(var i=0;i<what.length; i++){
|
||||
found = from.indexOf(what[i]);
|
||||
if(found !== -1){
|
||||
to.push(from.splice(found, 1)[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
scope.move_left = function(){
|
||||
move_options(scope.rightSelected, scope.rightList, scope.leftList);
|
||||
};
|
||||
scope.move_right = function(){
|
||||
move_options(scope.leftSelected, scope.leftList, scope.rightList);
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
treeherder.directive("thTruncatedList", function($log){
|
||||
// transforms a list of elements in a shortened list
|
||||
// with a "more" link
|
||||
return {
|
||||
restrict: "E",
|
||||
scope: {
|
||||
// number of visible elementrs
|
||||
visible: "@",
|
||||
elem_list: "=elements"
|
||||
},
|
||||
link: function(scope, element, attrs){
|
||||
scope.visible = parseInt(scope.visible)
|
||||
if(typeof scope.visible !== 'number'
|
||||
|| scope.visible < 0
|
||||
|| isNaN(scope.visible)){
|
||||
throw new TypeError("The visible parameter must be a positive number")
|
||||
}
|
||||
// cloning the original list to avoid
|
||||
scope.$watch("elem_list", function(newValue, oldValue){
|
||||
if(newValue){
|
||||
var elem_list_clone = angular.copy(newValue);
|
||||
scope.visible = Math.min(scope.visible, elem_list_clone.length);
|
||||
var visible_content = elem_list_clone.splice(0, scope.visible);
|
||||
$(element[0]).empty();
|
||||
$(element[0]).append(visible_content.join(", "));
|
||||
if(elem_list_clone.length > 0){
|
||||
$(element[0]).append(
|
||||
$("<a></a>")
|
||||
.attr("title", elem_list_clone.join(", "))
|
||||
.text(" and "+ elem_list_clone.length+ " others")
|
||||
.tooltip()
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,135 @@
|
|||
'use strict';
|
||||
|
||||
treeherder.directive('ngRightClick', function($parse) {
|
||||
return function(scope, element, attrs) {
|
||||
var fn = $parse(attrs.ngRightClick);
|
||||
element.bind('contextmenu', function(event) {
|
||||
scope.$apply(function() {
|
||||
event.preventDefault();
|
||||
fn(scope, {$event:event});
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
// allow an input on a form to request focus when the value it sets in its
|
||||
// ``focus-me`` directive is true. You can set ``focus-me="focusInput"`` and
|
||||
// when ``$scope.focusInput`` changes to true, it will request focus on
|
||||
// the element with this directive.
|
||||
treeherder.directive('focusMe', function($timeout) {
|
||||
return {
|
||||
link: function(scope, element, attrs) {
|
||||
scope.$watch(attrs.focusMe, function(value) {
|
||||
if(value === true) {
|
||||
$timeout(function() {
|
||||
element[0].focus();
|
||||
scope[attrs.focusMe] = false;
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive('thNotificationBox', function(thNotify){
|
||||
return {
|
||||
restrict: "E",
|
||||
templateUrl: "partials/thNotificationsBox.html",
|
||||
link: function(scope, element, attr) {
|
||||
scope.notifier = thNotify
|
||||
scope.alert_class_prefix = "alert-"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
treeherder.directive('numbersOnly', function(){
|
||||
return {
|
||||
require: 'ngModel',
|
||||
link: function(scope, element, attrs, modelCtrl) {
|
||||
modelCtrl.$parsers.push(function (inputValue) {
|
||||
// this next is necessary for when using ng-required on your input.
|
||||
// In such cases, when a letter is typed first, this parser will be called
|
||||
// again, and the 2nd time, the value will be undefined
|
||||
if (inputValue == undefined) return ''
|
||||
var transformedInput = inputValue.replace(/[^0-9]/g, '');
|
||||
if (transformedInput!=inputValue) {
|
||||
modelCtrl.$setViewValue(transformedInput);
|
||||
modelCtrl.$render();
|
||||
}
|
||||
|
||||
return transformedInput;
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive("thMultiSelect", function($log){
|
||||
return {
|
||||
restrict: "E",
|
||||
templateUrl: "partials/thMultiSelect.html",
|
||||
scope: {
|
||||
leftList: "=",
|
||||
rightList: "="
|
||||
},
|
||||
link: function(scope, element, attrs){
|
||||
|
||||
scope.leftSelected = [];
|
||||
scope.rightSelected = [];
|
||||
// move the elements selected from one list to the other
|
||||
var move_options = function(what, from, to){
|
||||
var found;
|
||||
for(var i=0;i<what.length; i++){
|
||||
found = from.indexOf(what[i]);
|
||||
if(found !== -1){
|
||||
to.push(from.splice(found, 1)[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
scope.move_left = function(){
|
||||
move_options(scope.rightSelected, scope.rightList, scope.leftList);
|
||||
};
|
||||
scope.move_right = function(){
|
||||
move_options(scope.leftSelected, scope.leftList, scope.rightList);
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
treeherder.directive("thTruncatedList", function($log){
|
||||
// transforms a list of elements in a shortened list
|
||||
// with a "more" link
|
||||
return {
|
||||
restrict: "E",
|
||||
scope: {
|
||||
// number of visible elementrs
|
||||
visible: "@",
|
||||
elem_list: "=elements"
|
||||
},
|
||||
link: function(scope, element, attrs){
|
||||
scope.visible = parseInt(scope.visible)
|
||||
if(typeof scope.visible !== 'number'
|
||||
|| scope.visible < 0
|
||||
|| isNaN(scope.visible)){
|
||||
throw new TypeError("The visible parameter must be a positive number")
|
||||
}
|
||||
// cloning the original list to avoid
|
||||
scope.$watch("elem_list", function(newValue, oldValue){
|
||||
if(newValue){
|
||||
var elem_list_clone = angular.copy(newValue);
|
||||
scope.visible = Math.min(scope.visible, elem_list_clone.length);
|
||||
var visible_content = elem_list_clone.splice(0, scope.visible);
|
||||
$(element[0]).empty();
|
||||
$(element[0]).append(visible_content.join(", "));
|
||||
if(elem_list_clone.length > 0){
|
||||
$(element[0]).append(
|
||||
$("<a></a>")
|
||||
.attr("title", elem_list_clone.join(", "))
|
||||
.text(" and "+ elem_list_clone.length+ " others")
|
||||
.tooltip()
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,71 @@
|
|||
'use strict';
|
||||
|
||||
treeherder.directive('personaButtons', function($http, $q, ThLog, $rootScope, localStorageService, thServiceDomain, BrowserId) {
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
link: function(scope, element, attrs) {
|
||||
scope.user = scope.user || {};
|
||||
// check if already know who the current user is
|
||||
// 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.
|
||||
* If successful, we will find the user email in the response
|
||||
*/
|
||||
BrowserId.login()
|
||||
.then(function(response){
|
||||
scope.user.loggedin = true;
|
||||
scope.user.email = response.data.email;
|
||||
localStorageService.add('user.email', scope.user.email);
|
||||
},function(){
|
||||
// logout if the verification failed
|
||||
scope.logout();
|
||||
});
|
||||
};
|
||||
scope.logout = function(){
|
||||
BrowserId.logout().then(function(response){
|
||||
scope.user.loggedin = false;
|
||||
scope.user.email = null;
|
||||
localStorageService.remove('user.loggedin');
|
||||
localStorageService.remove('user.email');
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
navigator.id.watch({
|
||||
/*
|
||||
* loggedinUser is all that we know about the user before
|
||||
* the interaction with persona. This value could come from a cookie to persist the authentication
|
||||
* among page reloads. If the value is null, the user is considered logged out.
|
||||
*/
|
||||
|
||||
loggedInUser: scope.user.email,
|
||||
/*
|
||||
* We need a watch call to interact with persona.
|
||||
* onLogin is called when persona provides an assertion
|
||||
* This is the only way we can know the assertion from persona,
|
||||
* so we resolve BrowserId.requestDeferred with the assertion retrieved
|
||||
*/
|
||||
onlogin: function(assertion){
|
||||
if (BrowserId.requestDeferred) {
|
||||
BrowserId.requestDeferred.resolve(assertion);
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Resolve BrowserId.logoutDeferred once the user is logged out from persona
|
||||
*/
|
||||
onlogout: function(){
|
||||
if (BrowserId.logoutDeferred) {
|
||||
BrowserId.logoutDeferred.resolve();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
templateUrl: 'partials/persona_buttons.html'
|
||||
};
|
||||
});
|
|
@ -0,0 +1,83 @@
|
|||
'use strict';
|
||||
|
||||
treeherder.directive('thActionButton', function () {
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
templateUrl: 'partials/thActionButton.html'
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive('thResultCounts', function () {
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
templateUrl: 'partials/thResultCounts.html'
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive('thResultStatusCount', function () {
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
link: function(scope, element, attrs) {
|
||||
scope.resultCountText = scope.getCountText(scope.resultStatus);
|
||||
scope.resultStatusCountClassPrefix = scope.getCountClass(scope.resultStatus);
|
||||
|
||||
// @@@ this will change once we have classifying implemented
|
||||
scope.resultCount = scope.resultset.job_counts[scope.resultStatus];
|
||||
scope.unclassifiedResultCount = scope.resultCount;
|
||||
var getCountAlertClass = function() {
|
||||
if (scope.unclassifiedResultCount) {
|
||||
return scope.resultStatusCountClassPrefix + "-count-unclassified";
|
||||
} else {
|
||||
return scope.resultStatusCountClassPrefix + "-count-classified";
|
||||
}
|
||||
};
|
||||
scope.countAlertClass = getCountAlertClass();
|
||||
|
||||
scope.$watch("resultset.job_counts", function(newValue) {
|
||||
scope.resultCount = scope.resultset.job_counts[scope.resultStatus];
|
||||
scope.unclassifiedResultCount = scope.resultCount;
|
||||
scope.countAlertClass = getCountAlertClass();
|
||||
}, true);
|
||||
|
||||
},
|
||||
templateUrl: 'partials/thResultStatusCount.html'
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive('thRevision', function($parse) {
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
link: function(scope, element, attrs) {
|
||||
scope.$watch('resultset.revisions', function(newVal) {
|
||||
if (newVal) {
|
||||
scope.revisionUrl = scope.currentRepo.url + "/rev/" + scope.revision.revision;
|
||||
}
|
||||
}, true);
|
||||
},
|
||||
templateUrl: 'partials/thRevision.html'
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive('thAuthor', function () {
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
link: function(scope, element, attrs) {
|
||||
var userTokens = attrs.author.split(/[<>]+/);
|
||||
var email = "";
|
||||
if (userTokens.length > 1) {
|
||||
email = userTokens[1];
|
||||
}
|
||||
scope.authorName = userTokens[0].trim();
|
||||
scope.authorEmail = email;
|
||||
},
|
||||
template: '<span title="open resultsets for {{authorName}}: {{authorEmail}}">' +
|
||||
'<a href="{{authorResultsetFilterUrl}}" ' +
|
||||
'target="_blank">{{authorName}}</a></span>'
|
||||
};
|
||||
});
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
'use strict';
|
||||
|
||||
treeherder.directive('thFilterCheckbox', function (thResultStatusInfo) {
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
link: function(scope, element, attrs) {
|
||||
scope.checkClass = thResultStatusInfo(scope.filterName).btnClass + "-count-classified";
|
||||
},
|
||||
templateUrl: 'partials/thFilterCheckbox.html'
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive('thWatchedRepo', function (ThLog, ThRepositoryModel) {
|
||||
var $log = new ThLog("thWatchedRepo");
|
||||
|
||||
var statusInfo = {
|
||||
"open": {
|
||||
icon: "fa-circle-o",
|
||||
color: "treeOpen"
|
||||
},
|
||||
"approval required": {
|
||||
icon: "fa-lock",
|
||||
color: "treeApproval"
|
||||
},
|
||||
"closed": {
|
||||
icon: "fa-times-circle",
|
||||
color: "treeClosed"
|
||||
},
|
||||
"unavailable": {
|
||||
icon: "fa-chain-broken",
|
||||
color: "treeUnavailable"
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
link: function(scope, element, attrs) {
|
||||
|
||||
scope.$watch('repoData.treeStatus', function(newVal) {
|
||||
if (newVal) {
|
||||
$log.debug("updated treeStatus", newVal.status);
|
||||
scope.statusIcon = statusInfo[newVal.status].icon;
|
||||
scope.statusColor = statusInfo[newVal.status].color;
|
||||
scope.titleText = newVal.status;
|
||||
if (newVal.message_of_the_day) {
|
||||
scope.titleText = scope.titleText + ' - ' + newVal.message_of_the_day;
|
||||
}
|
||||
}
|
||||
}, true);
|
||||
|
||||
},
|
||||
templateUrl: 'partials/thWatchedRepo.html'
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.directive('thRepoDropDown', function (ThLog, ThRepositoryModel) {
|
||||
var $log = new ThLog("thRepoDropDown");
|
||||
|
||||
return {
|
||||
restrict: "E",
|
||||
replace: true,
|
||||
link: function(scope, element, attrs) {
|
||||
|
||||
scope.name = attrs.name;
|
||||
var repo_obj = ThRepositoryModel.getRepo(attrs.name);
|
||||
scope.pushlog = repo_obj.url +"/pushloghtml";
|
||||
|
||||
scope.$watch('repoData.treeStatus', function(newVal) {
|
||||
if (newVal) {
|
||||
$log.debug("updated treeStatus", repo_obj, newVal);
|
||||
scope.reason = newVal.reason;
|
||||
scope.message_of_the_day = newVal.message_of_the_day;
|
||||
}
|
||||
}, true);
|
||||
|
||||
},
|
||||
templateUrl: 'partials/thRepoDropDown.html'
|
||||
};
|
||||
});
|
|
@ -24,4 +24,12 @@ treeherder.filter('platformName', function() {
|
|||
// if it's not found in Config.js, then return it unchanged.
|
||||
return name;
|
||||
};
|
||||
})
|
||||
})
|
||||
|
||||
treeherder.filter('stripHtml', function() {
|
||||
return function(input) {
|
||||
var str = input || '';
|
||||
return str.replace(/<\/?[^>]+>/gi, '');
|
||||
};
|
||||
})
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ treeherder.factory('ThBugJobMapModel', function($http, thUrl) {
|
|||
// 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 $http.delete(ThBugJobMapModel.get_uri()+pk+"/");
|
||||
};
|
||||
|
||||
return ThBugJobMapModel;
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
treeherder.factory('ThJobClassificationModel', function($http, $log, thUrl) {
|
||||
treeherder.factory('ThJobClassificationModel', function($http, ThLog, thUrl) {
|
||||
// ThJobClassificationModel is the js counterpart of note
|
||||
|
||||
var ThJobClassificationModel = function(data) {
|
||||
|
@ -39,7 +39,7 @@ treeherder.factory('ThJobClassificationModel', function($http, $log, thUrl) {
|
|||
|
||||
// an instance method to delete a ThJobClassificationModel object
|
||||
ThJobClassificationModel.prototype.delete = function(){
|
||||
return $http.delete(ThJobClassificationModel.get_uri()+this.id);
|
||||
return $http.delete(ThJobClassificationModel.get_uri()+this.id+"/");
|
||||
};
|
||||
|
||||
return ThJobClassificationModel;
|
|
@ -1,7 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
treeherder.factory('ThJobModel', ['$http', '$log', 'thUrl', function($http, $log, thUrl) {
|
||||
// ThJobArtifactModel is the js counterpart of job_artifact
|
||||
treeherder.factory('ThJobModel', function($http, ThLog, thUrl) {
|
||||
// ThJobModel is the js counterpart of job
|
||||
|
||||
var ThJobModel = function(data) {
|
||||
// creates a new instance of ThJobArtifactModel
|
||||
|
@ -26,10 +26,10 @@ treeherder.factory('ThJobModel', ['$http', '$log', 'thUrl', function($http, $log
|
|||
|
||||
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 $http.get(ThJobModel.get_uri()+pk+"/").then(function(response) {
|
||||
return new ThJobModel(response.data);
|
||||
});
|
||||
};
|
||||
|
||||
return ThJobModel;
|
||||
}]);
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
treeherder.factory('ThJobArtifactModel', ['$http', '$log', 'thUrl', function($http, $log, thUrl) {
|
||||
treeherder.factory('ThJobArtifactModel', function($http, ThLog, thUrl) {
|
||||
// ThJobArtifactModel is the js counterpart of job_artifact
|
||||
|
||||
var ThJobArtifactModel = function(data) {
|
||||
|
@ -32,4 +32,4 @@ treeherder.factory('ThJobArtifactModel', ['$http', '$log', 'thUrl', function($ht
|
|||
};
|
||||
|
||||
return ThJobArtifactModel;
|
||||
}]);
|
||||
});
|
|
@ -0,0 +1,185 @@
|
|||
'use strict';
|
||||
|
||||
treeherder.factory('ThRepositoryModel',
|
||||
function($http, thUrl, $rootScope, ThLog, localStorageService,
|
||||
thSocket, treeStatus) {
|
||||
var $log = new ThLog("ThRepositoryModel");
|
||||
|
||||
var new_failures = {};
|
||||
var repos = {};
|
||||
|
||||
thSocket.on('job_failure', function(msg){
|
||||
if (! new_failures.hasOwnProperty(msg.branch)){
|
||||
new_failures[msg.branch] = [];
|
||||
}
|
||||
new_failures[msg.branch].push(msg.id);
|
||||
$log.debug("new failure on branch ", msg.branch);
|
||||
});
|
||||
|
||||
// get the repositories (aka trees)
|
||||
// sample: 'resources/menu.json'
|
||||
var getByName = function(name) {
|
||||
if ($rootScope.repos !== undefined) {
|
||||
for (var i = 0; i < $rootScope.repos.length; i++) {
|
||||
var repo = $rootScope.repos[i];
|
||||
if (repo.name === name) {
|
||||
return repo;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$log.warn("Repos list has not been loaded.");
|
||||
}
|
||||
$log.warn("'" + name + "' not found in repos list.");
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
// get by category
|
||||
var getByGroup = function() {
|
||||
var groupedRepos = {};
|
||||
var group = function(repo) {
|
||||
if (!_.has(groupedRepos, repo.repository_group.name)) {
|
||||
groupedRepos[repo.repository_group.name] = [];
|
||||
}
|
||||
groupedRepos[repo.repository_group.name].push(repo);
|
||||
};
|
||||
|
||||
if (!groupedRepos.length) {
|
||||
_.each($rootScope.repos, group);
|
||||
}
|
||||
return groupedRepos;
|
||||
};
|
||||
|
||||
var addAsUnwatched = function(repo) {
|
||||
repos[repo.name] = {
|
||||
isWatched: false,
|
||||
treeStatus: null,
|
||||
unclassifiedFailureCount: 0
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* We want to add this repo as watched, but we also
|
||||
* want to get the treestatus for it
|
||||
*/
|
||||
var addAsWatched = function(data, repoName) {
|
||||
if (data.isWatched) {
|
||||
repos[repoName] = {
|
||||
isWatched: true,
|
||||
treeStatus: null,
|
||||
unclassifiedFailureCount: 0
|
||||
};
|
||||
updateTreeStatus(repoName);
|
||||
$log.debug("watchedRepo", repoName, repos[repoName]);
|
||||
}
|
||||
};
|
||||
|
||||
var unwatch = function(name) {
|
||||
if (!_.contains(repos, name)) {
|
||||
repos[name].isWatched = false;
|
||||
}
|
||||
watchedReposUpdated();
|
||||
};
|
||||
|
||||
var get_uri = function(){
|
||||
return thUrl.getRootUrl("/repository/");
|
||||
}
|
||||
|
||||
var get_list = function(){
|
||||
return $http.get(api.get_uri(), {cache: true})
|
||||
}
|
||||
|
||||
var load = function(name) {
|
||||
|
||||
var storedWatchedRepos = localStorageService.get("watchedRepos");
|
||||
|
||||
return get_list().
|
||||
success(function(data) {
|
||||
$rootScope.repos = data;
|
||||
$rootScope.groupedRepos = getByGroup();
|
||||
|
||||
_.each(data, addAsUnwatched);
|
||||
if (storedWatchedRepos) {
|
||||
_.each(storedWatchedRepos, addAsWatched);
|
||||
}
|
||||
localStorageService.add("watchedRepos", repos);
|
||||
|
||||
if (name) {
|
||||
$rootScope.currentRepo = getByName(name);
|
||||
addAsWatched({isWatched: true}, name);
|
||||
}
|
||||
watchedReposUpdated();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
var getCurrent = function() {
|
||||
return $rootScope.currentRepo;
|
||||
};
|
||||
|
||||
var setCurrent = function(name) {
|
||||
$rootScope.currentRepo = getByName(name);
|
||||
$log.debug("repoModel", "setCurrent", name, "watchedRepos", repos);
|
||||
};
|
||||
|
||||
var repo_has_failures = function(repo_name){
|
||||
return ($rootScope.new_failures.hasOwnProperty(repo_name) &&
|
||||
$rootScope.new_failures[repo_name].length > 0);
|
||||
};
|
||||
|
||||
var watchedReposUpdated = function(repoName) {
|
||||
localStorageService.add("watchedRepos", repos);
|
||||
if (repoName) {
|
||||
updateTreeStatus(repoName);
|
||||
} else {
|
||||
updateAllWatchedRepoTreeStatus();
|
||||
}
|
||||
};
|
||||
|
||||
var updateTreeStatus = function(repoName) {
|
||||
if (repos[repoName].isWatched) {
|
||||
$log.debug("updateTreeStatus", "updating", repoName);
|
||||
treeStatus.get(repoName).then(function(data) {
|
||||
repos[repoName].treeStatus = data.data;
|
||||
}, function(data) {
|
||||
repos[repoName].treeStatus = {
|
||||
status: "unavailable",
|
||||
message_of_the_day: repoName +
|
||||
' is not supported in <a href="https://treestatus.mozilla.org">treestatus.mozilla.org</a>'
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var updateAllWatchedRepoTreeStatus = function() {
|
||||
_.each(_.keys(repos), updateTreeStatus);
|
||||
};
|
||||
|
||||
|
||||
return {
|
||||
// load the list of repos into $rootScope, and set the current repo.
|
||||
load: load,
|
||||
|
||||
// return the currently selected repo
|
||||
getCurrent: getCurrent,
|
||||
|
||||
// set the current repo to one in the repos list
|
||||
setCurrent: setCurrent,
|
||||
|
||||
// get a repo object without setting anything
|
||||
getRepo: getByName,
|
||||
|
||||
getByGroup: getByGroup,
|
||||
|
||||
watchedRepos: repos,
|
||||
|
||||
watchedReposUpdated: watchedReposUpdated,
|
||||
|
||||
unwatch: unwatch,
|
||||
|
||||
updateAllWatchedRepoTreeStatus: updateAllWatchedRepoTreeStatus,
|
||||
|
||||
repo_has_failures: repo_has_failures
|
||||
|
||||
};
|
||||
});
|
|
@ -1,10 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
treeherder.factory('ThResultSetModel',
|
||||
['$log', '$rootScope', 'thResultSets', 'thSocket',
|
||||
'ThJobModel', 'thEvents', 'thAggregateIds',
|
||||
function($log, $rootScope, thResultSets, thSocket,
|
||||
ThJobModel, thEvents, thAggregateIds) {
|
||||
['$rootScope', 'thResultSets', 'thSocket',
|
||||
'ThJobModel', 'thEvents', 'thAggregateIds', 'ThLog',
|
||||
function($rootScope, thResultSets, thSocket,
|
||||
ThJobModel, thEvents, thAggregateIds, ThLog) {
|
||||
|
||||
var $log = new ThLog("ThResultSetModel");
|
||||
|
||||
/******
|
||||
* Handle updating the resultset datamodel based on a queue of jobs
|
||||
|
@ -112,7 +114,7 @@ treeherder.factory('ThResultSetModel',
|
|||
|
||||
var getPlatformKey = function(name, option){
|
||||
var key = name;
|
||||
if(option != undefined){
|
||||
if(option !== undefined){
|
||||
key += option;
|
||||
}
|
||||
return key;
|
||||
|
@ -144,6 +146,13 @@ treeherder.factory('ThResultSetModel',
|
|||
repositories[repoName].rsMapOldestTimestamp = rs_obj.push_timestamp;
|
||||
}
|
||||
|
||||
//Keep track of the last resultset id for paging
|
||||
var resultsetId = parseInt(rs_obj.id, 10);
|
||||
if( (resultsetId < repositories[repoName].rsOffsetId) ||
|
||||
(repositories[repoName].rsOffsetId === 0) ){
|
||||
repositories[repoName].rsOffsetId = resultsetId;
|
||||
}
|
||||
|
||||
// platforms
|
||||
for (var pl_i = 0; pl_i < rs_obj.platforms.length; pl_i++) {
|
||||
var pl_obj = rs_obj.platforms[pl_i];
|
||||
|
@ -191,10 +200,9 @@ treeherder.factory('ThResultSetModel',
|
|||
|
||||
repositories[repoName].resultSets.sort(rsCompare);
|
||||
|
||||
$log.debug("oldest job: " + repositories[repoName].jobMapOldestId);
|
||||
$log.debug("oldest result set: " + repositories[repoName].rsMapOldestTimestamp);
|
||||
$log.debug("done mapping:");
|
||||
$log.debug(repositories[repoName].rsMap);
|
||||
$log.debug("oldest job: ", repositories[repoName].jobMapOldestId);
|
||||
$log.debug("oldest result set: ", repositories[repoName].rsMapOldestTimestamp);
|
||||
$log.debug("done mapping:", repositories[repoName].rsMap);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -226,7 +234,7 @@ treeherder.factory('ThResultSetModel',
|
|||
|
||||
var pl_obj = {
|
||||
name: newJob.platform,
|
||||
option: newJob.platform_opt,
|
||||
option: newJob.platform_option,
|
||||
groups: []
|
||||
};
|
||||
|
||||
|
@ -308,28 +316,31 @@ treeherder.factory('ThResultSetModel',
|
|||
}
|
||||
|
||||
if (jobFetchList.length > 0) {
|
||||
$log.debug("processing jobFetchList");
|
||||
$log.debug(jobFetchList);
|
||||
$log.debug("processing jobFetchList", jobFetchList);
|
||||
|
||||
// make an ajax call to get the job details
|
||||
|
||||
ThJobModel.get_list({
|
||||
id__in: jobFetchList.join()
|
||||
}).then(
|
||||
_.bind(updateJobs, $rootScope, repoName),
|
||||
function(data) {
|
||||
$log.error("Error fetching jobUpdateQueue: " + data);
|
||||
});
|
||||
fetchJobs(repoName, jobFetchList);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Fetch the job objects for the ids in ``jobFetchList`` and update them
|
||||
* in the data model.
|
||||
*/
|
||||
var fetchJobs = function(repoName, jobFetchList) {
|
||||
ThJobModel.get_list({
|
||||
id__in: jobFetchList.join()
|
||||
}).then(
|
||||
_.bind(updateJobs, $rootScope, repoName),
|
||||
function(data) {
|
||||
$log.error("Error fetching jobs: " + data);
|
||||
});
|
||||
};
|
||||
var aggregateJobPlatform = function(repoName, job, platformData){
|
||||
|
||||
var resultsetId, platformName, platformOption, platformAggregateId,
|
||||
platformKey, jobUpdated, resultsetAggregateId, revision,
|
||||
jobGroups;
|
||||
|
||||
console.log('aggregating job platform');
|
||||
console.log(job);
|
||||
jobUpdated = updateJob(repoName, job);
|
||||
|
||||
//the job was not updated or added to the model, don't include it
|
||||
|
@ -340,7 +351,7 @@ treeherder.factory('ThResultSetModel',
|
|||
|
||||
resultsetId = job.result_set_id;
|
||||
platformName = job.platform;
|
||||
platformOption = job.platform_opt;
|
||||
platformOption = job.platform_option;
|
||||
|
||||
if(_.isEmpty(repositories[repoName].rsMap[ resultsetId ])){
|
||||
//We don't have this resultset
|
||||
|
@ -351,7 +362,7 @@ treeherder.factory('ThResultSetModel',
|
|||
repoName,
|
||||
job.result_set_id,
|
||||
job.platform,
|
||||
job.platform_opt
|
||||
job.platform_option
|
||||
);
|
||||
|
||||
if(!platformData[platformAggregateId]){
|
||||
|
@ -366,6 +377,7 @@ treeherder.factory('ThResultSetModel',
|
|||
|
||||
platformKey = getPlatformKey(platformName, platformOption);
|
||||
|
||||
$log.debug("aggregateJobPlatform", repoName, resultsetId, platformKey, repositories);
|
||||
jobGroups = repositories[repoName].rsMap[resultsetId].platforms[platformKey].pl_obj.groups;
|
||||
platformData[platformAggregateId] = {
|
||||
platformName:platformName,
|
||||
|
@ -389,7 +401,7 @@ treeherder.factory('ThResultSetModel',
|
|||
*/
|
||||
var updateJobs = function(repoName, jobList) {
|
||||
|
||||
$log.debug("number of jobs returned for add/update: " + jobList.length);
|
||||
$log.debug("number of jobs returned for add/update: ", jobList.length);
|
||||
|
||||
var platformData = {};
|
||||
|
||||
|
@ -450,11 +462,11 @@ treeherder.factory('ThResultSetModel',
|
|||
}
|
||||
|
||||
if (loadedJob) {
|
||||
$log.debug("updating existing job");
|
||||
$log.debug("updating existing job", loadedJob, newJob);
|
||||
_.extend(loadedJob, newJob);
|
||||
} else {
|
||||
// this job is not yet in the model or the map. add it to both
|
||||
$log.debug("adding new job");
|
||||
$log.debug("adding new job", newJob);
|
||||
|
||||
var grpMapElement = getOrCreateGroup(repoName, newJob);
|
||||
|
||||
|
@ -486,7 +498,7 @@ treeherder.factory('ThResultSetModel',
|
|||
var added = [];
|
||||
for (var i = data.length - 1; i > -1; i--) {
|
||||
if (data[i].push_timestamp > repositories[repoName].rsMapOldestTimestamp) {
|
||||
$log.debug("prepending resultset: " + data[i].id);
|
||||
$log.debug("prepending resultset: ", data[i].id);
|
||||
repositories[repoName].resultSets.push(data[i]);
|
||||
added.push(data[i]);
|
||||
} else {
|
||||
|
@ -502,7 +514,7 @@ treeherder.factory('ThResultSetModel',
|
|||
var appendResultSets = function(repoName, data) {
|
||||
|
||||
if(data.length > 0){
|
||||
repositories[repoName].rsOffsetId = data[ data.length - 1 ].id;
|
||||
|
||||
|
||||
Array.prototype.push.apply(
|
||||
repositories[repoName].resultSets, data
|
||||
|
@ -556,7 +568,7 @@ treeherder.factory('ThResultSetModel',
|
|||
*/
|
||||
if(resultsetList.length > 0){
|
||||
repositories[repoName].loadingStatus.prepending = true;
|
||||
thResultSets.getResultSets(0, resultsetlist.length, resultsetlist).
|
||||
thResultSets.getResultSets(0, resultsetList.length, resultsetList).
|
||||
success( _.bind(prependResultSets, $rootScope, repoName) );
|
||||
}
|
||||
};
|
||||
|
@ -596,6 +608,8 @@ treeherder.factory('ThResultSetModel',
|
|||
|
||||
fetchResultSets: fetchResultSets,
|
||||
|
||||
fetchJobs: fetchJobs,
|
||||
|
||||
aggregateJobPlatform: aggregateJobPlatform
|
||||
|
||||
};
|
|
@ -10,42 +10,12 @@ treeherder.provider('thServiceDomain', function() {
|
|||
};
|
||||
});
|
||||
|
||||
treeherder.provider('thClassificationTypes', function() {
|
||||
this.$get = function() {
|
||||
return {
|
||||
1: {
|
||||
name: "not classified",
|
||||
star: ""
|
||||
},
|
||||
2: {
|
||||
name: "expected fail",
|
||||
star: "label-info"
|
||||
},
|
||||
3: {
|
||||
name: "fixed by backout",
|
||||
star: "label-success"
|
||||
},
|
||||
4: {
|
||||
name: "intermittent",
|
||||
star: "label-warning"
|
||||
},
|
||||
5: {
|
||||
name: "infra",
|
||||
star: "label-default"
|
||||
},
|
||||
6: {
|
||||
name: "intermittent needs filing",
|
||||
star: "label-danger"
|
||||
}
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.provider('thResultStatusList', function() {
|
||||
this.$get = function() {
|
||||
return ['success', 'testfailed', 'busted', 'exception', 'retry', 'running', 'pending'];
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.provider('thResultStatus', function() {
|
||||
this.$get = function() {
|
||||
return function(job) {
|
||||
|
@ -57,6 +27,7 @@ treeherder.provider('thResultStatus', function() {
|
|||
};
|
||||
};
|
||||
});
|
||||
|
||||
treeherder.provider('thResultStatusObject', function() {
|
||||
var getResultStatusObject = function(){
|
||||
return {
|
||||
|
@ -203,6 +174,9 @@ treeherder.provider('thEvents', function() {
|
|||
// fired (surprisingly) when a job is clicked
|
||||
jobClick: "job-click-EVT",
|
||||
|
||||
// fired when the job details are loaded
|
||||
jobDetailLoaded: "job-detail-loaded-EVT",
|
||||
|
||||
// fired when a job is shift-clicked
|
||||
jobPin: "job-pin-EVT",
|
||||
|
||||
|
@ -230,9 +204,21 @@ treeherder.provider('thEvents', function() {
|
|||
|
||||
toggleJobs: "toggle-jobs-EVT",
|
||||
|
||||
toggleUnclassifiedFailures: "toggle-unclassified-failures-EVT",
|
||||
|
||||
selectNextUnclassifiedFailure: "next-unclassified-failure-EVT",
|
||||
|
||||
selectPreviousUnclassifiedFailure: "previous-unclassified-failure-EVT",
|
||||
|
||||
searchPage: "search-page-EVT",
|
||||
|
||||
repoChanged: "repo-changed-EVT"
|
||||
repoChanged: "repo-changed-EVT",
|
||||
|
||||
// throwing this event will filter jobs to only show failures
|
||||
// that have no classification.
|
||||
showUnclassifiedFailures: "show-unclassified-failures-EVT",
|
||||
|
||||
selectJob: "select-job-EVT"
|
||||
};
|
||||
};
|
||||
});
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
'use strict';
|
||||
|
||||
treeherder.factory('thClassificationTypes', function($http, thUrl) {
|
||||
|
||||
var classifications = {};
|
||||
|
||||
var classificationColors = {
|
||||
1: "", // not classified
|
||||
2: "label-info", // expected fail",
|
||||
3: "label-success", // fixed by backout",
|
||||
4: "label-warning", // intermittent",
|
||||
5: "label-default", // infra",
|
||||
6: "label-danger" // intermittent needs filing",
|
||||
};
|
||||
|
||||
var addClassification = function(cl) {
|
||||
classifications[cl.id] = {
|
||||
name: cl.name,
|
||||
star: classificationColors[cl.id]
|
||||
};
|
||||
};
|
||||
|
||||
var load = function() {
|
||||
return $http.get(thUrl.getRootUrl("/failureclassification/")).
|
||||
success(function(data) {
|
||||
_.forEach(data, addClassification);
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
classifications: classifications,
|
||||
load: load
|
||||
};
|
||||
});
|
||||
|
|
@ -21,7 +21,11 @@
|
|||
* Each field is AND'ed so that, if a field exists in ``filters`` then the job
|
||||
* must match at least one value in every field.
|
||||
*/
|
||||
treeherder.factory('thJobFilters', function(thResultStatusList, $log, $rootScope) {
|
||||
treeherder.factory('thJobFilters',
|
||||
function(thResultStatusList, ThLog, $rootScope,
|
||||
ThResultSetModel, thPinboard, thNotify) {
|
||||
|
||||
var $log = new ThLog("thJobFilters");
|
||||
|
||||
/**
|
||||
* If a custom resultStatusList is passed in (like for individual
|
||||
|
@ -32,19 +36,29 @@ treeherder.factory('thJobFilters', function(thResultStatusList, $log, $rootScope
|
|||
* means it must have a value set, ``false`` means it must be null.
|
||||
*/
|
||||
var checkFilter = function(field, job, resultStatusList) {
|
||||
// resultStatus is a special case that spans two job fields
|
||||
$log.debug("checkFilter", field, job, resultStatusList);
|
||||
if (field === api.resultStatus) {
|
||||
// resultStatus is a special case that spans two job fields
|
||||
var filterList = resultStatusList || filters[field].values;
|
||||
return _.contains(filterList, job.result) ||
|
||||
_.contains(filterList, job.state);
|
||||
} else if (field === api.failure_classification_id) {
|
||||
// fci is a special case, too. Where 1 is "not classified"
|
||||
var fci_filters = filters[field].values;
|
||||
if (_.contains(fci_filters, false) && (job.failure_classification_id === 1 ||
|
||||
job.failure_classification_id === null)) {
|
||||
return true;
|
||||
}
|
||||
return _.contains(fci_filters, true) && job.failure_classification_id > 1;
|
||||
} else {
|
||||
var jobFieldValue = getJobFieldValue(job, field);
|
||||
if (_.isUndefined(jobFieldValue)) {
|
||||
//$log.warn("job object has no field of '" + field + "'. Skipping filtration.");
|
||||
// if a filter is added somehow, but the job object doesn't
|
||||
// have that field, then don't filter. Consider it a pass.
|
||||
return true;
|
||||
}
|
||||
|
||||
$log.debug(field + ": " + JSON.stringify(job));
|
||||
$log.debug("jobField filter", field, job);
|
||||
switch (filters[field].matchType) {
|
||||
case api.matchType.isnull:
|
||||
jobFieldValue = !_.isNull(jobFieldValue);
|
||||
|
@ -117,7 +131,7 @@ treeherder.factory('thJobFilters', function(thResultStatusList, $log, $rootScope
|
|||
value = value.toLowerCase();
|
||||
}
|
||||
if (filters.hasOwnProperty(field)) {
|
||||
if (!_.contains(filters[field], value)) {
|
||||
if (!_.contains(filters[field].values, value)) {
|
||||
filters[field].values.push(value);
|
||||
filters[field].matchType = matchType;
|
||||
}
|
||||
|
@ -128,8 +142,11 @@ treeherder.factory('thJobFilters', function(thResultStatusList, $log, $rootScope
|
|||
removeWhenEmpty: true
|
||||
};
|
||||
}
|
||||
$log.debug("adding " + field + ": " + value);
|
||||
$log.debug(filters);
|
||||
|
||||
filterKeys = _.keys(filters);
|
||||
|
||||
$log.debug("adding ", field, ": ", value);
|
||||
$log.debug("filters", filters);
|
||||
},
|
||||
removeFilter: function(field, value) {
|
||||
if (filters.hasOwnProperty(field)) {
|
||||
|
@ -139,7 +156,7 @@ treeherder.factory('thJobFilters', function(thResultStatusList, $log, $rootScope
|
|||
}
|
||||
var idx = filters[field].values.indexOf(value);
|
||||
if(idx > -1) {
|
||||
$log.debug("removing " + value);
|
||||
$log.debug("removing ", value);
|
||||
filters[field].values.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
|
@ -149,7 +166,9 @@ treeherder.factory('thJobFilters', function(thResultStatusList, $log, $rootScope
|
|||
if (filters[field].removeWhenEmpty && filters[field].values.length === 0) {
|
||||
delete filters[field];
|
||||
}
|
||||
$log.debug(filters);
|
||||
|
||||
filterKeys = _.keys(filters);
|
||||
$log.debug("filters", filters);
|
||||
},
|
||||
/**
|
||||
* used mostly for resultStatus doing group toggles
|
||||
|
@ -159,7 +178,7 @@ treeherder.factory('thJobFilters', function(thResultStatusList, $log, $rootScope
|
|||
* @param add - true if adding, false if removing
|
||||
*/
|
||||
toggleFilters: function(field, values, add) {
|
||||
$log.debug("toggling: " + add);
|
||||
$log.debug("toggling: ", add);
|
||||
var action = add? api.addFilter: api.removeFilter;
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
action(field, values[i]);
|
||||
|
@ -182,7 +201,7 @@ treeherder.factory('thJobFilters', function(thResultStatusList, $log, $rootScope
|
|||
return false;
|
||||
}
|
||||
}
|
||||
if($rootScope.searchQuery != ""){
|
||||
if(typeof $rootScope.searchQuery === 'string'){
|
||||
//Confirm job matches search query
|
||||
if(job.searchableStr.toLowerCase().indexOf(
|
||||
$rootScope.searchQuery.toLowerCase()
|
||||
|
@ -206,6 +225,31 @@ treeherder.factory('thJobFilters', function(thResultStatusList, $log, $rootScope
|
|||
getFilters: function() {
|
||||
return filters;
|
||||
},
|
||||
/**
|
||||
* Pin all jobs that pass the GLOBAL filters. Ignores toggling at
|
||||
* the result set level.
|
||||
*/
|
||||
pinAllShownJobs: function() {
|
||||
var jobs = ThResultSetModel.getJobMap($rootScope.repoName);
|
||||
var jobsToPin = [];
|
||||
|
||||
var queuePinIfShown = function(jMap) {
|
||||
if (api.showJob(jMap.job_obj)) {
|
||||
jobsToPin.push(jMap.job_obj);
|
||||
}
|
||||
};
|
||||
_.forEach(jobs, queuePinIfShown);
|
||||
|
||||
if (_.size(jobsToPin) > thPinboard.spaceRemaining()) {
|
||||
jobsToPin = jobsToPin.splice(0, thPinboard.spaceRemaining());
|
||||
thNotify.send("Pinboard max size exceeded. Pinning only the first " + thPinboard.spaceRemaining(),
|
||||
"danger",
|
||||
true);
|
||||
}
|
||||
|
||||
$rootScope.selectedJob = jobsToPin[0];
|
||||
_.forEach(jobsToPin, thPinboard.pinJob);
|
||||
},
|
||||
|
||||
// CONSTANTS
|
||||
failure_classification_id: "failure_classification_id",
|
||||
|
@ -213,7 +257,8 @@ treeherder.factory('thJobFilters', function(thResultStatusList, $log, $rootScope
|
|||
matchType: {
|
||||
exactstr: 0,
|
||||
substr: 1,
|
||||
isnull: 2
|
||||
isnull: 2,
|
||||
bool: 3
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -225,7 +270,7 @@ treeherder.factory('thJobFilters', function(thResultStatusList, $log, $rootScope
|
|||
removeWhenEmpty: false
|
||||
},
|
||||
failure_classification_id: {
|
||||
matchType: api.matchType.isnull,
|
||||
matchType: api.matchType.bool,
|
||||
values: [true, false],
|
||||
removeWhenEmpty: false
|
||||
}
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
'use strict';
|
||||
|
||||
treeherder.factory('ThLog', function($log, ThLogConfig) {
|
||||
// a logger that states the object doing the logging
|
||||
|
||||
var ThLog = function(name) {
|
||||
this.name = name;
|
||||
};
|
||||
|
||||
/**
|
||||
* If ``whitelist`` has values, then only show messages from those.
|
||||
* If ``whitelist`` is empty, then skip any messages from ``blacklist`` items.
|
||||
*/
|
||||
var whitelist = ThLogConfig.whitelist;
|
||||
var blacklist = ThLogConfig.blacklist;
|
||||
|
||||
ThLog.prototype.getClassName = function() {
|
||||
return this.name;
|
||||
};
|
||||
|
||||
ThLog.prototype.debug = function() {logIt(this, $log.debug, arguments);};
|
||||
ThLog.prototype.log = function() {logIt(this, $log.log, arguments);};
|
||||
ThLog.prototype.warn = function() {logIt(this, $log.warn, arguments);};
|
||||
ThLog.prototype.info = function() {logIt(this, $log.info, arguments);};
|
||||
ThLog.prototype.error = function() {logIt(this, $log.error, arguments);};
|
||||
|
||||
var logIt = function(self, func, args) {
|
||||
if ((whitelist.length && _.contains(whitelist, self.getClassName())) ||
|
||||
(blacklist.length && !_.contains(blacklist, self.getClassName())) ||
|
||||
(!whitelist.length && !blacklist.length)) {
|
||||
var newArgs = Array.prototype.slice.call(args);
|
||||
newArgs.unshift(self.getClassName());
|
||||
func.apply(null, newArgs);
|
||||
}
|
||||
};
|
||||
|
||||
return ThLog;
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* You can use this to configure which debug lines you want to see in your
|
||||
* ``local.conf.js`` file. You can see ONLY ``ResultSetCtrl`` lines by adding
|
||||
* a line like:
|
||||
*
|
||||
* ThLogConfigProvider.setWhitelist([
|
||||
* 'ResultSetCtrl'
|
||||
* ]);
|
||||
*
|
||||
* Note: even though this is called ThLogConfig, when you configure it, you must
|
||||
* refer to it as a ``ThLogConfigProvider`` in ``local.conf.js``.
|
||||
*/
|
||||
treeherder.provider('ThLogConfig', function() {
|
||||
this.whitelist = [];
|
||||
this.blacklist = [];
|
||||
|
||||
this.setBlacklist = function(bl) {
|
||||
this.blacklist = bl;
|
||||
};
|
||||
this.setWhitelist = function(wl) {
|
||||
this.whitelist = wl;
|
||||
};
|
||||
|
||||
this.$get = function() {
|
||||
var self = this;
|
||||
|
||||
return {
|
||||
|
||||
whitelist: self.whitelist,
|
||||
blacklist: self.blacklist
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
treeherder.config(["$provide", function($provide, ThLog) {
|
||||
$provide.decorator("$log", ["$delegate", function($delegate) {
|
||||
|
||||
$delegate.getInstance = function(className) {
|
||||
return new ThLog(className);
|
||||
};
|
||||
return $delegate;
|
||||
|
||||
}]);
|
||||
|
||||
}]);
|
|
@ -1,7 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
/* Services */
|
||||
treeherder.factory('thUrl',['$rootScope', 'thServiceDomain', '$log', function($rootScope, thServiceDomain, $log) {
|
||||
treeherder.factory('thUrl',['$rootScope', 'thServiceDomain', 'ThLog', function($rootScope, thServiceDomain, ThLog) {
|
||||
|
||||
var thUrl = {
|
||||
getRootUrl: function(uri) {
|
||||
|
@ -22,7 +22,9 @@ treeherder.factory('thUrl',['$rootScope', 'thServiceDomain', '$log', function($r
|
|||
|
||||
}]);
|
||||
|
||||
treeherder.factory('thSocket', function ($rootScope, $log, thUrl) {
|
||||
treeherder.factory('thSocket', function ($rootScope, ThLog, thUrl) {
|
||||
var $log = new ThLog("thSocket");
|
||||
|
||||
var socket = io.connect(thUrl.getSocketEventUrl());
|
||||
socket.on('connect', function () {
|
||||
$log.debug('socketio connected');
|
||||
|
@ -107,7 +109,7 @@ treeherder.factory('ThPaginator', function(){
|
|||
|
||||
});
|
||||
|
||||
treeherder.factory('BrowserId', function($http, $q, $log, thServiceDomain){
|
||||
treeherder.factory('BrowserId', function($http, $q, ThLog, thServiceDomain){
|
||||
|
||||
/*
|
||||
* BrowserId is a wrapper for the persona authentication service
|
||||
|
@ -180,9 +182,11 @@ treeherder.factory('BrowserId', function($http, $q, $log, thServiceDomain){
|
|||
return browserid;
|
||||
});
|
||||
|
||||
treeherder.factory('thNotify', function($timeout, $log){
|
||||
treeherder.factory('thNotify', function($timeout, ThLog){
|
||||
//a growl-like notification system
|
||||
|
||||
var $log = new ThLog("thNotify");
|
||||
|
||||
var thNotify = {
|
||||
// message queue
|
||||
notifications: [],
|
||||
|
@ -194,8 +198,7 @@ treeherder.factory('thNotify', function($timeout, $log){
|
|||
* after a while or not
|
||||
*/
|
||||
send: function(message, severity, sticky){
|
||||
$log.debug("received message");
|
||||
$log.debug(message);
|
||||
$log.debug("received message", message);
|
||||
var severity = severity || 'info';
|
||||
var sticky = sticky || false;
|
||||
thNotify.notifications.push({
|
||||
|
|
|
@ -1,115 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
treeherder.factory('ThRepositoryModel',
|
||||
['$http', 'thUrl', '$rootScope', '$log', 'localStorageService', 'thSocket', 'thEvents',
|
||||
function($http, thUrl, $rootScope, $log, localStorageService, thSocket, thEvents) {
|
||||
|
||||
var new_failures = {};
|
||||
|
||||
thSocket.on('job_failure', function(msg){
|
||||
if (! new_failures.hasOwnProperty(msg.branch)){
|
||||
new_failures[msg.branch] = [];
|
||||
}
|
||||
new_failures[msg.branch].push(msg.id);
|
||||
$log.debug("new failure on branch "+msg.branch);
|
||||
});
|
||||
|
||||
|
||||
// get the repositories (aka trees)
|
||||
// sample: 'resources/menu.json'
|
||||
var byName = function(name) {
|
||||
if ($rootScope.repos !== undefined) {
|
||||
for (var i = 0; i < $rootScope.repos.length; i++) {
|
||||
var repo = $rootScope.repos[i];
|
||||
if (repo.name === name) {
|
||||
return repo;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$log.warn("Repos list has not been loaded.");
|
||||
}
|
||||
$log.warn("'" + name + "' not found in repos list.");
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
// get by category
|
||||
var byGroup = function() {
|
||||
var groupedRepos = {};
|
||||
var group = function(repo) {
|
||||
if (!_.has(groupedRepos, repo.repository_group.name)) {
|
||||
groupedRepos[repo.repository_group.name] = [];
|
||||
}
|
||||
groupedRepos[repo.repository_group.name].push(repo);
|
||||
};
|
||||
|
||||
if (!groupedRepos.length) {
|
||||
_.each($rootScope.repos, group);
|
||||
}
|
||||
return groupedRepos;
|
||||
};
|
||||
|
||||
var addAsUnwatched = function(repo) {
|
||||
api.watchedRepos[repo.name] = false;
|
||||
};
|
||||
|
||||
var api = {
|
||||
// load the list of repos into $rootScope, and set the current repo.
|
||||
load: function(name) {
|
||||
|
||||
var storedWatchedRepos = localStorageService.get("watchedRepos") || {};
|
||||
$log.debug("stored watchedRepos");
|
||||
$log.debug(storedWatchedRepos);
|
||||
|
||||
return api.get_list().
|
||||
success(function(data) {
|
||||
$rootScope.repos = data;
|
||||
$rootScope.groupedRepos = byGroup();
|
||||
_.each(data, addAsUnwatched);
|
||||
_.extend(api.watchedRepos, storedWatchedRepos);
|
||||
|
||||
if (name) {
|
||||
$rootScope.currentRepo = byName(name);
|
||||
|
||||
}
|
||||
});
|
||||
},
|
||||
get_uri : function(){return thUrl.getRootUrl("/repository/");},
|
||||
|
||||
get_list: function(){
|
||||
return $http.get(api.get_uri(), {cache: true})
|
||||
},
|
||||
|
||||
// return the currently selected repo
|
||||
getCurrent: function() {
|
||||
return $rootScope.currentRepo;
|
||||
},
|
||||
// set the current repo to one in the repos list
|
||||
setCurrent: function(name) {
|
||||
$rootScope.currentRepo = byName(name);
|
||||
api.watchedRepos[name] = true;
|
||||
api.saveWatchedRepos();
|
||||
},
|
||||
// get a repo object without setting anything
|
||||
getRepo: function(name) {
|
||||
return byName(name);
|
||||
},
|
||||
getByGroup: function() {
|
||||
return byGroup();
|
||||
},
|
||||
watchedRepos: {},
|
||||
saveWatchedRepos: function() {
|
||||
localStorageService.set("watchedRepos", api.watchedRepos);
|
||||
|
||||
$log.debug("saveWatchedRepos");
|
||||
$log.debug(localStorageService.get("watchedRepos"));
|
||||
},
|
||||
repo_has_failures: function(repo_name){
|
||||
return ($rootScope.new_failures.hasOwnProperty(repo_name) &&
|
||||
$rootScope.new_failures[repo_name].length > 0);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return api;
|
||||
}]);
|
|
@ -2,7 +2,9 @@
|
|||
|
||||
treeherder.factory('thPinboard',
|
||||
function($http, thUrl, ThJobClassificationModel, $rootScope,
|
||||
thEvents, ThBugJobMapModel, thNotify) {
|
||||
thEvents, ThBugJobMapModel, thNotify, ThLog) {
|
||||
|
||||
var $log = new ThLog("thPinboard");
|
||||
|
||||
var pinnedJobs = {};
|
||||
var relatedBugs = {};
|
||||
|
@ -10,15 +12,18 @@ treeherder.factory('thPinboard',
|
|||
var saveClassification = function(job) {
|
||||
var classification = new ThJobClassificationModel(this);
|
||||
|
||||
job.failure_classification_id = classification.failure_classification_id;
|
||||
// classification can be left unset making this a no-op
|
||||
if (classification.failure_classification_id > 0) {
|
||||
job.failure_classification_id = classification.failure_classification_id;
|
||||
|
||||
classification.job_id = job.id;
|
||||
classification.create().
|
||||
success(function(data) {
|
||||
thNotify.send("classification saved for " + job.platform + ": " + job.job_type_name, "success");
|
||||
}).error(function(data) {
|
||||
thNotify.send("error saving classification for " + job.platform + ": " + job.job_type_name, "danger");
|
||||
});
|
||||
classification.job_id = job.id;
|
||||
classification.create().
|
||||
success(function(data) {
|
||||
thNotify.send("classification saved for " + job.platform + ": " + job.job_type_name, "success");
|
||||
}).error(function(data) {
|
||||
thNotify.send("error saving classification for " + job.platform + ": " + job.job_type_name, "danger");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var saveBugs = function(job) {
|
||||
|
@ -40,8 +45,12 @@ treeherder.factory('thPinboard',
|
|||
|
||||
var api = {
|
||||
pinJob: function(job) {
|
||||
pinnedJobs[job.id] = job;
|
||||
api.count.numPinnedJobs = _.size(pinnedJobs);
|
||||
if (api.spaceRemaining() > 0) {
|
||||
pinnedJobs[job.id] = job;
|
||||
api.count.numPinnedJobs = _.size(pinnedJobs);
|
||||
} else {
|
||||
thNotify.send("Pinboard is already at maximum size of " + api.maxNumPinned, "danger", true);
|
||||
}
|
||||
},
|
||||
|
||||
unPinJob: function(id) {
|
||||
|
@ -59,8 +68,11 @@ treeherder.factory('thPinboard',
|
|||
},
|
||||
|
||||
addBug: function(bug) {
|
||||
$log.debug("adding bug ", bug);
|
||||
relatedBugs[bug.id] = bug;
|
||||
api.count.numRelatedBugs = _.size(relatedBugs);
|
||||
$log.debug("related bugs", relatedBugs);
|
||||
|
||||
},
|
||||
|
||||
removeBug: function(id) {
|
||||
|
@ -105,23 +117,26 @@ treeherder.factory('thPinboard',
|
|||
|
||||
// save bug associations only on all pinned jobs
|
||||
saveBugsOnly: function() {
|
||||
if (!_.size(relatedBugs)) {
|
||||
thNotify.send("no bug associations to save");
|
||||
} else {
|
||||
_.each(pinnedJobs, saveBugs);
|
||||
$rootScope.$broadcast(thEvents.bugsAssociated, {jobs: pinnedJobs});
|
||||
}
|
||||
_.each(pinnedJobs, saveBugs);
|
||||
$rootScope.$broadcast(thEvents.bugsAssociated, {jobs: pinnedJobs});
|
||||
},
|
||||
|
||||
hasPinnedJobs: function() {
|
||||
return !_.isEmpty(pinnedJobs);
|
||||
},
|
||||
|
||||
spaceRemaining: function() {
|
||||
return api.maxNumPinned - api.count.numPinnedJobs;
|
||||
},
|
||||
|
||||
pinnedJobs: pinnedJobs,
|
||||
relatedBugs: relatedBugs,
|
||||
count: {
|
||||
numPinnedJobs: 0,
|
||||
numRelatedBugs: 0
|
||||
}
|
||||
},
|
||||
// not sure what this should be, but we need some limit, I think.
|
||||
maxNumPinned: 500
|
||||
};
|
||||
|
||||
return api;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
treeherder.factory('thResultSets',
|
||||
['$http', '$location', 'thUrl', 'thServiceDomain',
|
||||
function($http, $location, thUrl, thServiceDomain) {
|
||||
|
||||
// get the resultsets for this repo
|
||||
|
@ -42,4 +41,4 @@ treeherder.factory('thResultSets',
|
|||
return $http.get(thServiceDomain + uri, {params: {format: "json"}});
|
||||
}
|
||||
};
|
||||
}]);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
'use strict';
|
||||
|
||||
treeherder.factory('treeStatus', function($http, $q) {
|
||||
|
||||
var urlBase = "https://treestatus.mozilla.org/";
|
||||
|
||||
var getTreeStatusName = function(name) {
|
||||
// the thunderbird names in treestatus.mozilla.org don't match what
|
||||
// we use, so translate them. pretty hacky, yes...
|
||||
if (name.contains("thunderbird")) {
|
||||
if (name === "thunderbird-trunk") {
|
||||
return "comm-central-thunderbird";
|
||||
} else {
|
||||
var tokens = name.split("-");
|
||||
return "comm-" + tokens[1] + "-" + tokens[0];
|
||||
}
|
||||
}
|
||||
return name;
|
||||
};
|
||||
|
||||
var get = function(repoName) {
|
||||
var url = urlBase + getTreeStatusName(repoName);
|
||||
|
||||
return $http.get(url, {params: {format: "json"}});
|
||||
};
|
||||
|
||||
return {
|
||||
get: get
|
||||
};
|
||||
});
|
||||
|
|
@ -9,39 +9,37 @@
|
|||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<dl class="dl-horizontal">
|
||||
<span ng-repeat="(label, value) in artifact.header">
|
||||
<dt class="label label-info">{{label}}</dt>
|
||||
<dd class="">{{value}}</dd>
|
||||
</span>
|
||||
</dl>
|
||||
<div class="row" >
|
||||
<div ng-repeat="step in artifact.step_data.steps"
|
||||
ng-class="{'btn-warning': (step.error_count > 0), 'btn-success': (step.error_count == 0)}"
|
||||
ng-click="displayLog(step)"
|
||||
class="btn btn-block clearfix">
|
||||
<span class="pull-left clearfix">{{step.order+1}}. {{step.name}}</span>
|
||||
<span ng-init="time=formatTime(step.duration)"
|
||||
ng-mouseover="time=displayTime(step.started, step.finished)"
|
||||
ng-mouseleave="time=formatTime(step.duration)"
|
||||
class="pull-right clearfix">{{time}}</span>
|
||||
<div ng-switch on="(step.error_count > 0)">
|
||||
<p ng-switch-when="true" class="">
|
||||
<div ng-repeat="error in step.errors"
|
||||
ng-mouseover="check=(step==displayedStep)"
|
||||
ng-mouseleave="check=false"
|
||||
ng-class="{'lv-line-highlight': check}"
|
||||
ng-click="scrollTo(step, error.linenumber);
|
||||
$event.stopPropagation()"
|
||||
class="text-left pull-left lv-error-line">
|
||||
<span class="label label-default lv-line-no">{{error.linenumber}}</span>
|
||||
<span>{{error.line}}</span>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
<table class="table table-condensed">
|
||||
<tr ng-repeat="(label, value) in artifact.header">
|
||||
<th>{{label}}</th><td>{{value}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>Select one of these steps to see more details:</p>
|
||||
<div ng-repeat="step in artifact.step_data.steps"
|
||||
ng-class="{'btn-warning': (step.error_count > 0), 'btn-success': (step.error_count == 0)}"
|
||||
ng-click="displayLog(step)"
|
||||
class="btn btn-block logviewer-step clearfix">
|
||||
<span class="pull-left clearfix">{{step.order+1}}. {{step.name}}</span>
|
||||
<span ng-init="time=formatTime(step.duration)"
|
||||
ng-mouseover="time=displayTime(step.started, step.finished)"
|
||||
ng-mouseleave="time=formatTime(step.duration)"
|
||||
class="pull-right clearfix">{{time}}</span>
|
||||
<div ng-switch on="(step.error_count > 0)">
|
||||
<p ng-switch-when="true" class="">
|
||||
<div ng-repeat="error in step.errors"
|
||||
ng-mouseover="check=(step==displayedStep)"
|
||||
ng-mouseleave="check=false"
|
||||
ng-class="{'lv-line-highlight': check}"
|
||||
ng-click="scrollTo(step, error.linenumber);
|
||||
$event.stopPropagation()"
|
||||
class="text-left pull-left lv-error-line">
|
||||
<span class="label label-default lv-line-no">{{error.linenumber}}</span>
|
||||
<span title="{{error.line}}">{{error.line | limitTo: 67}}</span><span ng-if="error.line.length > 70">...</span>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 lv-log-container container well"
|
||||
id="lv-log-container">
|
||||
<div ng-repeat="lv_line in displayedStep.logPieces"
|
||||
|
@ -65,7 +63,8 @@
|
|||
<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/services/log.js"></script>
|
||||
<script src="js/models/job_artifact.js"></script>
|
||||
<script src="js/providers.js"></script>
|
||||
<script src="js/controllers/logviewer.js"></script>
|
||||
</body>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<ul class="dropdown-menu pull-left">
|
||||
<li><a target="_blank" href="https://tbpl.mozilla.org/mcmerge/?cset={{resultset.revision}}&tree={{repoName}}">m-mcMerge</a></li>
|
||||
<li><a target="_blank" href="https://secure.pub.build.mozilla.org/buildapi/self-serve/{{repoName}}/rev/{{resultset.revision}}">BuildAPI</a></li>
|
||||
<li><a target="_blank" href="" data-toggle="modal" data-target="#revisionListModal">Changeset URL List</a></li>
|
||||
<li class="hidden"><a target="_blank" href="" data-toggle="modal" data-target="#revisionListModal">Changeset URL List</a></li>
|
||||
<li class="hidden"><a href="" ng-disabled="true">Cancel All Jobs</a></li>
|
||||
</ul>
|
||||
</span>
|
||||
|
|
|
@ -1,7 +1,16 @@
|
|||
<div id="filter"
|
||||
class="th-top-nav-options-panel model-body"
|
||||
ng-controller="StatusFilterPanelCtrl">
|
||||
<div class="nav-panel-help-text">Specify which jobs to show based on this filter criteria.</div>
|
||||
ng-controller="FilterPanelCtrl">
|
||||
<div class="nav-panel-help-text">Specify which jobs to show based on this filter criteria.
|
||||
<span class="pull-right">
|
||||
<span class="btn btn-default btn-xs"
|
||||
title="set filtering to show only unclassified failures"
|
||||
ng-click="showUnclassifiedFailures()"><i class="fa fa-star-o"></i> unclassified failures</span>
|
||||
<span class="btn btn-default btn-xs"
|
||||
title="pin all jobs that pass the global filters"
|
||||
ng-click="pinAllShownJobs()"><i class="glyphicon glyphicon-pushpin"></i> pin all showing</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- result status filters -->
|
||||
<span ng-repeat="group in filterGroups"
|
|
@ -23,44 +23,43 @@
|
|||
ng-show="isSheriffPanelShowing"></i>
|
||||
</span>
|
||||
</span>
|
||||
<span class="btn btn-view-nav"
|
||||
ng-class="{'active': (isRepoPanelShowing)}"
|
||||
ng-click="isRepoPanelShowing=!isRepoPanelShowing"><span>repos</span>
|
||||
<i class="glyphicon glyphicon-chevron-down lightgray"
|
||||
ng-hide="isRepoPanelShowing"></i>
|
||||
<i class="glyphicon glyphicon-chevron-up lightgray"
|
||||
ng-show="isRepoPanelShowing"></i>
|
||||
</span>
|
||||
<span class="btn btn-view-nav"
|
||||
ng-class="{'active': (isFilterPanelShowing)}"
|
||||
ng-click="isFilterPanelShowing=!isFilterPanelShowing"><span>filters</span>
|
||||
<i class="glyphicon glyphicon-chevron-down lightgray"
|
||||
ng-hide="isFilterPanelShowing"></i>
|
||||
<i class="glyphicon glyphicon-chevron-up lightgray"
|
||||
ng-show="isFilterPanelShowing"></i>
|
||||
</span>
|
||||
<a class="btn btn-view-nav" href="help.html" target="_blank">help</a>
|
||||
<span class="nav-text white th-username">{{user.email}}</span>
|
||||
|
||||
<!--TODO: change this condition to enable the settings panel-->
|
||||
<span ng-show="false" class="btn btn-view-nav"
|
||||
ng-class="{'active': (isSettingsPanelShowing)}"
|
||||
ng-click="isSettingsPanelShowing=!isSettingsPanelShowing"><span>Settings</span>
|
||||
<i class="glyphicon glyphicon-chevron-down lightgray"
|
||||
ng-hide="isSettingsPanelShowing"></i>
|
||||
<i class="glyphicon glyphicon-chevron-up lightgray"
|
||||
ng-show="isSettingsPanelShowing"></i>
|
||||
<span class="pull-right">
|
||||
<span class="btn btn-view-nav"
|
||||
ng-class="{'active': (isRepoPanelShowing)}"
|
||||
ng-click="isRepoPanelShowing=!isRepoPanelShowing"><span>Repos</span>
|
||||
<i class="glyphicon glyphicon-chevron-down lightgray"
|
||||
ng-hide="isRepoPanelShowing"></i>
|
||||
<i class="glyphicon glyphicon-chevron-up lightgray"
|
||||
ng-show="isRepoPanelShowing"></i>
|
||||
</span>
|
||||
<span class="btn btn-view-nav"
|
||||
ng-class="{'active': (isFilterPanelShowing)}"
|
||||
ng-click="isFilterPanelShowing=!isFilterPanelShowing"><span>Filters</span>
|
||||
<i class="glyphicon glyphicon-chevron-down lightgray"
|
||||
ng-hide="isFilterPanelShowing"></i>
|
||||
<i class="glyphicon glyphicon-chevron-up lightgray"
|
||||
ng-show="isFilterPanelShowing"></i>
|
||||
</span>
|
||||
<a class="btn btn-view-nav" href="help.html" target="_blank">Help</a>
|
||||
<span class="nav-text white th-username">{{user.email}}</span>
|
||||
<!--TODO: change this condition to enable the settings panel-->
|
||||
<span ng-show="false" class="btn btn-view-nav"
|
||||
ng-class="{'active': (isSettingsPanelShowing)}"
|
||||
ng-click="isSettingsPanelShowing=!isSettingsPanelShowing"><span>Settings</span>
|
||||
<i class="glyphicon glyphicon-chevron-down lightgray"
|
||||
ng-hide="isSettingsPanelShowing"></i>
|
||||
<i class="glyphicon glyphicon-chevron-up lightgray"
|
||||
ng-show="isSettingsPanelShowing"></i>
|
||||
</span>
|
||||
<persona-buttons></persona-buttons>
|
||||
</span>
|
||||
|
||||
<persona-buttons></persona-buttons>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
<ng-include class="watched-repo-navbar" src="'partials/thWatchedRepoPanel.html'" ng-show="locationPath==='jobs'">
|
||||
</ng-include>
|
||||
</div>
|
||||
<th-watched-repo-panel ng-show="locationPath==='jobs'"></th-watched-repo-panel>
|
||||
<div ng-show="isFilterPanelShowing">
|
||||
<th-status-filter-panel></th-status-filter-panel>
|
||||
</div>
|
||||
<th-repo-panel ng-show="isRepoPanelShowing"></th-repo-panel>
|
||||
<th-sheriff-panel ng-show="isSheriffPanelShowing"></th-sheriff-panel>
|
||||
<th-settings-panel ng-show="isSettingsPanelShowing"></th-settings-panel>
|
||||
<ng-include src="'partials/thSheriffPanel.html'" ng-show="isSheriffPanelShowing"></ng-include>
|
||||
<ng-include src="'partials/thFilterPanel.html'" ng-show="isFilterPanelShowing"></ng-include>
|
||||
<ng-include src="'partials/thRepoPanel.html'" ng-show="isRepoPanelShowing"></ng-include>
|
||||
</nav>
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
<span class="btn job-btn {{ job.display.btnClass }}"
|
||||
ng-class="{'btn-lg selected-job': (selectedJob==job), 'btn-xs': (selectedJob!=job)}"
|
||||
title="{{ hoverText }}"
|
||||
ng-click="viewJob(job)"
|
||||
ng-hide="job.job_coalesced_to_guid"
|
||||
ng-right-click="viewLog(job.resource_uri)"
|
||||
data-job-id="{{ job.job_id }}">
|
||||
{{ job.job_type_symbol }}
|
||||
</span>
|
|
@ -1,4 +1,5 @@
|
|||
<div id="pinboard-panel"
|
||||
|
||||
ng-show="hasPinnedJobs()">
|
||||
<div class="bottom-panel-title"><i class="glyphicon glyphicon-pushpin"></i> pinboard</div>
|
||||
<div class="panel shadowed-panel pinboard-shadowed-panel">
|
||||
|
@ -9,6 +10,7 @@
|
|||
<div id="pinboard-related-bugs-panel">
|
||||
<div class="bottom-panel-title"><i class="fa fa-bug"></i> related bugs
|
||||
<a ng-click="toggleEnterBugNumber()"
|
||||
class="click-able-icon lightgray"
|
||||
title="type in a bug number"><i class="fa fa-plus"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -16,13 +18,13 @@
|
|||
<form ng-submit="saveEnteredBugNumber()">
|
||||
<input type="number"
|
||||
ng-show="enteringBugNumber"
|
||||
ng-model="newEnteredBugNumber"
|
||||
ng-model="$parent.newEnteredBugNumber"
|
||||
placeholder="enter bug number"
|
||||
numbers-only="numbers-only"
|
||||
focus-me="focusInput">
|
||||
</form>
|
||||
<span ng-repeat="bug in relatedBugs">
|
||||
<th-related-bug></th-related-bug>
|
||||
<th-related-bug-queued></th-related-bug-queued>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<span class="btn-group">
|
||||
<a class="btn btn-default btn-xs pinboard-related-bug-button"
|
||||
href="https://bugzilla.mozilla.org/show_bug.cgi?id={{bug.bug_id}}"
|
||||
target="_blank"
|
||||
>{{bug.bug_id}}</a>
|
||||
<span class="btn btn-ltgray btn-xs pinned-job-close-btn"
|
||||
ng-click="deleteBug(bug)"
|
||||
title="delete relation to bug {{bug.bug_id}}"><i class="fa fa-trash-o"></i></span>
|
||||
</span>
|
|
@ -0,0 +1,38 @@
|
|||
<ul class="dropdown-menu" role="menu">
|
||||
<li ng-show="reason" class="watched-repo-dropdown-item">
|
||||
<span ng-bind-html="reason"></span>
|
||||
</li>
|
||||
|
||||
<li class="divider" ng-show="reason && message_of_the_day"></li>
|
||||
|
||||
<li ng-show="message_of_the_day" class="watched-repo-dropdown-item">
|
||||
<span ng-bind-html="message_of_the_day"></span>
|
||||
</li>
|
||||
|
||||
<li class="divider" ng-show="reason || message_of_the_day"></li>
|
||||
|
||||
<li class="watched-repo-dropdown-item">
|
||||
<a href="irc://irc.mozilla.org/#developers" target="_blank">#developers</a>
|
||||
</li>
|
||||
|
||||
<li class="watched-repo-dropdown-item">
|
||||
<a href="https://www.google.com/calendar/embed?src=aelh98g866kuc80d5nbfqo6u54%40group.calendar.google.com" target="_blank">schedule</a>
|
||||
</li>
|
||||
|
||||
<li class="watched-repo-dropdown-item">
|
||||
<a href="https://treestatus.mozilla.org/{{name}}" target="_blank">tree status</a>
|
||||
</li>
|
||||
|
||||
<li class="watched-repo-dropdown-item">
|
||||
<a href="{{pushlog}}" target="_blank">pushlog</a>
|
||||
</li>
|
||||
|
||||
<li class="watched-repo-dropdown-item">
|
||||
<a href="https://secure.pub.build.mozilla.org/clobberer/?branch={{name}}" target="_blank">clobberer</a>
|
||||
</li>
|
||||
|
||||
<li class="watched-repo-dropdown-item">
|
||||
<a href="" ng-click="unwatchRepo(name)">unwatch</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
|
@ -13,12 +13,19 @@
|
|||
<div class="th-repo-group">
|
||||
<span ng-repeat="repo in group | orderBy:'name'"
|
||||
class="th-repo-group-items">
|
||||
<input type="checkbox"
|
||||
ng-model="watchedRepos[repo.name]"
|
||||
ng-change="saveWatchedRepos()">
|
||||
<input type="checkbox"
|
||||
ng-checked="watchedRepos[repo.name].isWatched"
|
||||
ng-click="toggleRepo(repo.name)">
|
||||
<a href="" class="repo-link"
|
||||
ng-click="changeRepo(repo.name)">{{repo.name}}
|
||||
</a>
|
||||
<span class="dropdown-toggle"
|
||||
data-toggle="dropdown"
|
||||
title="{{repo.name}} info"
|
||||
ng-click="setDropDownPull($event)">
|
||||
<span class="fa fa-caret-down"></span>
|
||||
</span>
|
||||
<th-repo-drop-down name="{{repo.name}}"></th-repo-drop-down>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
<span class="btn-group"
|
||||
ng-show="repoData.isWatched">
|
||||
<button ng-class="{'active': name===repoName}"
|
||||
ng-click="changeRepo(name)"
|
||||
type="button"
|
||||
title="{{titleText|stripHtml}}"
|
||||
class="btn btn-sm btn-view-nav">
|
||||
<i class="fa {{statusIcon}} {{statusColor}}"></i> {{name}}
|
||||
<span class="badge"
|
||||
ng-show="repoData.unclassifiedFailureCount > 0">
|
||||
{{repoData.unclassifiedFailureCount}}
|
||||
</span>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-view-nav dropdown-toggle"
|
||||
ng-class="{'active': name===repoName}"
|
||||
data-toggle="dropdown"
|
||||
title="{{name}} info"
|
||||
type="button"
|
||||
ng-click="setDropDownPull($event)">
|
||||
<span class="fa fa-info-circle"></span>
|
||||
</button>
|
||||
<th-repo-drop-down name="{{name}}"
|
||||
reason="{{repoData.treeStatus.reason}}"
|
||||
message_of_the_day="{{repoData.treeStatus.message_of_the_day}}">
|
||||
</th-repo-drop-down>
|
||||
</span>
|
||||
|
|
@ -1,17 +1,13 @@
|
|||
<div class="th-context-navbar" >
|
||||
<span ng-repeat="(repo, isVisible) in watchedRepos"
|
||||
ng-class="{'active': repo===repoName}"
|
||||
ng-click="changeRepo(repo)"
|
||||
ng-show="isVisible"
|
||||
type="button"
|
||||
class="btn btn-sm btn-view-nav">{{repo}} <span class="badge" ng-show="failures[repo] > 0">{{failures[repo]}}</span></span>
|
||||
<span class="pull-right">
|
||||
<div class="th-context-navbar watched-repo-navbar clearfix" >
|
||||
<th-watched-repo ng-repeat="(name, repoData) in watchedRepos"></th-watched-repo>
|
||||
<span class="navbar-right">
|
||||
<span>
|
||||
<form role="search" class="form-inline">
|
||||
<span class="label label-primary"><span ng-bind="pinboardCount.numPinnedJobs"></span> pinned jobs</span>
|
||||
<span class="label label-primary">0 unclassified</span>
|
||||
<div ng-controller="SearchCtrl" class="form-group form-inline">
|
||||
<input ng-model="searchQuery" ng-keyup="search($event)" type="text"
|
||||
<input id="platform-job-text-search-field"
|
||||
ng-model="searchQuery" ng-keyup="search($event)" type="text"
|
||||
class="form-control input-sm"
|
||||
placeholder="Filter platforms & jobs">
|
||||
</div>
|
||||
|
|
|
@ -1,12 +1,53 @@
|
|||
"use strict";
|
||||
|
||||
treeherder.controller('AnnotationsPluginCtrl',
|
||||
function AnnotationsPluginCtrl($scope, $log) {
|
||||
function AnnotationsPluginCtrl($scope, $rootScope, ThLog, ThJobClassificationModel,
|
||||
thNotify, thEvents, ThResultSetModel, ThBugJobMapModel) {
|
||||
var $log = new ThLog(this.constructor.name);
|
||||
|
||||
$log.debug("annotations plugin initialized");
|
||||
|
||||
$scope.$watch('classifications', function(newValue, oldValue){
|
||||
|
||||
$scope.tabs.annotations.num_items = newValue ? $scope.classifications.length : 0;
|
||||
}, true);
|
||||
|
||||
$scope.deleteClassification = function(classification) {
|
||||
var jcModel = new ThJobClassificationModel(classification);
|
||||
jcModel.delete()
|
||||
.then(
|
||||
function(){
|
||||
thNotify.send("Classification successfully deleted", "success", false);
|
||||
var jobs = {};
|
||||
jobs[$scope.selectedJob.id] = $scope.selectedJob;
|
||||
|
||||
// also be sure the job object in question gets updated to the latest
|
||||
// classification state (in case one was added or removed).
|
||||
ThResultSetModel.fetchJobs($scope.repoName, [$scope.job.id]);
|
||||
|
||||
$rootScope.$broadcast(thEvents.jobsClassified, {jobs: jobs});
|
||||
},
|
||||
function(){
|
||||
thNotify.send("Classification deletion failed", "danger", true);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
$scope.deleteBug = function(bug) {
|
||||
var bjmModel = new ThBugJobMapModel(bug);
|
||||
bjmModel.delete()
|
||||
.then(
|
||||
function(){
|
||||
thNotify.send("Association to bug " + bug.bug_id + " successfully deleted", "success", false);
|
||||
var jobs = {};
|
||||
jobs[$scope.selectedJob.id] = $scope.selectedJob;
|
||||
|
||||
$rootScope.$broadcast(thEvents.bugsAssociated, {jobs: jobs});
|
||||
},
|
||||
function(){
|
||||
thNotify.send("Association to bug " + bug.bug_id + " deletion failed", "danger", true);
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div ng-controller="AnnotationsPluginCtrl" class="row annotations-panel">
|
||||
<div class="col-xs-11 classifications-panel">
|
||||
<div class="col-xs-10 classifications-panel">
|
||||
<table class="table-condensed table-hover">
|
||||
<thead>
|
||||
<tr><th>Datetime</th><th>Author</th><th>Failure type</th><th>Note</th></tr>
|
||||
|
@ -13,23 +13,28 @@
|
|||
{{classification.who}}
|
||||
</td>
|
||||
<td>
|
||||
<span th-star star-id="classification.failure_classification_id"></span>
|
||||
<span th-failure-classification failure-id="classification.failure_classification_id"></span>
|
||||
</td>
|
||||
<td>
|
||||
{{classification.note}}
|
||||
</td>
|
||||
<td>
|
||||
<span ng-click="deleteClassification(classification)"
|
||||
class="click-able-icon"
|
||||
title="delete this classification">
|
||||
<i class="fa fa-trash-o"></i>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-show="classifications.length < 1"></br><em>This job has not been classified</em></div>
|
||||
</div>
|
||||
<div class="col-xs-1 bug-list-panel">
|
||||
<div class="col-xs-2 bug-list-panel">
|
||||
<h5><strong>Bugs</strong></h5>
|
||||
<ul class="bug-list">
|
||||
<li ng-repeat="bug in bugs">
|
||||
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id={{bug.bug_id}}"
|
||||
target="_blank"
|
||||
class="label label-default">{{bug.bug_id}}</a>
|
||||
<th-related-bug-saved></th-related-bug-saved>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -1,90 +1,12 @@
|
|||
"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,
|
||||
function BugsPluginCtrl($scope, $rootScope, ThLog, ThJobArtifactModel,
|
||||
ThBugJobMapModel, ThJobClassificationModel, thNotify, $modal) {
|
||||
var $log = new ThLog(this.constructor.name);
|
||||
|
||||
$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 ThJobClassificationModel({
|
||||
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";
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
"use strict";
|
||||
|
||||
treeherder.controller('PluginCtrl',
|
||||
|
||||
function PluginCtrl($scope, $rootScope, thUrl, ThJobClassificationModel,
|
||||
thClassificationTypes, ThJobModel, thEvents, dateFilter,
|
||||
numberFilter, ThBugJobMapModel, thResultStatus, thSocket,
|
||||
ThResultSetModel, $log) {
|
||||
ThResultSetModel, ThLog) {
|
||||
|
||||
var $log = new ThLog("PluginCtrl");
|
||||
|
||||
$scope.job = {};
|
||||
|
||||
|
@ -18,7 +18,9 @@ treeherder.controller('PluginCtrl',
|
|||
|
||||
// get the details of the current job
|
||||
ThJobModel.get($scope.job.id).then(function(data){
|
||||
_.extend($scope.job, data);
|
||||
$scope.job = data;
|
||||
$scope.$broadcast(thEvents.jobDetailLoaded);
|
||||
|
||||
updateVisibleFields();
|
||||
$scope.logs = data.logs;
|
||||
});
|
||||
|
@ -62,6 +64,17 @@ treeherder.controller('PluginCtrl',
|
|||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Test whether or not the selected job is a reftest
|
||||
*/
|
||||
$scope.isReftest = function() {
|
||||
if ($scope.selectedJob) {
|
||||
return $scope.selectedJob.job_group_symbol === "R";
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
$rootScope.$on(thEvents.jobClick, function(event, job) {
|
||||
selectJob(job, $rootScope.selectedJob);
|
||||
$rootScope.selectedJob = job;
|
||||
|
@ -75,7 +88,7 @@ treeherder.controller('PluginCtrl',
|
|||
$scope.updateBugs();
|
||||
});
|
||||
|
||||
$scope.classificationTypes = thClassificationTypes;
|
||||
$scope.classificationTypes = thClassificationTypes.classifications;
|
||||
|
||||
// load the list of existing classifications (including possibly a new one just
|
||||
// added).
|
||||
|
@ -100,7 +113,7 @@ treeherder.controller('PluginCtrl',
|
|||
};
|
||||
|
||||
var updateClassification = function(classification){
|
||||
if(classification.who != $scope.user.email){
|
||||
if(classification.who !== $scope.user.email){
|
||||
// get a fresh version of the job
|
||||
ThJobModel.get_list({id:classification.id})
|
||||
.then(function(job_list){
|
||||
|
@ -123,7 +136,7 @@ treeherder.controller('PluginCtrl',
|
|||
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
thSocket.on("job_classification", updateClassification);
|
||||
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
"use strict";
|
||||
|
||||
treeherder.controller('PinboardCtrl',
|
||||
function PinboardCtrl($scope, $rootScope, thEvents, thPinboard, thNotify) {
|
||||
function PinboardCtrl($scope, $rootScope, thEvents, thPinboard, thNotify, ThLog) {
|
||||
|
||||
var $log = new ThLog(this.constructor.name);
|
||||
|
||||
$rootScope.$on(thEvents.jobPin, function(event, job) {
|
||||
$scope.pinJob(job);
|
||||
$rootScope.$digest();
|
||||
$scope.$digest();
|
||||
});
|
||||
|
||||
$scope.pinJob = function(job) {
|
||||
|
@ -44,6 +46,7 @@ treeherder.controller('PinboardCtrl',
|
|||
}
|
||||
$scope.classification.who = $scope.user.email;
|
||||
thPinboard.save($scope.classification);
|
||||
$rootScope.selectedJob = null;
|
||||
} else {
|
||||
thNotify.send("must be logged in to classify jobs", "danger");
|
||||
}
|
||||
|
@ -76,6 +79,7 @@ treeherder.controller('PinboardCtrl',
|
|||
};
|
||||
|
||||
$scope.saveEnteredBugNumber = function() {
|
||||
$log.debug("new bug number to be saved: ", $scope.newEnteredBugNumber);
|
||||
thPinboard.addBug({id:$scope.newEnteredBugNumber});
|
||||
$scope.newEnteredBugNumber = null;
|
||||
$scope.toggleEnterBugNumber();
|
||||
|
@ -84,10 +88,12 @@ treeherder.controller('PinboardCtrl',
|
|||
$scope.viewJob = function(job) {
|
||||
$rootScope.selectedJob = job;
|
||||
$rootScope.$broadcast(thEvents.jobClick, job);
|
||||
$rootScope.$broadcast(thEvents.selectJob, job);
|
||||
};
|
||||
|
||||
$scope.classification = thPinboard.createNewClassification();
|
||||
$scope.pinnedJobs = thPinboard.pinnedJobs;
|
||||
$scope.relatedBugs = thPinboard.relatedBugs;
|
||||
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
<div ng-controller="PluginCtrl">
|
||||
<div ng-controller="PinboardCtrl">
|
||||
<div ng-controller="PluginCtrl" class="full-height">
|
||||
<div ng-controller="PinboardCtrl" class="full-height">
|
||||
<div class="bottom-panel-title" ng-hide="hasPinnedJobs()">Pin one or more jobs to classify (shift-click).</div>
|
||||
<th-pinboard-panel></th-pinboard-panel>
|
||||
|
||||
|
||||
<ng-include src="'partials/thPinboardPanel.html'"></ng-include>
|
||||
|
||||
|
||||
<div id="bottom-left-panel"
|
||||
ng-class="{'with-pinboard-abs': hasPinnedJobs(), 'without-pinboard-abs': !hasPinnedJobs()}">
|
||||
<div class="panel shadowed-panel">
|
||||
<div class="panel shadowed-panel full-height">
|
||||
<div class="panel-head">
|
||||
<table class="table-super-condensed table-striped">
|
||||
<table class="table-super-condensed table-striped {{resultStatusShading}}">
|
||||
<tr>
|
||||
<th class="small">Result</th>
|
||||
<td class="small {{ resultStatusClass }}">{{ job.result }}</td>
|
||||
<td class="small">{{ job.result }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="small">Machine name</th>
|
||||
|
@ -22,9 +26,13 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="bottom-center-panel"
|
||||
class="full-height"
|
||||
ng-class="{'with-pinboard-margin': hasPinnedJobs(), 'without-pinboard-margin': !hasPinnedJobs()}">
|
||||
<div class="panel shadowed-panel bottom-shadowed-panel">
|
||||
<div class="panel shadowed-panel"
|
||||
ng-class="{'bottom-shadowed-panel-with-pinboard': hasPinnedJobs(), 'bottom-shadowed-panel-without-pinboard': !hasPinnedJobs()}">
|
||||
<div class="panel-body resizable">
|
||||
<tabset class="tabs-below bottom-panel-tabs">
|
||||
<tab ng-repeat="(tab_id, tab) in tabs" active="tab.active" disabled="tab.disabled">
|
||||
|
@ -38,6 +46,8 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="bottom-menu"
|
||||
class="without-pinboard-abs">
|
||||
<div class="btn-group-vertical bottom-menu-group">
|
||||
|
@ -65,7 +75,9 @@
|
|||
ng-click="save()">
|
||||
<span class="glyphicon glyphicon-floppy-disk pull-left"></span> save
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-xs dropdown-toggle save-btn-dropdown" data-toggle="dropdown">
|
||||
<button type="button"
|
||||
class="btn btn-default btn-xs dropdown-toggle save-btn-dropdown"
|
||||
data-toggle="dropdown">
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu pull-right" role="menu">
|
||||
|
@ -73,7 +85,11 @@
|
|||
<li><a ng-click="saveBugsOnly()"><i class="fa fa-bug"></i> bugs only</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="btn-group-vertical">
|
||||
<div class="btn-group-vertical bottom-menu-group">
|
||||
<div class="btn btn-default btn-xs"
|
||||
ng-show="isReftest()">
|
||||
<a target="_blank" href="https://hg.mozilla.org/mozilla-central/raw-file/tip/layout/tools/reftest/reftest-analyzer.xhtml">Reftest Analyzer</a>
|
||||
</div>
|
||||
<div class="btn btn-default btn-xs"
|
||||
ng-show="logs"
|
||||
ng-disabled="artifacts.length>0">
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
"use strict";
|
||||
|
||||
treeherder.controller('SimilarJobsPluginCtrl',
|
||||
function SimilarJobsPluginCtrl($scope, $log, ThJobModel, thResultStatusInfo) {
|
||||
function SimilarJobsPluginCtrl($scope, ThLog, $rootScope, ThJobModel, thResultStatusInfo, thEvents,
|
||||
numberFilter, dateFilter, thClassificationTypes, thResultStatus,
|
||||
ThJobArtifactModel) {
|
||||
var $log = new ThLog(this.constructor.name);
|
||||
|
||||
$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 = {
|
||||
// do the jobs retrieval based on the user selection
|
||||
$scope.get_similar_jobs = function(){
|
||||
var options = {
|
||||
count: $scope.similar_jobs_count
|
||||
};
|
||||
angular.forEach($scope.similar_jobs_filters, function(value, key){
|
||||
|
@ -18,14 +20,25 @@ treeherder.controller('SimilarJobsPluginCtrl',
|
|||
});
|
||||
$log.log(options);
|
||||
ThJobModel.get_list(options).then(function(data){
|
||||
$log.log(data);
|
||||
$scope.similar_jobs = data;
|
||||
});
|
||||
};
|
||||
|
||||
// reset the counter and retrieve the list of jobs
|
||||
$scope.update_similar_jobs = function(event) {
|
||||
if($scope.job){
|
||||
$scope.similar_jobs_count = 20;
|
||||
$scope.similar_job_selected = null;
|
||||
}
|
||||
if($scope.job.id){
|
||||
$scope.get_similar_jobs();
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
$scope.result_status_info = thResultStatusInfo;
|
||||
|
||||
$scope.$watch('job.job_guid', $scope.update_similar_jobs, true);
|
||||
$scope.$on(thEvents.jobDetailLoaded, $scope.update_similar_jobs);
|
||||
$scope.similar_jobs = [];
|
||||
$scope.similar_jobs_filters = {
|
||||
"machine_id": false,
|
||||
|
@ -40,11 +53,48 @@ treeherder.controller('SimilarJobsPluginCtrl',
|
|||
return thResultStatusInfo(resultState).btnClass;
|
||||
|
||||
};
|
||||
|
||||
// this is triggered by the show more link
|
||||
$scope.show_more = function(){
|
||||
$scope.similar_jobs_count += 20;
|
||||
$scope.update_similar_jobs();
|
||||
$scope.get_similar_jobs();
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
$scope.similar_job_selected = null;
|
||||
|
||||
$scope.show_job_info = function(job){
|
||||
ThJobModel.get(job.id)
|
||||
.then(function(job){
|
||||
$scope.similar_job_selected = job;
|
||||
$scope.similar_job_selected.result_status = thResultStatus($scope.similar_job_selected);
|
||||
var duration = (
|
||||
$scope.similar_job_selected.end_timestamp - $scope.similar_job_selected.start_timestamp
|
||||
)/60;
|
||||
if (duration) {
|
||||
duration = numberFilter(duration, 0) + " minutes";
|
||||
}
|
||||
$scope.similar_job_selected.duration = duration;
|
||||
$scope.similar_job_selected.start_time = dateFilter(
|
||||
$scope.similar_job_selected.start_timestamp*1000,
|
||||
'short'
|
||||
);
|
||||
$scope.similar_job_selected.failure_classification_name = thClassificationTypes[
|
||||
$scope.similar_job_selected.failure_classification_id
|
||||
];
|
||||
|
||||
//retrieve the list of error lines
|
||||
ThJobArtifactModel.get_list({
|
||||
name: "Structured Log",
|
||||
job_id: $scope.similar_job_selected.id
|
||||
})
|
||||
.then(function(artifact_list){
|
||||
if(artifact_list.length > 0){
|
||||
$scope.similar_job_selected.error_lines = artifact_list[0].blob.step_data.all_errors;
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -1,32 +1,75 @@
|
|||
<div ng-controller="SimilarJobsPluginCtrl">
|
||||
<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>
|
||||
<div class="" ng-controller="SimilarJobsPluginCtrl">
|
||||
<div class="row">
|
||||
<div class="col-xs-2">
|
||||
<form role="form">
|
||||
<div class="checkbox">
|
||||
|
||||
<input ng-change="update_similar_jobs()" type="checkbox" ng-model="similar_jobs_filters.machine_id"/>
|
||||
<small>Same machine</small>
|
||||
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
|
||||
<input ng-change="update_similar_jobs()" type="checkbox" ng-model="similar_jobs_filters.job_type_id"/>
|
||||
<small>Same job type</small>
|
||||
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
|
||||
<input ng-change="update_similar_jobs()" type="checkbox" ng-model="similar_jobs_filters.build_platform_id"/>
|
||||
<small>Same platform</small>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-xs-10">
|
||||
<p class="similar_job_list">
|
||||
<button ng-click="show_job_info(job)" 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>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<table class="table-bordered table-super-condensed" ng-if="similar_job_selected">
|
||||
<tr>
|
||||
<th>Job Name</th>
|
||||
<th>Start time</th>
|
||||
<th>Duration</th>
|
||||
<th>Machine</th>
|
||||
<th>Build</th>
|
||||
<th>Result</th>
|
||||
<th>Classification</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ similar_job_selected.job_type_name }}</td>
|
||||
<td>{{ similar_job_selected.start_time }}</td>
|
||||
<td>{{ similar_job_selected.duration }}</td>
|
||||
<td>{{ similar_job_selected.machine_platform_architecture }}
|
||||
{{ similar_job_selected.machine_platform_os }}
|
||||
</td>
|
||||
<td>
|
||||
{{ similar_job_selected.build_architecture }}
|
||||
{{ similar_job_selected.build_platform }}
|
||||
{{ similar_job_selected.build_os }}
|
||||
</td>
|
||||
<td>{{ similar_job_selected.result_status }}</td>
|
||||
<td>{{ similar_job_selected.failure_classification_name }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<ul class="list-unstyled" ng-if="similar_job_selected.error_lines.length > 0">
|
||||
<li><h5>Error lines:</h5></li>
|
||||
<li class="" ng-repeat="error in similar_job_selected.error_lines">
|
||||
<small>{{error.line}}</small>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</p>
|
||||
<a ng-click="show_more()">Show more</a>
|
||||
|
||||
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
"use strict";
|
||||
|
||||
treeherder.controller('TinderboxPluginCtrl',
|
||||
function TinderboxPluginCtrl($scope, $rootScope, $log, ThJobArtifactModel) {
|
||||
function TinderboxPluginCtrl($scope, $rootScope, ThLog, ThJobArtifactModel) {
|
||||
var $log = new ThLog(this.constructor.name);
|
||||
|
||||
$log.debug("Tinderbox plugin initialized");
|
||||
var update_job_info = function(newValue, oldValue){
|
||||
$scope.tinderbox_lines = [];
|
||||
|
|
|
@ -3,17 +3,17 @@
|
|||
<tr ng-repeat="line in tinderbox_lines_parsed">
|
||||
<th>{{line.title}}</th>
|
||||
<td ng-switch on="line.type">
|
||||
<a ng-switch-when="link" href="{{line.link}}">{{line.value}}</a>
|
||||
<a ng-switch-when="link" href="{{line.link}}" target="_blank">{{line.value}}</a>
|
||||
<ul ng-switch-when="TalosResult">
|
||||
<li>Datazilla:
|
||||
<ul>
|
||||
<li ng-repeat="(k,v) in line.value.datazilla"><a href="{{v.url}}">{{k}}</a></li>
|
||||
<li ng-repeat="(k,v) in line.value.datazilla"><a href="{{v.url}}" target="_blank">{{k}}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li>Graphserver:
|
||||
<ul>
|
||||
<li ng-repeat="(k,v) in line.value.graphserver">{{k}}:<a href="{{v.url}}">{{v.result}}</a></li>
|
||||
<li ng-repeat="(k,v) in line.value.graphserver">{{k}}:<a href="{{v.url}}" target="_blank">{{v.result}}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
Загрузка…
Ссылка в новой задаче