зеркало из https://github.com/mozilla/treeherder.git
merged changes from master
This commit is contained in:
Коммит
6b0c56eb29
|
@ -1,12 +1,12 @@
|
||||||
body {
|
body {
|
||||||
padding-top: 64px;
|
padding-top: 61px;
|
||||||
padding-bottom: 500px;
|
padding-bottom: 500px;
|
||||||
min-width: 800px;
|
min-width: 970px;
|
||||||
width: auto !important;
|
width: auto !important;
|
||||||
width: 1300px;
|
width: 1300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav, .pagination, .carousel, .panel-title a {
|
.pagination, .carousel, .panel-title a {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,12 +15,16 @@ body {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.full-height {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.th-navbar {
|
.th-navbar {
|
||||||
min-width: 800px;
|
min-width: 970px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar {
|
.navbar {
|
||||||
min-width: 800px;
|
min-width: 970px;
|
||||||
min-height: 36px;
|
min-height: 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,9 +42,19 @@ body {
|
||||||
|
|
||||||
.th-global-navbar {
|
.th-global-navbar {
|
||||||
border-bottom: 1px solid black;
|
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 {
|
.th-username {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
@ -51,14 +65,34 @@ body {
|
||||||
|
|
||||||
.th-context-navbar {
|
.th-context-navbar {
|
||||||
background-color: #354048;
|
background-color: #354048;
|
||||||
height: 30px;
|
min-width: 970px;
|
||||||
min-width: 800px;
|
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 {
|
.th-content {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-width: 800px;
|
min-width: 970px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-bottom: 101px;
|
padding-bottom: 101px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
@ -76,8 +110,8 @@ body {
|
||||||
background-color: lightgray;
|
background-color: lightgray;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
max-height: 500px;
|
max-height: 300px;
|
||||||
overflow: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.th-top-nav-options-panel .th-option-heading {
|
.th-top-nav-options-panel .th-option-heading {
|
||||||
|
@ -149,6 +183,15 @@ body {
|
||||||
min-width: 225px;
|
min-width: 225px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.th-repo-group-items .dropdown-menu {
|
||||||
|
top: inherit;
|
||||||
|
left: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.th-repo-group-items .dropdown-toggle {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Failures
|
Failures
|
||||||
*/
|
*/
|
||||||
|
@ -241,8 +284,10 @@ body {
|
||||||
|
|
||||||
.revision-link {
|
.revision-link {
|
||||||
padding-top: 5px;
|
padding-top: 5px;
|
||||||
width: 115px;
|
width: 130px;
|
||||||
display: block;
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.revision-button {
|
.revision-button {
|
||||||
|
@ -262,6 +307,7 @@ body {
|
||||||
.result-set .job-list-nopad {
|
.result-set .job-list-nopad {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected-job {
|
.selected-job {
|
||||||
|
@ -313,10 +359,14 @@ div.navbar-fixed-bottom{
|
||||||
}
|
}
|
||||||
|
|
||||||
div.navbar-fixed-bottom .tab-content{
|
div.navbar-fixed-bottom .tab-content{
|
||||||
height:160px;
|
height: calc(100% - 22px);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bottom-panel-tabs {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.bottom-panel-tabs .nav li a {
|
.bottom-panel-tabs .nav li a {
|
||||||
padding: 1px 10px 2px 10px;
|
padding: 1px 10px 2px 10px;
|
||||||
}
|
}
|
||||||
|
@ -325,34 +375,55 @@ div.navbar-fixed-bottom dt,
|
||||||
div.navbar-fixed-bottom dt {
|
div.navbar-fixed-bottom dt {
|
||||||
font-size:.8em;
|
font-size:.8em;
|
||||||
}
|
}
|
||||||
#bottom-left-panel {width:242px; position: absolute; left:5px;}
|
#bottom-left-panel {
|
||||||
#bottom-left-panel .panel{padding:5px;}
|
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 {
|
#bottom-center-panel {
|
||||||
margin-right: 90px;
|
margin-right: 98px;
|
||||||
margin-left: 242px;
|
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-success {background-color: rgba(2, 131, 44, 0.24);}
|
||||||
.result-status-shading-testfailed {background-color: rgba(221, 102, 2, 0.56);}
|
.result-status-shading-testfailed {background-color: rgba(221, 102, 2, 0.25);}
|
||||||
.result-status-shading-busted {background-color: rgba(144, 0, 0, 0.60);}
|
.result-status-shading-busted {background-color: rgba(144, 0, 0, 0.25);}
|
||||||
.result-status-shading-exception {background-color: rgba(61, 2, 85, 0.50);}
|
.result-status-shading-exception {background-color: rgba(61, 2, 85, 0.25);}
|
||||||
.result-status-shading-retry {background-color: #263fc3;}
|
.result-status-shading-retry {background-color: rgba(38, 63, 195, 0.25);}
|
||||||
.result-status-shading-usercancel {background-color: rgba(250, 115, 172, 0.63)}
|
.result-status-shading-usercancel {background-color: rgba(250, 115, 172, 0.25)}
|
||||||
.result-status-shading-pending {background-color: white;}
|
.result-status-shading-pending {background-color: white;}
|
||||||
.result-status-shading-running {background-color: white;}
|
.result-status-shading-running {background-color: white;}
|
||||||
|
|
||||||
.bottom-shadowed-panel {
|
.bottom-shadowed-panel-with-pinboard {
|
||||||
height: 195px;
|
height: calc(100% - 75px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-shadowed-panel-without-pinboard {
|
||||||
|
height: calc(100% - 20px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-menu-group {
|
.bottom-menu-group {
|
||||||
width: 90px;
|
width: 98px;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.save-btn {
|
.save-btn {
|
||||||
width: 73px;
|
width: 81px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.save-btn-dropdown {
|
.save-btn-dropdown {
|
||||||
|
@ -407,12 +478,12 @@ div.navbar-fixed-bottom dt {
|
||||||
}
|
}
|
||||||
|
|
||||||
.pinboard-classification-comment {
|
.pinboard-classification-comment {
|
||||||
width: 185px;
|
width: 177px;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pinboard-classification-select {
|
.pinboard-classification-select {
|
||||||
width: 185px;
|
width: 177px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pinned-job {
|
.pinned-job {
|
||||||
|
@ -427,7 +498,10 @@ div.navbar-fixed-bottom dt {
|
||||||
margin-top: 0.3em;
|
margin-top: 0.3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-body{ padding:5px;}
|
.panel-body{
|
||||||
|
padding:5px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.timestamp-name {
|
.timestamp-name {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -502,13 +576,16 @@ div.navbar-fixed-bottom dt {
|
||||||
width: 69px;
|
width: 69px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.click-able-icon, .nav-tabs li {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CUSTOM BUTTONS
|
* CUSTOM BUTTONS
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.btn-view-nav {
|
.btn-view-nav {
|
||||||
background-color: rgba(63, 74, 81, 0.56);
|
background-color: rgba(75, 86, 93, 0.56);
|
||||||
border-color: #22282d;
|
border-color: #22282d;
|
||||||
color: lightgray;
|
color: lightgray;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
@ -1015,7 +1092,7 @@ fieldset[disabled] .btn-repo.active {
|
||||||
position:fixed;
|
position:fixed;
|
||||||
top:70px;
|
top:70px;
|
||||||
right:10px;
|
right:10px;
|
||||||
z-index: 9000;
|
z-index: 1100;
|
||||||
}
|
}
|
||||||
|
|
||||||
#notification_box div.alert{
|
#notification_box div.alert{
|
||||||
|
@ -1044,4 +1121,6 @@ fieldset[disabled] .btn-repo.active {
|
||||||
.form-group-inline>.form-group{
|
.form-group-inline>.form-group{
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: top;
|
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-heading"><h3>Keyboard shortcuts</h3></div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<table id="shortcuts">
|
<table id="shortcuts">
|
||||||
<tr><th>space</th>
|
<tr><th>s</th>
|
||||||
<td>Select/deselect active build or changeset</td></tr>
|
<td>Add selected job to the pin board</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>j</th>
|
<tr><th>j</th>
|
||||||
<td>Highlight next unstarred failure</td></tr>
|
<td>Highlight next unstarred failure</td></tr>
|
||||||
<tr><th>k</th>
|
<tr><th>k</th>
|
||||||
|
@ -69,15 +65,9 @@
|
||||||
<tr><th>p</th>
|
<tr><th>p</th>
|
||||||
<td>Highlight previous unstarred failure</td></tr>
|
<td>Highlight previous unstarred failure</td></tr>
|
||||||
<tr><th>u</th>
|
<tr><th>u</th>
|
||||||
<td>Toggle showing only unstarred failures</td></tr>
|
<td>Show only unstarred failures</td></tr>
|
||||||
<tr><th>Click</th>
|
<tr><th>Shift-Click</th>
|
||||||
<td>Choose an active build and display its details</td></tr>
|
<td>Add job to the pinboard</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>
|
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -317,4 +307,4 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -13,16 +13,11 @@
|
||||||
<link href="css/persona-buttons.css" rel="stylesheet" media="screen">
|
<link href="css/persona-buttons.css" rel="stylesheet" media="screen">
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body ng-controller="MainCtrl">
|
<body ng-controller="MainCtrl" ng-keydown="processKeyboardInput($event)">
|
||||||
<th-global-top-nav-panel></th-global-top-nav-panel>
|
<ng-include id="th-global-top-nav-panel" src="'partials/thGlobalTopNavPanel.html'"></ng-include>
|
||||||
<div class="th-content">
|
<div class="th-content">
|
||||||
|
|
||||||
<span class="th-view-content" ng-cloak>
|
<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>
|
<ng-view ></ng-view>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
@ -31,7 +26,7 @@
|
||||||
<!-- Footer -->
|
<!-- Footer -->
|
||||||
<div class="nav navbar navbar-default navbar-fixed-bottom bottom-panel" ng-show="selectedJob">
|
<div class="nav navbar navbar-default navbar-fixed-bottom bottom-panel" ng-show="selectedJob">
|
||||||
<resizable-panel></resizable-panel>
|
<resizable-panel></resizable-panel>
|
||||||
<div ng-include src="'plugins/pluginpanel.html'"></div>
|
<div class="full-height" ng-include src="'plugins/pluginpanel.html'"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<th-notification-box></th-notification-box>
|
<th-notification-box></th-notification-box>
|
||||||
|
@ -47,26 +42,39 @@
|
||||||
<script src="vendor/socket.io.js"></script>
|
<script src="vendor/socket.io.js"></script>
|
||||||
<script src="vendor/angular-local-storage.min.js"></script>
|
<script src="vendor/angular-local-storage.min.js"></script>
|
||||||
<script src="vendor/underscore-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/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/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/main.js"></script>
|
||||||
<script src="js/services/jobfilters.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/pinboard.js"></script>
|
||||||
<script src="js/services/resultsets.js"></script>
|
<script src="js/services/resultsets.js"></script>
|
||||||
<script src="js/services/models/resultsets.js"></script>
|
<script src="js/services/treestatus.js"></script>
|
||||||
<script src="js/services/models/job_artifact.js"></script>
|
<!-- Model services -->
|
||||||
<script src="js/services/models/job_filter.js"></script>
|
<script src="js/models/resultsets.js"></script>
|
||||||
<script src="js/services/models/exclusion_profile.js"></script>
|
<script src="js/models/job_artifact.js"></script>
|
||||||
<script src="js/services/models/repository.js"></script>
|
<script src="js/models/repository.js"></script>
|
||||||
<script src="js/services/models/bug_job_map.js"></script>
|
<script src="js/models/bug_job_map.js"></script>
|
||||||
<script src="js/services/models/build_platform.js"></script>
|
<script src="js/models/classification.js"></script>
|
||||||
<script src="js/services/models/job_type.js"></script>
|
<script src="js/models/job.js"></script>
|
||||||
<script src="js/services/models/classification.js"></script>
|
<script src="js/models/job_filter.js"></script>
|
||||||
<script src="js/services/models/option.js"></script>
|
<script src="js/models/exclusion_profile.js"></script>
|
||||||
<script src="js/services/models/job.js"></script>
|
<script src="js/models/build_platform.js"></script>
|
||||||
<script src="js/services/models/user.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/main.js"></script>
|
||||||
<script src="js/controllers/sheriff.js"></script>
|
<script src="js/controllers/sheriff.js"></script>
|
||||||
<script src="js/controllers/settings.js"></script>
|
<script src="js/controllers/settings.js"></script>
|
||||||
|
@ -75,12 +83,14 @@
|
||||||
<script src="js/controllers/jobs.js"></script>
|
<script src="js/controllers/jobs.js"></script>
|
||||||
<script src="js/controllers/machines.js"></script>
|
<script src="js/controllers/machines.js"></script>
|
||||||
<script src="js/controllers/timeline.js"></script>
|
<script src="js/controllers/timeline.js"></script>
|
||||||
|
<!-- Plugins -->
|
||||||
<script src="plugins/controller.js"></script>
|
<script src="plugins/controller.js"></script>
|
||||||
<script src="plugins/pinboard.js"></script>
|
<script src="plugins/pinboard.js"></script>
|
||||||
<script src="plugins/annotations/controller.js"></script>
|
<script src="plugins/annotations/controller.js"></script>
|
||||||
<script src="plugins/tinderbox/controller.js"></script>
|
<script src="plugins/tinderbox/controller.js"></script>
|
||||||
<script src="plugins/bugs_suggestions/controller.js"></script>
|
<script src="plugins/bugs_suggestions/controller.js"></script>
|
||||||
<script src="plugins/similar_jobs/controller.js"></script>
|
<script src="plugins/similar_jobs/controller.js"></script>
|
||||||
|
|
||||||
<script src="js/filters.js"></script>
|
<script src="js/filters.js"></script>
|
||||||
<script src="vendor/Config.js"></script>
|
<script src="vendor/Config.js"></script>
|
||||||
<script src="https://login.persona.org/include.js"></script>
|
<script src="https://login.persona.org/include.js"></script>
|
||||||
|
@ -147,9 +157,28 @@
|
||||||
|
|
||||||
<!-- Job Btn span -->
|
<!-- Job Btn span -->
|
||||||
<script type="'text/ng-template'" id="jobBtnClone.html">
|
<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>
|
</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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
14
ui/js/app.js
14
ui/js/app.js
|
@ -43,18 +43,4 @@ treeherder.config(function($routeProvider, $httpProvider, $logProvider) {
|
||||||
otherwise({redirectTo: '/jobs'});
|
otherwise({redirectTo: '/jobs'});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
var logViewer = angular.module('logViewer',['treeherder']);
|
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
|
// mozilla hosted service
|
||||||
//window.thServiceDomain = "http://dev.treeherder.mozilla.org";
|
//window.thServiceDomain = "http://dev.treeherder.mozilla.org";
|
||||||
|
|
||||||
// local vagrant instance of service
|
// local vagrant instance of service
|
||||||
window.thServiceDomain = "http://local.treeherder.mozilla.org";
|
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";
|
"use strict";
|
||||||
|
|
||||||
treeherder.controller('StatusFilterPanelCtrl',
|
treeherder.controller('FilterPanelCtrl',
|
||||||
function StatusFilterPanelCtrl($scope, $rootScope, $routeParams, $location, $log,
|
function FilterPanelCtrl($scope, $rootScope, $routeParams, $location, ThLog,
|
||||||
localStorageService, thResultStatusList, thEvents, thJobFilters) {
|
localStorageService, thResultStatusList, thEvents,
|
||||||
|
thJobFilters) {
|
||||||
|
var $log = new ThLog(this.constructor.name);
|
||||||
|
|
||||||
$scope.filterOptions = thResultStatusList;
|
$scope.filterOptions = thResultStatusList;
|
||||||
|
|
||||||
|
@ -29,8 +31,10 @@ treeherder.controller('StatusFilterPanelCtrl',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle checking the "all" button for a result status group
|
* 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) {
|
var check = function(rs) {
|
||||||
$scope.resultStatusFilters[rs] = group.allChecked;
|
$scope.resultStatusFilters[rs] = group.allChecked;
|
||||||
};
|
};
|
||||||
|
@ -41,9 +45,35 @@ treeherder.controller('StatusFilterPanelCtrl',
|
||||||
group.resultStatuses,
|
group.resultStatuses,
|
||||||
group.allChecked
|
group.allChecked
|
||||||
);
|
);
|
||||||
$rootScope.$broadcast(thEvents.globalFilterChanged,
|
|
||||||
{target: group, newValue: group.allChecked});
|
if (!quiet) {
|
||||||
showCheck();
|
$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,
|
$rootScope.$broadcast(thEvents.globalFilterChanged,
|
||||||
{target: filter, newValue: $scope.resultStatusFilters[filter]});
|
{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``
|
* ``classified`` (when true) or ``unclassified``
|
||||||
* (when false)
|
* (when false)
|
||||||
*/
|
*/
|
||||||
$scope.toggleClassificationFilter = function(isClassified) {
|
$scope.setClassificationFilter = function(isClassified, isChecked, quiet) {
|
||||||
var field = "failure_classification_id";
|
var field = "failure_classification_id";
|
||||||
// this function is called before the checkbox value has actually
|
// this function is called before the checkbox value has actually
|
||||||
// changed the scope model value, so change to the inverse.
|
// 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 func = isChecked? thJobFilters.addFilter: thJobFilters.removeFilter;
|
||||||
var target = isClassified? "classified": "unclassified";
|
var target = isClassified? "classified": "unclassified";
|
||||||
|
|
||||||
func(field, isClassified, thJobFilters.matchType.isnull);
|
func(field, isClassified, thJobFilters.matchType.bool);
|
||||||
|
|
||||||
$rootScope.$broadcast(thEvents.globalFilterChanged,
|
if (!quiet) {
|
||||||
{target: target, newValue: isChecked});
|
$rootScope.$broadcast(thEvents.globalFilterChanged,
|
||||||
showCheck();
|
{target: target, newValue: isChecked});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.createFieldFilter = function() {
|
$scope.createFieldFilter = function() {
|
||||||
|
@ -96,7 +134,7 @@ treeherder.controller('StatusFilterPanelCtrl',
|
||||||
|
|
||||||
|
|
||||||
$scope.addFieldFilter = function() {
|
$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 === "") {
|
if (!$scope.newFieldFilter || $scope.newFieldFilter.field === "" || $scope.newFieldFilter.value === "") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -112,7 +150,6 @@ treeherder.controller('StatusFilterPanelCtrl',
|
||||||
$rootScope.$broadcast(thEvents.globalFilterChanged,
|
$rootScope.$broadcast(thEvents.globalFilterChanged,
|
||||||
{target: $scope.newFieldFilter.field, newValue: $scope.newFieldFilter.value});
|
{target: $scope.newFieldFilter.field, newValue: $scope.newFieldFilter.value});
|
||||||
$scope.newFieldFilter = null;
|
$scope.newFieldFilter = null;
|
||||||
showCheck();
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -123,11 +160,10 @@ treeherder.controller('StatusFilterPanelCtrl',
|
||||||
$rootScope.$broadcast(thEvents.globalFilterChanged,
|
$rootScope.$broadcast(thEvents.globalFilterChanged,
|
||||||
{target: "allFieldFilters", newValue: null});
|
{target: "allFieldFilters", newValue: null});
|
||||||
$scope.fieldFilters = [];
|
$scope.fieldFilters = [];
|
||||||
showCheck();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.removeFilter = function(index) {
|
$scope.removeFilter = function(index) {
|
||||||
$log.debug("removing index: " + index);
|
$log.debug("removing index", index);
|
||||||
thJobFilters.removeFilter(
|
thJobFilters.removeFilter(
|
||||||
$scope.fieldFilters[index].field,
|
$scope.fieldFilters[index].field,
|
||||||
$scope.fieldFilters[index].value
|
$scope.fieldFilters[index].value
|
||||||
|
@ -135,40 +171,11 @@ treeherder.controller('StatusFilterPanelCtrl',
|
||||||
$rootScope.$broadcast(thEvents.globalFilterChanged,
|
$rootScope.$broadcast(thEvents.globalFilterChanged,
|
||||||
{target: $scope.fieldFilters[index].field, newValue: null});
|
{target: $scope.fieldFilters[index].field, newValue: null});
|
||||||
$scope.fieldFilters.splice(index, 1);
|
$scope.fieldFilters.splice(index, 1);
|
||||||
showCheck();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
$scope.pinAllShownJobs = function() {
|
||||||
@@@ TODO: CAMD: test code, remove before merge.
|
thJobFilters.pinAllShownJobs();
|
||||||
*/
|
|
||||||
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()));
|
|
||||||
};
|
};
|
||||||
// END test code
|
|
||||||
|
|
||||||
$scope.resultStatusFilters = {};
|
$scope.resultStatusFilters = {};
|
||||||
for (var i = 0; i < $scope.filterOptions.length; i++) {
|
for (var i = 0; i < $scope.filterOptions.length; i++) {
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
treeherder.controller('JobsCtrl',
|
treeherder.controller('JobsCtrl',
|
||||||
function JobsCtrl($scope, $http, $rootScope, $routeParams, $log, $cookies,
|
function JobsCtrl($scope, $http, $rootScope, $routeParams, ThLog, $cookies,
|
||||||
localStorageService, thUrl, ThRepositoryModel, thSocket,
|
localStorageService, thUrl, ThRepositoryModel, thSocket,
|
||||||
ThResultSetModel, thResultStatusList) {
|
ThResultSetModel, thResultStatusList) {
|
||||||
|
var $log = new ThLog(this.constructor.name);
|
||||||
|
|
||||||
// load our initial set of resultsets
|
// load our initial set of resultsets
|
||||||
// scope needs this function so it can be called directly by the user, too.
|
// scope needs this function so it can be called directly by the user, too.
|
||||||
|
@ -18,8 +19,9 @@ treeherder.controller('JobsCtrl',
|
||||||
} else {
|
} else {
|
||||||
$rootScope.repoName = "mozilla-inbound";
|
$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);
|
ThResultSetModel.addRepository($scope.repoName);
|
||||||
|
|
||||||
|
@ -28,9 +30,6 @@ treeherder.controller('JobsCtrl',
|
||||||
$scope.job_map = ThResultSetModel.getJobMap($scope.repoName);
|
$scope.job_map = ThResultSetModel.getJobMap($scope.repoName);
|
||||||
$scope.statusList = thResultStatusList;
|
$scope.statusList = thResultStatusList;
|
||||||
|
|
||||||
// load the list of repos into $rootScope, and set the current repo.
|
|
||||||
ThRepositoryModel.load($scope.repoName);
|
|
||||||
|
|
||||||
if(ThResultSetModel.isNotLoaded($scope.repoName)){
|
if(ThResultSetModel.isNotLoaded($scope.repoName)){
|
||||||
// get our first set of resultsets
|
// get our first set of resultsets
|
||||||
$scope.fetchResultSets(10);
|
$scope.fetchResultSets(10);
|
||||||
|
@ -41,9 +40,11 @@ treeherder.controller('JobsCtrl',
|
||||||
|
|
||||||
|
|
||||||
treeherder.controller('ResultSetCtrl',
|
treeherder.controller('ResultSetCtrl',
|
||||||
function ResultSetCtrl($scope, $rootScope, $http, $log, $location,
|
function ResultSetCtrl($scope, $rootScope, $http, ThLog, $location,
|
||||||
thUrl, thServiceDomain, thResultStatusInfo,
|
thUrl, thServiceDomain, thResultStatusInfo,
|
||||||
ThResultSetModel, thEvents, thJobFilters, $route) {
|
ThResultSetModel, thEvents, thJobFilters) {
|
||||||
|
|
||||||
|
var $log = new ThLog(this.constructor.name);
|
||||||
|
|
||||||
$scope.getCountClass = function(resultStatus) {
|
$scope.getCountClass = function(resultStatus) {
|
||||||
return thResultStatusInfo(resultStatus).btnClass;
|
return thResultStatusInfo(resultStatus).btnClass;
|
||||||
|
@ -111,8 +112,8 @@ treeherder.controller('ResultSetCtrl',
|
||||||
thEvents.resultSetFilterChanged, $scope.resultset
|
thEvents.resultSetFilterChanged, $scope.resultset
|
||||||
);
|
);
|
||||||
|
|
||||||
$log.debug("toggled: " + resultStatus);
|
$log.debug("toggled: ", resultStatus);
|
||||||
$log.debug($scope.resultStatusFilters);
|
$log.debug("resultStatusFilters", $scope.resultStatusFilters);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -125,7 +126,7 @@ treeherder.controller('ResultSetCtrl',
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.revisionResultsetFilterUrl = $scope.urlBasePath + "?repo=" + $scope.repoName + "&revision=" + $scope.resultset.revision;
|
$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();
|
$scope.resultStatusFilters = thJobFilters.copyResultStatusFilters();
|
||||||
|
|
||||||
|
@ -135,7 +136,7 @@ treeherder.controller('ResultSetCtrl',
|
||||||
$scope.isCollapsedRevisions = true;
|
$scope.isCollapsedRevisions = true;
|
||||||
|
|
||||||
$rootScope.$on(thEvents.jobContextMenu, function(event, job){
|
$rootScope.$on(thEvents.jobContextMenu, function(event, job){
|
||||||
$log.debug(thEvents.jobContextMenu + ' caught');
|
$log.debug("caught", thEvents.jobContextMenu);
|
||||||
//$scope.viewLog(job.resource_uri);
|
//$scope.viewLog(job.resource_uri);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
logViewer.controller('LogviewerCtrl',
|
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();
|
var query_string = $location.search();
|
||||||
if (query_string.repo !== "") {
|
if (query_string.repo !== "") {
|
||||||
|
|
|
@ -1,22 +1,49 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
treeherder.controller('MainCtrl',
|
treeherder.controller('MainCtrl',
|
||||||
function MainController($scope, $rootScope, $routeParams, $location, $log,
|
function MainController($scope, $rootScope, $routeParams, $location, ThLog,
|
||||||
localStorageService, ThRepositoryModel, thPinboard,
|
localStorageService, ThRepositoryModel, thPinboard,
|
||||||
ThExclusionProfileModel, thEvents) {
|
thClassificationTypes, thEvents, $interval, ThExclusionProfileModel) {
|
||||||
$scope.query="";
|
|
||||||
$scope.statusError = function(msg) {
|
var $log = new ThLog("MainCtrl");
|
||||||
$rootScope.statusMsg = msg;
|
|
||||||
$rootScope.statusColor = "red";
|
thClassificationTypes.load();
|
||||||
};
|
ThRepositoryModel.load();
|
||||||
$scope.statusSuccess = function(msg) {
|
|
||||||
$rootScope.statusMsg = msg;
|
|
||||||
$rootScope.statusColor = "green";
|
|
||||||
};
|
|
||||||
$scope.clearJob = function() {
|
$scope.clearJob = function() {
|
||||||
// setting the selectedJob to null hides the bottom panel
|
// setting the selectedJob to null hides the bottom panel
|
||||||
$rootScope.selectedJob = null;
|
$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
|
// detect window width and put it in scope so items can react to
|
||||||
// a narrow/wide window
|
// a narrow/wide window
|
||||||
|
@ -30,6 +57,45 @@ treeherder.controller('MainCtrl',
|
||||||
$scope.$apply();
|
$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
|
// give the page a way to determine which nav toolbar to show
|
||||||
$rootScope.$on('$locationChangeSuccess', function(ev,newUrl) {
|
$rootScope.$on('$locationChangeSuccess', function(ev,newUrl) {
|
||||||
$rootScope.locationPath = $location.path().replace('/', '');
|
$rootScope.locationPath = $location.path().replace('/', '');
|
||||||
|
@ -37,9 +103,6 @@ treeherder.controller('MainCtrl',
|
||||||
|
|
||||||
$rootScope.urlBasePath = $location.absUrl().split('?')[0];
|
$rootScope.urlBasePath = $location.absUrl().split('?')[0];
|
||||||
|
|
||||||
// the repos the user has chosen to watch
|
|
||||||
$scope.watchedRepos = ThRepositoryModel.watchedRepos;
|
|
||||||
|
|
||||||
$scope.changeRepo = function(repo_name) {
|
$scope.changeRepo = function(repo_name) {
|
||||||
// hide the repo panel if they chose to load one.
|
// hide the repo panel if they chose to load one.
|
||||||
$scope.isRepoPanelShowing = false;
|
$scope.isRepoPanelShowing = false;
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
treeherder.controller('RepositoryPanelCtrl',
|
treeherder.controller('RepositoryPanelCtrl',
|
||||||
function RepositoryPanelCtrl($scope, $rootScope, $routeParams, $location, $log,
|
function RepositoryPanelCtrl($scope, $rootScope, $routeParams, $location, ThLog,
|
||||||
localStorageService, ThRepositoryModel, thSocket) {
|
localStorageService, ThRepositoryModel, thSocket) {
|
||||||
|
var $log = new ThLog(this.constructor.name);
|
||||||
$scope.saveWatchedRepos = function() {
|
|
||||||
ThRepositoryModel.saveWatchedRepos();
|
|
||||||
};
|
|
||||||
|
|
||||||
for (var repo in $scope.watchedRepos) {
|
for (var repo in $scope.watchedRepos) {
|
||||||
if($scope.watchedRepos[repo]){
|
if($scope.watchedRepos[repo]){
|
||||||
|
@ -14,6 +11,9 @@ treeherder.controller('RepositoryPanelCtrl',
|
||||||
$log.debug("subscribing to "+repo+".job_failure");
|
$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 */
|
/* Directives */
|
||||||
treeherder.directive('thCloneJobs', function(
|
treeherder.directive('thCloneJobs', function(
|
||||||
$rootScope, $http, $log, thUrl, thCloneHtml, thServiceDomain,
|
$rootScope, $http, ThLog, thUrl, thCloneHtml, thServiceDomain,
|
||||||
thResultStatusInfo, thEvents, thAggregateIds, thJobFilters,
|
thResultStatusInfo, thEvents, thAggregateIds, thJobFilters,
|
||||||
thResultStatusObject, ThResultSetModel){
|
thResultStatusObject, ThResultSetModel){
|
||||||
|
|
||||||
var lastJobElSelected = {};
|
var $log = new ThLog("thCloneJobs");
|
||||||
|
|
||||||
|
var lastJobElSelected, lastJobObjSelected;
|
||||||
|
|
||||||
|
var classificationRequired = {
|
||||||
|
"busted":1,
|
||||||
|
"exception":1,
|
||||||
|
"testfailed":1
|
||||||
|
};
|
||||||
|
|
||||||
// CSS classes
|
// CSS classes
|
||||||
var btnCls = 'btn-xs';
|
var btnCls = 'btn-xs';
|
||||||
|
@ -38,15 +46,73 @@ treeherder.directive('thCloneJobs', function(
|
||||||
};
|
};
|
||||||
|
|
||||||
var getHoverText = function(job) {
|
var getHoverText = function(job) {
|
||||||
var duration = Math.round((job.end_timestamp - job.submit_timestamp) / 60);
|
|
||||||
var jobStatus = job.result;
|
var jobStatus = job.result;
|
||||||
if (job.state != "completed") {
|
if (job.state !== "completed") {
|
||||||
jobStatus = job.state;
|
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)){
|
if(!_.isEmpty(lastJobElSelected)){
|
||||||
lastJobElSelected.removeClass(selectedBtnCls);
|
lastJobElSelected.removeClass(selectedBtnCls);
|
||||||
|
@ -61,7 +127,7 @@ treeherder.directive('thCloneJobs', function(
|
||||||
};
|
};
|
||||||
|
|
||||||
var clickJobCb = function(ev, el, job){
|
var clickJobCb = function(ev, el, job){
|
||||||
selectJob(el);
|
setSelectJobStyles(el);
|
||||||
$rootScope.$broadcast(thEvents.jobClick, job);
|
$rootScope.$broadcast(thEvents.jobClick, job);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -82,7 +148,7 @@ treeherder.directive('thCloneJobs', function(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} 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
|
//Set the resultState
|
||||||
resultState = job.result;
|
resultState = job.result;
|
||||||
if (job.state != "completed") {
|
if (job.state !== "completed") {
|
||||||
resultState = job.state;
|
resultState = job.state;
|
||||||
}
|
}
|
||||||
resultState = resultState || 'unknown';
|
resultState = resultState || 'unknown';
|
||||||
|
|
||||||
if(job.job_coalesced_to_guid != null){
|
if(job.job_coalesced_to_guid !== null){
|
||||||
// Don't count or render coalesced jobs
|
// Don't count or render coalesced jobs
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -123,25 +189,31 @@ treeherder.directive('thCloneJobs', function(
|
||||||
//Make sure that filtering doesn't effect the resultset counts
|
//Make sure that filtering doesn't effect the resultset counts
|
||||||
//displayed
|
//displayed
|
||||||
if(thJobFilters.showJob(job, resultStatusFilters) === false){
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
jobsShown++;
|
jobsShown++;
|
||||||
|
|
||||||
|
job.visible = true;
|
||||||
|
|
||||||
hText = getHoverText(job);
|
hText = getHoverText(job);
|
||||||
key = getJobMapKey(job);
|
key = getJobMapKey(job);
|
||||||
|
|
||||||
jobStatus = thResultStatusInfo(resultState);
|
jobStatus = thResultStatusInfo(resultState);
|
||||||
|
|
||||||
jobStatus['key'] = key;
|
jobStatus.key = key;
|
||||||
if(parseInt(job.failure_classification_id) > 1){
|
if(parseInt(job.failure_classification_id, 10) > 1){
|
||||||
jobStatus['value'] = job.job_type_symbol + '*';
|
jobStatus.value = job.job_type_symbol + '*';
|
||||||
}else{
|
}else{
|
||||||
jobStatus['value'] = job.job_type_symbol;
|
jobStatus.value = job.job_type_symbol;
|
||||||
}
|
}
|
||||||
|
|
||||||
jobStatus['title'] = hText;
|
jobStatus.title = hText;
|
||||||
jobStatus['btnClass'] = jobStatus.btnClass;
|
jobStatus.btnClass = jobStatus.btnClass;
|
||||||
|
|
||||||
jobBtn = $( jobBtnInterpolator(jobStatus) );
|
jobBtn = $( jobBtnInterpolator(jobStatus) );
|
||||||
|
|
||||||
|
@ -184,6 +256,7 @@ treeherder.directive('thCloneJobs', function(
|
||||||
}
|
}
|
||||||
|
|
||||||
lastJobElSelected = el;
|
lastJobElSelected = el;
|
||||||
|
lastJobObjSelected = job;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -203,14 +276,14 @@ treeherder.directive('thCloneJobs', function(
|
||||||
|
|
||||||
revision = resultset.revisions[i];
|
revision = resultset.revisions[i];
|
||||||
|
|
||||||
revision['urlBasePath'] = $rootScope.urlBasePath;
|
revision.urlBasePath = $rootScope.urlBasePath;
|
||||||
revision['currentRepo'] = $rootScope.currentRepo;
|
revision.currentRepo = $rootScope.currentRepo;
|
||||||
|
|
||||||
userTokens = revision.author.split(/[<>]+/);
|
userTokens = revision.author.split(/[<>]+/);
|
||||||
if (userTokens.length > 1) {
|
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);
|
revisionHtml = revisionInterpolator(revision);
|
||||||
ulEl.append(revisionHtml);
|
ulEl.append(revisionHtml);
|
||||||
|
@ -229,7 +302,7 @@ treeherder.directive('thCloneJobs', function(
|
||||||
var rowEl = revisionsEl.parent();
|
var rowEl = revisionsEl.parent();
|
||||||
rowEl.css('display', 'block');
|
rowEl.css('display', 'block');
|
||||||
|
|
||||||
if(revElDisplayState != 'block'){
|
if(revElDisplayState !== 'block'){
|
||||||
|
|
||||||
if(jobsElDisplayState === 'block'){
|
if(jobsElDisplayState === 'block'){
|
||||||
toggleRevisionsSpanOnWithJobs(revisionsEl);
|
toggleRevisionsSpanOnWithJobs(revisionsEl);
|
||||||
|
@ -264,7 +337,7 @@ treeherder.directive('thCloneJobs', function(
|
||||||
var rowEl = revisionsEl.parent();
|
var rowEl = revisionsEl.parent();
|
||||||
rowEl.css('display', 'block');
|
rowEl.css('display', 'block');
|
||||||
|
|
||||||
if(jobsElDisplayState != 'block'){
|
if(jobsElDisplayState !== 'block'){
|
||||||
|
|
||||||
if(revElDisplayState === 'block'){
|
if(revElDisplayState === 'block'){
|
||||||
toggleJobsSpanOnWithRevisions(jobsEl);
|
toggleJobsSpanOnWithRevisions(jobsEl);
|
||||||
|
@ -343,7 +416,7 @@ treeherder.directive('thCloneJobs', function(
|
||||||
jgObj = jobGroups[i];
|
jgObj = jobGroups[i];
|
||||||
|
|
||||||
jobsShown = 0;
|
jobsShown = 0;
|
||||||
if(jgObj.symbol != '?'){
|
if(jgObj.symbol !== '?'){
|
||||||
// Job group detected, add job group symbols
|
// Job group detected, add job group symbols
|
||||||
jobGroup = $( jobGroupInterpolator(jobGroups[i]) );
|
jobGroup = $( jobGroupInterpolator(jobGroups[i]) );
|
||||||
|
|
||||||
|
@ -486,7 +559,7 @@ treeherder.directive('thCloneJobs', function(
|
||||||
var jobCounts = thResultStatusObject.getResultStatusObject();
|
var jobCounts = thResultStatusObject.getResultStatusObject();
|
||||||
|
|
||||||
var statusKeys = _.keys(jobCounts);
|
var statusKeys = _.keys(jobCounts);
|
||||||
jobCounts['total'] = 0;
|
jobCounts.total = 0;
|
||||||
|
|
||||||
resultsetId = resultSets[i].id;
|
resultsetId = resultSets[i].id;
|
||||||
|
|
||||||
|
@ -511,7 +584,7 @@ treeherder.directive('thCloneJobs', function(
|
||||||
for(k=0; k<statusKeys.length; k++){
|
for(k=0; k<statusKeys.length; k++){
|
||||||
jobStatus = statusKeys[k];
|
jobStatus = statusKeys[k];
|
||||||
jobCounts[jobStatus] += statusPerPlatform[jobStatus];
|
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){
|
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
|
//Confirm we are the correct result set
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -585,13 +658,119 @@ treeherder.directive('thCloneJobs', function(
|
||||||
}, this);
|
}, this);
|
||||||
};
|
};
|
||||||
|
|
||||||
var linker = function(scope, element, attrs){
|
var getNextUnclassifiedFailure = function(currentJob){
|
||||||
|
|
||||||
//Remove any jquery on() bindings
|
var resultsets = ThResultSetModel.getResultSetsArray($rootScope.repoName);
|
||||||
element.off();
|
|
||||||
|
|
||||||
//Register events callback
|
var startWatch = false;
|
||||||
element.on('mousedown', _.bind(jobMouseDown, scope));
|
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
|
//Register rootScope custom event listeners that require
|
||||||
//access to the anguler level resultset scope
|
//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
|
//Clone the target html
|
||||||
var resultsetAggregateId = thAggregateIds.getResultsetTableId(
|
var resultsetAggregateId = thAggregateIds.getResultsetTableId(
|
||||||
$rootScope.repoName, scope.resultset.id, scope.resultset.revision
|
$rootScope.repoName, scope.resultset.id, scope.resultset.revision
|
||||||
|
@ -730,557 +922,11 @@ treeherder.directive('thCloneJobs', function(
|
||||||
}
|
}
|
||||||
|
|
||||||
element.append(targetEl);
|
element.append(targetEl);
|
||||||
}
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
link:linker,
|
link:linker,
|
||||||
replace:true
|
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.
|
// if it's not found in Config.js, then return it unchanged.
|
||||||
return name;
|
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
|
// an instance method to delete a ThBugJobMap object
|
||||||
ThBugJobMapModel.prototype.delete = function(){
|
ThBugJobMapModel.prototype.delete = function(){
|
||||||
var pk = this.job_id+"-"+this.bug_id;
|
var pk = this.job_id+"-"+this.bug_id;
|
||||||
return $http.delete(ThBugJobMapModel.get_uri()+pk);
|
return $http.delete(ThBugJobMapModel.get_uri()+pk+"/");
|
||||||
};
|
};
|
||||||
|
|
||||||
return ThBugJobMapModel;
|
return ThBugJobMapModel;
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
treeherder.factory('ThJobClassificationModel', function($http, $log, thUrl) {
|
treeherder.factory('ThJobClassificationModel', function($http, ThLog, thUrl) {
|
||||||
// ThJobClassificationModel is the js counterpart of note
|
// ThJobClassificationModel is the js counterpart of note
|
||||||
|
|
||||||
var ThJobClassificationModel = function(data) {
|
var ThJobClassificationModel = function(data) {
|
||||||
|
@ -39,7 +39,7 @@ treeherder.factory('ThJobClassificationModel', function($http, $log, thUrl) {
|
||||||
|
|
||||||
// an instance method to delete a ThJobClassificationModel object
|
// an instance method to delete a ThJobClassificationModel object
|
||||||
ThJobClassificationModel.prototype.delete = function(){
|
ThJobClassificationModel.prototype.delete = function(){
|
||||||
return $http.delete(ThJobClassificationModel.get_uri()+this.id);
|
return $http.delete(ThJobClassificationModel.get_uri()+this.id+"/");
|
||||||
};
|
};
|
||||||
|
|
||||||
return ThJobClassificationModel;
|
return ThJobClassificationModel;
|
|
@ -1,7 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
treeherder.factory('ThJobModel', ['$http', '$log', 'thUrl', function($http, $log, thUrl) {
|
treeherder.factory('ThJobModel', function($http, ThLog, thUrl) {
|
||||||
// ThJobArtifactModel is the js counterpart of job_artifact
|
// ThJobModel is the js counterpart of job
|
||||||
|
|
||||||
var ThJobModel = function(data) {
|
var ThJobModel = function(data) {
|
||||||
// creates a new instance of ThJobArtifactModel
|
// creates a new instance of ThJobArtifactModel
|
||||||
|
@ -26,10 +26,10 @@ treeherder.factory('ThJobModel', ['$http', '$log', 'thUrl', function($http, $log
|
||||||
|
|
||||||
ThJobModel.get = function(pk) {
|
ThJobModel.get = function(pk) {
|
||||||
// a static method to retrieve a single instance of ThJobModel
|
// 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 new ThJobModel(response.data);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return ThJobModel;
|
return ThJobModel;
|
||||||
}]);
|
});
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'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
|
// ThJobArtifactModel is the js counterpart of job_artifact
|
||||||
|
|
||||||
var ThJobArtifactModel = function(data) {
|
var ThJobArtifactModel = function(data) {
|
||||||
|
@ -32,4 +32,4 @@ treeherder.factory('ThJobArtifactModel', ['$http', '$log', 'thUrl', function($ht
|
||||||
};
|
};
|
||||||
|
|
||||||
return ThJobArtifactModel;
|
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';
|
'use strict';
|
||||||
|
|
||||||
treeherder.factory('ThResultSetModel',
|
treeherder.factory('ThResultSetModel',
|
||||||
['$log', '$rootScope', 'thResultSets', 'thSocket',
|
['$rootScope', 'thResultSets', 'thSocket',
|
||||||
'ThJobModel', 'thEvents', 'thAggregateIds',
|
'ThJobModel', 'thEvents', 'thAggregateIds', 'ThLog',
|
||||||
function($log, $rootScope, thResultSets, thSocket,
|
function($rootScope, thResultSets, thSocket,
|
||||||
ThJobModel, thEvents, thAggregateIds) {
|
ThJobModel, thEvents, thAggregateIds, ThLog) {
|
||||||
|
|
||||||
|
var $log = new ThLog("ThResultSetModel");
|
||||||
|
|
||||||
/******
|
/******
|
||||||
* Handle updating the resultset datamodel based on a queue of jobs
|
* Handle updating the resultset datamodel based on a queue of jobs
|
||||||
|
@ -112,7 +114,7 @@ treeherder.factory('ThResultSetModel',
|
||||||
|
|
||||||
var getPlatformKey = function(name, option){
|
var getPlatformKey = function(name, option){
|
||||||
var key = name;
|
var key = name;
|
||||||
if(option != undefined){
|
if(option !== undefined){
|
||||||
key += option;
|
key += option;
|
||||||
}
|
}
|
||||||
return key;
|
return key;
|
||||||
|
@ -144,6 +146,13 @@ treeherder.factory('ThResultSetModel',
|
||||||
repositories[repoName].rsMapOldestTimestamp = rs_obj.push_timestamp;
|
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
|
// platforms
|
||||||
for (var pl_i = 0; pl_i < rs_obj.platforms.length; pl_i++) {
|
for (var pl_i = 0; pl_i < rs_obj.platforms.length; pl_i++) {
|
||||||
var pl_obj = rs_obj.platforms[pl_i];
|
var pl_obj = rs_obj.platforms[pl_i];
|
||||||
|
@ -191,10 +200,9 @@ treeherder.factory('ThResultSetModel',
|
||||||
|
|
||||||
repositories[repoName].resultSets.sort(rsCompare);
|
repositories[repoName].resultSets.sort(rsCompare);
|
||||||
|
|
||||||
$log.debug("oldest job: " + repositories[repoName].jobMapOldestId);
|
$log.debug("oldest job: ", repositories[repoName].jobMapOldestId);
|
||||||
$log.debug("oldest result set: " + repositories[repoName].rsMapOldestTimestamp);
|
$log.debug("oldest result set: ", repositories[repoName].rsMapOldestTimestamp);
|
||||||
$log.debug("done mapping:");
|
$log.debug("done mapping:", repositories[repoName].rsMap);
|
||||||
$log.debug(repositories[repoName].rsMap);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -226,7 +234,7 @@ treeherder.factory('ThResultSetModel',
|
||||||
|
|
||||||
var pl_obj = {
|
var pl_obj = {
|
||||||
name: newJob.platform,
|
name: newJob.platform,
|
||||||
option: newJob.platform_opt,
|
option: newJob.platform_option,
|
||||||
groups: []
|
groups: []
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -308,28 +316,31 @@ treeherder.factory('ThResultSetModel',
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jobFetchList.length > 0) {
|
if (jobFetchList.length > 0) {
|
||||||
$log.debug("processing jobFetchList");
|
$log.debug("processing jobFetchList", jobFetchList);
|
||||||
$log.debug(jobFetchList);
|
|
||||||
|
|
||||||
// make an ajax call to get the job details
|
// make an ajax call to get the job details
|
||||||
|
fetchJobs(repoName, jobFetchList);
|
||||||
ThJobModel.get_list({
|
|
||||||
id__in: jobFetchList.join()
|
|
||||||
}).then(
|
|
||||||
_.bind(updateJobs, $rootScope, repoName),
|
|
||||||
function(data) {
|
|
||||||
$log.error("Error fetching jobUpdateQueue: " + data);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* 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 aggregateJobPlatform = function(repoName, job, platformData){
|
||||||
|
|
||||||
var resultsetId, platformName, platformOption, platformAggregateId,
|
var resultsetId, platformName, platformOption, platformAggregateId,
|
||||||
platformKey, jobUpdated, resultsetAggregateId, revision,
|
platformKey, jobUpdated, resultsetAggregateId, revision,
|
||||||
jobGroups;
|
jobGroups;
|
||||||
|
|
||||||
console.log('aggregating job platform');
|
|
||||||
console.log(job);
|
|
||||||
jobUpdated = updateJob(repoName, job);
|
jobUpdated = updateJob(repoName, job);
|
||||||
|
|
||||||
//the job was not updated or added to the model, don't include it
|
//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;
|
resultsetId = job.result_set_id;
|
||||||
platformName = job.platform;
|
platformName = job.platform;
|
||||||
platformOption = job.platform_opt;
|
platformOption = job.platform_option;
|
||||||
|
|
||||||
if(_.isEmpty(repositories[repoName].rsMap[ resultsetId ])){
|
if(_.isEmpty(repositories[repoName].rsMap[ resultsetId ])){
|
||||||
//We don't have this resultset
|
//We don't have this resultset
|
||||||
|
@ -351,7 +362,7 @@ treeherder.factory('ThResultSetModel',
|
||||||
repoName,
|
repoName,
|
||||||
job.result_set_id,
|
job.result_set_id,
|
||||||
job.platform,
|
job.platform,
|
||||||
job.platform_opt
|
job.platform_option
|
||||||
);
|
);
|
||||||
|
|
||||||
if(!platformData[platformAggregateId]){
|
if(!platformData[platformAggregateId]){
|
||||||
|
@ -366,6 +377,7 @@ treeherder.factory('ThResultSetModel',
|
||||||
|
|
||||||
platformKey = getPlatformKey(platformName, platformOption);
|
platformKey = getPlatformKey(platformName, platformOption);
|
||||||
|
|
||||||
|
$log.debug("aggregateJobPlatform", repoName, resultsetId, platformKey, repositories);
|
||||||
jobGroups = repositories[repoName].rsMap[resultsetId].platforms[platformKey].pl_obj.groups;
|
jobGroups = repositories[repoName].rsMap[resultsetId].platforms[platformKey].pl_obj.groups;
|
||||||
platformData[platformAggregateId] = {
|
platformData[platformAggregateId] = {
|
||||||
platformName:platformName,
|
platformName:platformName,
|
||||||
|
@ -389,7 +401,7 @@ treeherder.factory('ThResultSetModel',
|
||||||
*/
|
*/
|
||||||
var updateJobs = function(repoName, jobList) {
|
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 = {};
|
var platformData = {};
|
||||||
|
|
||||||
|
@ -450,11 +462,11 @@ treeherder.factory('ThResultSetModel',
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loadedJob) {
|
if (loadedJob) {
|
||||||
$log.debug("updating existing job");
|
$log.debug("updating existing job", loadedJob, newJob);
|
||||||
_.extend(loadedJob, newJob);
|
_.extend(loadedJob, newJob);
|
||||||
} else {
|
} else {
|
||||||
// this job is not yet in the model or the map. add it to both
|
// 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);
|
var grpMapElement = getOrCreateGroup(repoName, newJob);
|
||||||
|
|
||||||
|
@ -486,7 +498,7 @@ treeherder.factory('ThResultSetModel',
|
||||||
var added = [];
|
var added = [];
|
||||||
for (var i = data.length - 1; i > -1; i--) {
|
for (var i = data.length - 1; i > -1; i--) {
|
||||||
if (data[i].push_timestamp > repositories[repoName].rsMapOldestTimestamp) {
|
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]);
|
repositories[repoName].resultSets.push(data[i]);
|
||||||
added.push(data[i]);
|
added.push(data[i]);
|
||||||
} else {
|
} else {
|
||||||
|
@ -502,7 +514,7 @@ treeherder.factory('ThResultSetModel',
|
||||||
var appendResultSets = function(repoName, data) {
|
var appendResultSets = function(repoName, data) {
|
||||||
|
|
||||||
if(data.length > 0){
|
if(data.length > 0){
|
||||||
repositories[repoName].rsOffsetId = data[ data.length - 1 ].id;
|
|
||||||
|
|
||||||
Array.prototype.push.apply(
|
Array.prototype.push.apply(
|
||||||
repositories[repoName].resultSets, data
|
repositories[repoName].resultSets, data
|
||||||
|
@ -556,7 +568,7 @@ treeherder.factory('ThResultSetModel',
|
||||||
*/
|
*/
|
||||||
if(resultsetList.length > 0){
|
if(resultsetList.length > 0){
|
||||||
repositories[repoName].loadingStatus.prepending = true;
|
repositories[repoName].loadingStatus.prepending = true;
|
||||||
thResultSets.getResultSets(0, resultsetlist.length, resultsetlist).
|
thResultSets.getResultSets(0, resultsetList.length, resultsetList).
|
||||||
success( _.bind(prependResultSets, $rootScope, repoName) );
|
success( _.bind(prependResultSets, $rootScope, repoName) );
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -596,6 +608,8 @@ treeherder.factory('ThResultSetModel',
|
||||||
|
|
||||||
fetchResultSets: fetchResultSets,
|
fetchResultSets: fetchResultSets,
|
||||||
|
|
||||||
|
fetchJobs: fetchJobs,
|
||||||
|
|
||||||
aggregateJobPlatform: aggregateJobPlatform
|
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() {
|
treeherder.provider('thResultStatusList', function() {
|
||||||
this.$get = function() {
|
this.$get = function() {
|
||||||
return ['success', 'testfailed', 'busted', 'exception', 'retry', 'running', 'pending'];
|
return ['success', 'testfailed', 'busted', 'exception', 'retry', 'running', 'pending'];
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
treeherder.provider('thResultStatus', function() {
|
treeherder.provider('thResultStatus', function() {
|
||||||
this.$get = function() {
|
this.$get = function() {
|
||||||
return function(job) {
|
return function(job) {
|
||||||
|
@ -57,6 +27,7 @@ treeherder.provider('thResultStatus', function() {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
treeherder.provider('thResultStatusObject', function() {
|
treeherder.provider('thResultStatusObject', function() {
|
||||||
var getResultStatusObject = function(){
|
var getResultStatusObject = function(){
|
||||||
return {
|
return {
|
||||||
|
@ -203,6 +174,9 @@ treeherder.provider('thEvents', function() {
|
||||||
// fired (surprisingly) when a job is clicked
|
// fired (surprisingly) when a job is clicked
|
||||||
jobClick: "job-click-EVT",
|
jobClick: "job-click-EVT",
|
||||||
|
|
||||||
|
// fired when the job details are loaded
|
||||||
|
jobDetailLoaded: "job-detail-loaded-EVT",
|
||||||
|
|
||||||
// fired when a job is shift-clicked
|
// fired when a job is shift-clicked
|
||||||
jobPin: "job-pin-EVT",
|
jobPin: "job-pin-EVT",
|
||||||
|
|
||||||
|
@ -230,9 +204,21 @@ treeherder.provider('thEvents', function() {
|
||||||
|
|
||||||
toggleJobs: "toggle-jobs-EVT",
|
toggleJobs: "toggle-jobs-EVT",
|
||||||
|
|
||||||
|
toggleUnclassifiedFailures: "toggle-unclassified-failures-EVT",
|
||||||
|
|
||||||
|
selectNextUnclassifiedFailure: "next-unclassified-failure-EVT",
|
||||||
|
|
||||||
|
selectPreviousUnclassifiedFailure: "previous-unclassified-failure-EVT",
|
||||||
|
|
||||||
searchPage: "search-page-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
|
* 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.
|
* 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
|
* 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.
|
* means it must have a value set, ``false`` means it must be null.
|
||||||
*/
|
*/
|
||||||
var checkFilter = function(field, job, resultStatusList) {
|
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) {
|
if (field === api.resultStatus) {
|
||||||
|
// resultStatus is a special case that spans two job fields
|
||||||
var filterList = resultStatusList || filters[field].values;
|
var filterList = resultStatusList || filters[field].values;
|
||||||
return _.contains(filterList, job.result) ||
|
return _.contains(filterList, job.result) ||
|
||||||
_.contains(filterList, job.state);
|
_.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 {
|
} else {
|
||||||
var jobFieldValue = getJobFieldValue(job, field);
|
var jobFieldValue = getJobFieldValue(job, field);
|
||||||
if (_.isUndefined(jobFieldValue)) {
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$log.debug(field + ": " + JSON.stringify(job));
|
$log.debug("jobField filter", field, job);
|
||||||
switch (filters[field].matchType) {
|
switch (filters[field].matchType) {
|
||||||
case api.matchType.isnull:
|
case api.matchType.isnull:
|
||||||
jobFieldValue = !_.isNull(jobFieldValue);
|
jobFieldValue = !_.isNull(jobFieldValue);
|
||||||
|
@ -117,7 +131,7 @@ treeherder.factory('thJobFilters', function(thResultStatusList, $log, $rootScope
|
||||||
value = value.toLowerCase();
|
value = value.toLowerCase();
|
||||||
}
|
}
|
||||||
if (filters.hasOwnProperty(field)) {
|
if (filters.hasOwnProperty(field)) {
|
||||||
if (!_.contains(filters[field], value)) {
|
if (!_.contains(filters[field].values, value)) {
|
||||||
filters[field].values.push(value);
|
filters[field].values.push(value);
|
||||||
filters[field].matchType = matchType;
|
filters[field].matchType = matchType;
|
||||||
}
|
}
|
||||||
|
@ -128,8 +142,11 @@ treeherder.factory('thJobFilters', function(thResultStatusList, $log, $rootScope
|
||||||
removeWhenEmpty: true
|
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) {
|
removeFilter: function(field, value) {
|
||||||
if (filters.hasOwnProperty(field)) {
|
if (filters.hasOwnProperty(field)) {
|
||||||
|
@ -139,7 +156,7 @@ treeherder.factory('thJobFilters', function(thResultStatusList, $log, $rootScope
|
||||||
}
|
}
|
||||||
var idx = filters[field].values.indexOf(value);
|
var idx = filters[field].values.indexOf(value);
|
||||||
if(idx > -1) {
|
if(idx > -1) {
|
||||||
$log.debug("removing " + value);
|
$log.debug("removing ", value);
|
||||||
filters[field].values.splice(idx, 1);
|
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) {
|
if (filters[field].removeWhenEmpty && filters[field].values.length === 0) {
|
||||||
delete filters[field];
|
delete filters[field];
|
||||||
}
|
}
|
||||||
$log.debug(filters);
|
|
||||||
|
filterKeys = _.keys(filters);
|
||||||
|
$log.debug("filters", filters);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* used mostly for resultStatus doing group toggles
|
* 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
|
* @param add - true if adding, false if removing
|
||||||
*/
|
*/
|
||||||
toggleFilters: function(field, values, add) {
|
toggleFilters: function(field, values, add) {
|
||||||
$log.debug("toggling: " + add);
|
$log.debug("toggling: ", add);
|
||||||
var action = add? api.addFilter: api.removeFilter;
|
var action = add? api.addFilter: api.removeFilter;
|
||||||
for (var i = 0; i < values.length; i++) {
|
for (var i = 0; i < values.length; i++) {
|
||||||
action(field, values[i]);
|
action(field, values[i]);
|
||||||
|
@ -182,7 +201,7 @@ treeherder.factory('thJobFilters', function(thResultStatusList, $log, $rootScope
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if($rootScope.searchQuery != ""){
|
if(typeof $rootScope.searchQuery === 'string'){
|
||||||
//Confirm job matches search query
|
//Confirm job matches search query
|
||||||
if(job.searchableStr.toLowerCase().indexOf(
|
if(job.searchableStr.toLowerCase().indexOf(
|
||||||
$rootScope.searchQuery.toLowerCase()
|
$rootScope.searchQuery.toLowerCase()
|
||||||
|
@ -206,6 +225,31 @@ treeherder.factory('thJobFilters', function(thResultStatusList, $log, $rootScope
|
||||||
getFilters: function() {
|
getFilters: function() {
|
||||||
return filters;
|
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
|
// CONSTANTS
|
||||||
failure_classification_id: "failure_classification_id",
|
failure_classification_id: "failure_classification_id",
|
||||||
|
@ -213,7 +257,8 @@ treeherder.factory('thJobFilters', function(thResultStatusList, $log, $rootScope
|
||||||
matchType: {
|
matchType: {
|
||||||
exactstr: 0,
|
exactstr: 0,
|
||||||
substr: 1,
|
substr: 1,
|
||||||
isnull: 2
|
isnull: 2,
|
||||||
|
bool: 3
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -225,7 +270,7 @@ treeherder.factory('thJobFilters', function(thResultStatusList, $log, $rootScope
|
||||||
removeWhenEmpty: false
|
removeWhenEmpty: false
|
||||||
},
|
},
|
||||||
failure_classification_id: {
|
failure_classification_id: {
|
||||||
matchType: api.matchType.isnull,
|
matchType: api.matchType.bool,
|
||||||
values: [true, false],
|
values: [true, false],
|
||||||
removeWhenEmpty: 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';
|
'use strict';
|
||||||
|
|
||||||
/* Services */
|
/* Services */
|
||||||
treeherder.factory('thUrl',['$rootScope', 'thServiceDomain', '$log', function($rootScope, thServiceDomain, $log) {
|
treeherder.factory('thUrl',['$rootScope', 'thServiceDomain', 'ThLog', function($rootScope, thServiceDomain, ThLog) {
|
||||||
|
|
||||||
var thUrl = {
|
var thUrl = {
|
||||||
getRootUrl: function(uri) {
|
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());
|
var socket = io.connect(thUrl.getSocketEventUrl());
|
||||||
socket.on('connect', function () {
|
socket.on('connect', function () {
|
||||||
$log.debug('socketio connected');
|
$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
|
* BrowserId is a wrapper for the persona authentication service
|
||||||
|
@ -180,9 +182,11 @@ treeherder.factory('BrowserId', function($http, $q, $log, thServiceDomain){
|
||||||
return browserid;
|
return browserid;
|
||||||
});
|
});
|
||||||
|
|
||||||
treeherder.factory('thNotify', function($timeout, $log){
|
treeherder.factory('thNotify', function($timeout, ThLog){
|
||||||
//a growl-like notification system
|
//a growl-like notification system
|
||||||
|
|
||||||
|
var $log = new ThLog("thNotify");
|
||||||
|
|
||||||
var thNotify = {
|
var thNotify = {
|
||||||
// message queue
|
// message queue
|
||||||
notifications: [],
|
notifications: [],
|
||||||
|
@ -194,8 +198,7 @@ treeherder.factory('thNotify', function($timeout, $log){
|
||||||
* after a while or not
|
* after a while or not
|
||||||
*/
|
*/
|
||||||
send: function(message, severity, sticky){
|
send: function(message, severity, sticky){
|
||||||
$log.debug("received message");
|
$log.debug("received message", message);
|
||||||
$log.debug(message);
|
|
||||||
var severity = severity || 'info';
|
var severity = severity || 'info';
|
||||||
var sticky = sticky || false;
|
var sticky = sticky || false;
|
||||||
thNotify.notifications.push({
|
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',
|
treeherder.factory('thPinboard',
|
||||||
function($http, thUrl, ThJobClassificationModel, $rootScope,
|
function($http, thUrl, ThJobClassificationModel, $rootScope,
|
||||||
thEvents, ThBugJobMapModel, thNotify) {
|
thEvents, ThBugJobMapModel, thNotify, ThLog) {
|
||||||
|
|
||||||
|
var $log = new ThLog("thPinboard");
|
||||||
|
|
||||||
var pinnedJobs = {};
|
var pinnedJobs = {};
|
||||||
var relatedBugs = {};
|
var relatedBugs = {};
|
||||||
|
@ -10,15 +12,18 @@ treeherder.factory('thPinboard',
|
||||||
var saveClassification = function(job) {
|
var saveClassification = function(job) {
|
||||||
var classification = new ThJobClassificationModel(this);
|
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.job_id = job.id;
|
||||||
classification.create().
|
classification.create().
|
||||||
success(function(data) {
|
success(function(data) {
|
||||||
thNotify.send("classification saved for " + job.platform + ": " + job.job_type_name, "success");
|
thNotify.send("classification saved for " + job.platform + ": " + job.job_type_name, "success");
|
||||||
}).error(function(data) {
|
}).error(function(data) {
|
||||||
thNotify.send("error saving classification for " + job.platform + ": " + job.job_type_name, "danger");
|
thNotify.send("error saving classification for " + job.platform + ": " + job.job_type_name, "danger");
|
||||||
});
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var saveBugs = function(job) {
|
var saveBugs = function(job) {
|
||||||
|
@ -40,8 +45,12 @@ treeherder.factory('thPinboard',
|
||||||
|
|
||||||
var api = {
|
var api = {
|
||||||
pinJob: function(job) {
|
pinJob: function(job) {
|
||||||
pinnedJobs[job.id] = job;
|
if (api.spaceRemaining() > 0) {
|
||||||
api.count.numPinnedJobs = _.size(pinnedJobs);
|
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) {
|
unPinJob: function(id) {
|
||||||
|
@ -59,8 +68,11 @@ treeherder.factory('thPinboard',
|
||||||
},
|
},
|
||||||
|
|
||||||
addBug: function(bug) {
|
addBug: function(bug) {
|
||||||
|
$log.debug("adding bug ", bug);
|
||||||
relatedBugs[bug.id] = bug;
|
relatedBugs[bug.id] = bug;
|
||||||
api.count.numRelatedBugs = _.size(relatedBugs);
|
api.count.numRelatedBugs = _.size(relatedBugs);
|
||||||
|
$log.debug("related bugs", relatedBugs);
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
removeBug: function(id) {
|
removeBug: function(id) {
|
||||||
|
@ -105,23 +117,26 @@ treeherder.factory('thPinboard',
|
||||||
|
|
||||||
// save bug associations only on all pinned jobs
|
// save bug associations only on all pinned jobs
|
||||||
saveBugsOnly: function() {
|
saveBugsOnly: function() {
|
||||||
if (!_.size(relatedBugs)) {
|
_.each(pinnedJobs, saveBugs);
|
||||||
thNotify.send("no bug associations to save");
|
$rootScope.$broadcast(thEvents.bugsAssociated, {jobs: pinnedJobs});
|
||||||
} else {
|
|
||||||
_.each(pinnedJobs, saveBugs);
|
|
||||||
$rootScope.$broadcast(thEvents.bugsAssociated, {jobs: pinnedJobs});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
hasPinnedJobs: function() {
|
hasPinnedJobs: function() {
|
||||||
return !_.isEmpty(pinnedJobs);
|
return !_.isEmpty(pinnedJobs);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
spaceRemaining: function() {
|
||||||
|
return api.maxNumPinned - api.count.numPinnedJobs;
|
||||||
|
},
|
||||||
|
|
||||||
pinnedJobs: pinnedJobs,
|
pinnedJobs: pinnedJobs,
|
||||||
relatedBugs: relatedBugs,
|
relatedBugs: relatedBugs,
|
||||||
count: {
|
count: {
|
||||||
numPinnedJobs: 0,
|
numPinnedJobs: 0,
|
||||||
numRelatedBugs: 0
|
numRelatedBugs: 0
|
||||||
}
|
},
|
||||||
|
// not sure what this should be, but we need some limit, I think.
|
||||||
|
maxNumPinned: 500
|
||||||
};
|
};
|
||||||
|
|
||||||
return api;
|
return api;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
treeherder.factory('thResultSets',
|
treeherder.factory('thResultSets',
|
||||||
['$http', '$location', 'thUrl', 'thServiceDomain',
|
|
||||||
function($http, $location, thUrl, thServiceDomain) {
|
function($http, $location, thUrl, thServiceDomain) {
|
||||||
|
|
||||||
// get the resultsets for this repo
|
// get the resultsets for this repo
|
||||||
|
@ -42,4 +41,4 @@ treeherder.factory('thResultSets',
|
||||||
return $http.get(thServiceDomain + uri, {params: {format: "json"}});
|
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="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<dl class="dl-horizontal">
|
<table class="table table-condensed">
|
||||||
<span ng-repeat="(label, value) in artifact.header">
|
<tr ng-repeat="(label, value) in artifact.header">
|
||||||
<dt class="label label-info">{{label}}</dt>
|
<th>{{label}}</th><td>{{value}}</td>
|
||||||
<dd class="">{{value}}</dd>
|
</tr>
|
||||||
</span>
|
</table>
|
||||||
</dl>
|
<p>Select one of these steps to see more details:</p>
|
||||||
<div class="row" >
|
<div ng-repeat="step in artifact.step_data.steps"
|
||||||
<div ng-repeat="step in artifact.step_data.steps"
|
ng-class="{'btn-warning': (step.error_count > 0), 'btn-success': (step.error_count == 0)}"
|
||||||
ng-class="{'btn-warning': (step.error_count > 0), 'btn-success': (step.error_count == 0)}"
|
ng-click="displayLog(step)"
|
||||||
ng-click="displayLog(step)"
|
class="btn btn-block logviewer-step clearfix">
|
||||||
class="btn btn-block clearfix">
|
<span class="pull-left clearfix">{{step.order+1}}. {{step.name}}</span>
|
||||||
<span class="pull-left clearfix">{{step.order+1}}. {{step.name}}</span>
|
<span ng-init="time=formatTime(step.duration)"
|
||||||
<span ng-init="time=formatTime(step.duration)"
|
ng-mouseover="time=displayTime(step.started, step.finished)"
|
||||||
ng-mouseover="time=displayTime(step.started, step.finished)"
|
ng-mouseleave="time=formatTime(step.duration)"
|
||||||
ng-mouseleave="time=formatTime(step.duration)"
|
class="pull-right clearfix">{{time}}</span>
|
||||||
class="pull-right clearfix">{{time}}</span>
|
<div ng-switch on="(step.error_count > 0)">
|
||||||
<div ng-switch on="(step.error_count > 0)">
|
<p ng-switch-when="true" class="">
|
||||||
<p ng-switch-when="true" class="">
|
<div ng-repeat="error in step.errors"
|
||||||
<div ng-repeat="error in step.errors"
|
ng-mouseover="check=(step==displayedStep)"
|
||||||
ng-mouseover="check=(step==displayedStep)"
|
ng-mouseleave="check=false"
|
||||||
ng-mouseleave="check=false"
|
ng-class="{'lv-line-highlight': check}"
|
||||||
ng-class="{'lv-line-highlight': check}"
|
ng-click="scrollTo(step, error.linenumber);
|
||||||
ng-click="scrollTo(step, error.linenumber);
|
$event.stopPropagation()"
|
||||||
$event.stopPropagation()"
|
class="text-left pull-left lv-error-line">
|
||||||
class="text-left pull-left lv-error-line">
|
<span class="label label-default lv-line-no">{{error.linenumber}}</span>
|
||||||
<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>
|
||||||
<span>{{error.line}}</span>
|
</div>
|
||||||
</div>
|
</p>
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 lv-log-container container well"
|
<div class="col-md-6 lv-log-container container well"
|
||||||
id="lv-log-container">
|
id="lv-log-container">
|
||||||
<div ng-repeat="lv_line in displayedStep.logPieces"
|
<div ng-repeat="lv_line in displayedStep.logPieces"
|
||||||
|
@ -65,7 +63,8 @@
|
||||||
<script src="js/config/local.conf.js"></script>
|
<script src="js/config/local.conf.js"></script>
|
||||||
<script src="js/app.js"></script>
|
<script src="js/app.js"></script>
|
||||||
<script src="js/services/main.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/providers.js"></script>
|
||||||
<script src="js/controllers/logviewer.js"></script>
|
<script src="js/controllers/logviewer.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<ul class="dropdown-menu pull-left">
|
<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://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="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>
|
<li class="hidden"><a href="" ng-disabled="true">Cancel All Jobs</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -1,7 +1,16 @@
|
||||||
<div id="filter"
|
<div id="filter"
|
||||||
class="th-top-nav-options-panel model-body"
|
class="th-top-nav-options-panel model-body"
|
||||||
ng-controller="StatusFilterPanelCtrl">
|
ng-controller="FilterPanelCtrl">
|
||||||
<div class="nav-panel-help-text">Specify which jobs to show based on this filter criteria.</div>
|
<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 -->
|
<!-- result status filters -->
|
||||||
<span ng-repeat="group in filterGroups"
|
<span ng-repeat="group in filterGroups"
|
|
@ -23,44 +23,43 @@
|
||||||
ng-show="isSheriffPanelShowing"></i>
|
ng-show="isSheriffPanelShowing"></i>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="btn btn-view-nav"
|
<span class="pull-right">
|
||||||
ng-class="{'active': (isRepoPanelShowing)}"
|
<span class="btn btn-view-nav"
|
||||||
ng-click="isRepoPanelShowing=!isRepoPanelShowing"><span>repos</span>
|
ng-class="{'active': (isRepoPanelShowing)}"
|
||||||
<i class="glyphicon glyphicon-chevron-down lightgray"
|
ng-click="isRepoPanelShowing=!isRepoPanelShowing"><span>Repos</span>
|
||||||
ng-hide="isRepoPanelShowing"></i>
|
<i class="glyphicon glyphicon-chevron-down lightgray"
|
||||||
<i class="glyphicon glyphicon-chevron-up lightgray"
|
ng-hide="isRepoPanelShowing"></i>
|
||||||
ng-show="isRepoPanelShowing"></i>
|
<i class="glyphicon glyphicon-chevron-up lightgray"
|
||||||
</span>
|
ng-show="isRepoPanelShowing"></i>
|
||||||
<span class="btn btn-view-nav"
|
</span>
|
||||||
ng-class="{'active': (isFilterPanelShowing)}"
|
<span class="btn btn-view-nav"
|
||||||
ng-click="isFilterPanelShowing=!isFilterPanelShowing"><span>filters</span>
|
ng-class="{'active': (isFilterPanelShowing)}"
|
||||||
<i class="glyphicon glyphicon-chevron-down lightgray"
|
ng-click="isFilterPanelShowing=!isFilterPanelShowing"><span>Filters</span>
|
||||||
ng-hide="isFilterPanelShowing"></i>
|
<i class="glyphicon glyphicon-chevron-down lightgray"
|
||||||
<i class="glyphicon glyphicon-chevron-up lightgray"
|
ng-hide="isFilterPanelShowing"></i>
|
||||||
ng-show="isFilterPanelShowing"></i>
|
<i class="glyphicon glyphicon-chevron-up lightgray"
|
||||||
</span>
|
ng-show="isFilterPanelShowing"></i>
|
||||||
<a class="btn btn-view-nav" href="help.html" target="_blank">help</a>
|
</span>
|
||||||
<span class="nav-text white th-username">{{user.email}}</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-->
|
<!--TODO: change this condition to enable the settings panel-->
|
||||||
<span ng-show="false" class="btn btn-view-nav"
|
<span ng-show="false" class="btn btn-view-nav"
|
||||||
ng-class="{'active': (isSettingsPanelShowing)}"
|
ng-class="{'active': (isSettingsPanelShowing)}"
|
||||||
ng-click="isSettingsPanelShowing=!isSettingsPanelShowing"><span>Settings</span>
|
ng-click="isSettingsPanelShowing=!isSettingsPanelShowing"><span>Settings</span>
|
||||||
<i class="glyphicon glyphicon-chevron-down lightgray"
|
<i class="glyphicon glyphicon-chevron-down lightgray"
|
||||||
ng-hide="isSettingsPanelShowing"></i>
|
ng-hide="isSettingsPanelShowing"></i>
|
||||||
<i class="glyphicon glyphicon-chevron-up lightgray"
|
<i class="glyphicon glyphicon-chevron-up lightgray"
|
||||||
ng-show="isSettingsPanelShowing"></i>
|
ng-show="isSettingsPanelShowing"></i>
|
||||||
|
</span>
|
||||||
|
<persona-buttons></persona-buttons>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<persona-buttons></persona-buttons>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<ng-include class="watched-repo-navbar" src="'partials/thWatchedRepoPanel.html'" ng-show="locationPath==='jobs'">
|
||||||
|
</ng-include>
|
||||||
</div>
|
</div>
|
||||||
<th-watched-repo-panel ng-show="locationPath==='jobs'"></th-watched-repo-panel>
|
<ng-include src="'partials/thSheriffPanel.html'" ng-show="isSheriffPanelShowing"></ng-include>
|
||||||
<div ng-show="isFilterPanelShowing">
|
<ng-include src="'partials/thFilterPanel.html'" ng-show="isFilterPanelShowing"></ng-include>
|
||||||
<th-status-filter-panel></th-status-filter-panel>
|
<ng-include src="'partials/thRepoPanel.html'" ng-show="isRepoPanelShowing"></ng-include>
|
||||||
</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>
|
|
||||||
</nav>
|
</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"
|
<div id="pinboard-panel"
|
||||||
|
|
||||||
ng-show="hasPinnedJobs()">
|
ng-show="hasPinnedJobs()">
|
||||||
<div class="bottom-panel-title"><i class="glyphicon glyphicon-pushpin"></i> pinboard</div>
|
<div class="bottom-panel-title"><i class="glyphicon glyphicon-pushpin"></i> pinboard</div>
|
||||||
<div class="panel shadowed-panel pinboard-shadowed-panel">
|
<div class="panel shadowed-panel pinboard-shadowed-panel">
|
||||||
|
@ -9,6 +10,7 @@
|
||||||
<div id="pinboard-related-bugs-panel">
|
<div id="pinboard-related-bugs-panel">
|
||||||
<div class="bottom-panel-title"><i class="fa fa-bug"></i> related bugs
|
<div class="bottom-panel-title"><i class="fa fa-bug"></i> related bugs
|
||||||
<a ng-click="toggleEnterBugNumber()"
|
<a ng-click="toggleEnterBugNumber()"
|
||||||
|
class="click-able-icon lightgray"
|
||||||
title="type in a bug number"><i class="fa fa-plus"></i>
|
title="type in a bug number"><i class="fa fa-plus"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,13 +18,13 @@
|
||||||
<form ng-submit="saveEnteredBugNumber()">
|
<form ng-submit="saveEnteredBugNumber()">
|
||||||
<input type="number"
|
<input type="number"
|
||||||
ng-show="enteringBugNumber"
|
ng-show="enteringBugNumber"
|
||||||
ng-model="newEnteredBugNumber"
|
ng-model="$parent.newEnteredBugNumber"
|
||||||
placeholder="enter bug number"
|
placeholder="enter bug number"
|
||||||
numbers-only="numbers-only"
|
numbers-only="numbers-only"
|
||||||
focus-me="focusInput">
|
focus-me="focusInput">
|
||||||
</form>
|
</form>
|
||||||
<span ng-repeat="bug in relatedBugs">
|
<span ng-repeat="bug in relatedBugs">
|
||||||
<th-related-bug></th-related-bug>
|
<th-related-bug-queued></th-related-bug-queued>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="th-repo-group">
|
||||||
<span ng-repeat="repo in group | orderBy:'name'"
|
<span ng-repeat="repo in group | orderBy:'name'"
|
||||||
class="th-repo-group-items">
|
class="th-repo-group-items">
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
ng-model="watchedRepos[repo.name]"
|
ng-checked="watchedRepos[repo.name].isWatched"
|
||||||
ng-change="saveWatchedRepos()">
|
ng-click="toggleRepo(repo.name)">
|
||||||
<a href="" class="repo-link"
|
<a href="" class="repo-link"
|
||||||
ng-click="changeRepo(repo.name)">{{repo.name}}
|
ng-click="changeRepo(repo.name)">{{repo.name}}
|
||||||
</a>
|
</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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</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" >
|
<div class="th-context-navbar watched-repo-navbar clearfix" >
|
||||||
<span ng-repeat="(repo, isVisible) in watchedRepos"
|
<th-watched-repo ng-repeat="(name, repoData) in watchedRepos"></th-watched-repo>
|
||||||
ng-class="{'active': repo===repoName}"
|
<span class="navbar-right">
|
||||||
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">
|
|
||||||
<span>
|
<span>
|
||||||
<form role="search" class="form-inline">
|
<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"><span ng-bind="pinboardCount.numPinnedJobs"></span> pinned jobs</span>
|
||||||
<span class="label label-primary">0 unclassified</span>
|
<span class="label label-primary">0 unclassified</span>
|
||||||
<div ng-controller="SearchCtrl" class="form-group form-inline">
|
<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"
|
class="form-control input-sm"
|
||||||
placeholder="Filter platforms & jobs">
|
placeholder="Filter platforms & jobs">
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,12 +1,53 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
treeherder.controller('AnnotationsPluginCtrl',
|
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");
|
$log.debug("annotations plugin initialized");
|
||||||
|
|
||||||
$scope.$watch('classifications', function(newValue, oldValue){
|
$scope.$watch('classifications', function(newValue, oldValue){
|
||||||
|
|
||||||
$scope.tabs.annotations.num_items = newValue ? $scope.classifications.length : 0;
|
$scope.tabs.annotations.num_items = newValue ? $scope.classifications.length : 0;
|
||||||
}, true);
|
}, 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 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">
|
<table class="table-condensed table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr><th>Datetime</th><th>Author</th><th>Failure type</th><th>Note</th></tr>
|
<tr><th>Datetime</th><th>Author</th><th>Failure type</th><th>Note</th></tr>
|
||||||
|
@ -13,23 +13,28 @@
|
||||||
{{classification.who}}
|
{{classification.who}}
|
||||||
</td>
|
</td>
|
||||||
<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>
|
||||||
<td>
|
<td>
|
||||||
{{classification.note}}
|
{{classification.note}}
|
||||||
</td>
|
</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>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div ng-show="classifications.length < 1"></br><em>This job has not been classified</em></div>
|
<div ng-show="classifications.length < 1"></br><em>This job has not been classified</em></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-1 bug-list-panel">
|
<div class="col-xs-2 bug-list-panel">
|
||||||
<h5><strong>Bugs</strong></h5>
|
<h5><strong>Bugs</strong></h5>
|
||||||
<ul class="bug-list">
|
<ul class="bug-list">
|
||||||
<li ng-repeat="bug in bugs">
|
<li ng-repeat="bug in bugs">
|
||||||
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id={{bug.bug_id}}"
|
<th-related-bug-saved></th-related-bug-saved>
|
||||||
target="_blank"
|
|
||||||
class="label label-default">{{bug.bug_id}}</a>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,90 +1,12 @@
|
||||||
"use strict";
|
"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',
|
treeherder.controller('BugsPluginCtrl',
|
||||||
function BugsPluginCtrl($scope, $rootScope, $log, ThJobArtifactModel,
|
function BugsPluginCtrl($scope, $rootScope, ThLog, ThJobArtifactModel,
|
||||||
ThBugJobMapModel, ThJobClassificationModel, thNotify, $modal) {
|
ThBugJobMapModel, ThJobClassificationModel, thNotify, $modal) {
|
||||||
|
var $log = new ThLog(this.constructor.name);
|
||||||
|
|
||||||
$log.debug("bugs plugin initialized");
|
$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){
|
var update_bugs = function(newValue, oldValue){
|
||||||
$scope.bugs = {};
|
$scope.bugs = {};
|
||||||
$scope.visible = "open";
|
$scope.visible = "open";
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
treeherder.controller('PluginCtrl',
|
treeherder.controller('PluginCtrl',
|
||||||
|
|
||||||
function PluginCtrl($scope, $rootScope, thUrl, ThJobClassificationModel,
|
function PluginCtrl($scope, $rootScope, thUrl, ThJobClassificationModel,
|
||||||
thClassificationTypes, ThJobModel, thEvents, dateFilter,
|
thClassificationTypes, ThJobModel, thEvents, dateFilter,
|
||||||
numberFilter, ThBugJobMapModel, thResultStatus, thSocket,
|
numberFilter, ThBugJobMapModel, thResultStatus, thSocket,
|
||||||
ThResultSetModel, $log) {
|
ThResultSetModel, ThLog) {
|
||||||
|
|
||||||
|
var $log = new ThLog("PluginCtrl");
|
||||||
|
|
||||||
$scope.job = {};
|
$scope.job = {};
|
||||||
|
|
||||||
|
@ -18,7 +18,9 @@ treeherder.controller('PluginCtrl',
|
||||||
|
|
||||||
// get the details of the current job
|
// get the details of the current job
|
||||||
ThJobModel.get($scope.job.id).then(function(data){
|
ThJobModel.get($scope.job.id).then(function(data){
|
||||||
_.extend($scope.job, data);
|
$scope.job = data;
|
||||||
|
$scope.$broadcast(thEvents.jobDetailLoaded);
|
||||||
|
|
||||||
updateVisibleFields();
|
updateVisibleFields();
|
||||||
$scope.logs = data.logs;
|
$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) {
|
$rootScope.$on(thEvents.jobClick, function(event, job) {
|
||||||
selectJob(job, $rootScope.selectedJob);
|
selectJob(job, $rootScope.selectedJob);
|
||||||
$rootScope.selectedJob = job;
|
$rootScope.selectedJob = job;
|
||||||
|
@ -75,7 +88,7 @@ treeherder.controller('PluginCtrl',
|
||||||
$scope.updateBugs();
|
$scope.updateBugs();
|
||||||
});
|
});
|
||||||
|
|
||||||
$scope.classificationTypes = thClassificationTypes;
|
$scope.classificationTypes = thClassificationTypes.classifications;
|
||||||
|
|
||||||
// load the list of existing classifications (including possibly a new one just
|
// load the list of existing classifications (including possibly a new one just
|
||||||
// added).
|
// added).
|
||||||
|
@ -100,7 +113,7 @@ treeherder.controller('PluginCtrl',
|
||||||
};
|
};
|
||||||
|
|
||||||
var updateClassification = function(classification){
|
var updateClassification = function(classification){
|
||||||
if(classification.who != $scope.user.email){
|
if(classification.who !== $scope.user.email){
|
||||||
// get a fresh version of the job
|
// get a fresh version of the job
|
||||||
ThJobModel.get_list({id:classification.id})
|
ThJobModel.get_list({id:classification.id})
|
||||||
.then(function(job_list){
|
.then(function(job_list){
|
||||||
|
@ -123,7 +136,7 @@ treeherder.controller('PluginCtrl',
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
};
|
||||||
|
|
||||||
thSocket.on("job_classification", updateClassification);
|
thSocket.on("job_classification", updateClassification);
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
treeherder.controller('PinboardCtrl',
|
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) {
|
$rootScope.$on(thEvents.jobPin, function(event, job) {
|
||||||
$scope.pinJob(job);
|
$scope.pinJob(job);
|
||||||
$rootScope.$digest();
|
$scope.$digest();
|
||||||
});
|
});
|
||||||
|
|
||||||
$scope.pinJob = function(job) {
|
$scope.pinJob = function(job) {
|
||||||
|
@ -44,6 +46,7 @@ treeherder.controller('PinboardCtrl',
|
||||||
}
|
}
|
||||||
$scope.classification.who = $scope.user.email;
|
$scope.classification.who = $scope.user.email;
|
||||||
thPinboard.save($scope.classification);
|
thPinboard.save($scope.classification);
|
||||||
|
$rootScope.selectedJob = null;
|
||||||
} else {
|
} else {
|
||||||
thNotify.send("must be logged in to classify jobs", "danger");
|
thNotify.send("must be logged in to classify jobs", "danger");
|
||||||
}
|
}
|
||||||
|
@ -76,6 +79,7 @@ treeherder.controller('PinboardCtrl',
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.saveEnteredBugNumber = function() {
|
$scope.saveEnteredBugNumber = function() {
|
||||||
|
$log.debug("new bug number to be saved: ", $scope.newEnteredBugNumber);
|
||||||
thPinboard.addBug({id:$scope.newEnteredBugNumber});
|
thPinboard.addBug({id:$scope.newEnteredBugNumber});
|
||||||
$scope.newEnteredBugNumber = null;
|
$scope.newEnteredBugNumber = null;
|
||||||
$scope.toggleEnterBugNumber();
|
$scope.toggleEnterBugNumber();
|
||||||
|
@ -84,10 +88,12 @@ treeherder.controller('PinboardCtrl',
|
||||||
$scope.viewJob = function(job) {
|
$scope.viewJob = function(job) {
|
||||||
$rootScope.selectedJob = job;
|
$rootScope.selectedJob = job;
|
||||||
$rootScope.$broadcast(thEvents.jobClick, job);
|
$rootScope.$broadcast(thEvents.jobClick, job);
|
||||||
|
$rootScope.$broadcast(thEvents.selectJob, job);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.classification = thPinboard.createNewClassification();
|
$scope.classification = thPinboard.createNewClassification();
|
||||||
$scope.pinnedJobs = thPinboard.pinnedJobs;
|
$scope.pinnedJobs = thPinboard.pinnedJobs;
|
||||||
$scope.relatedBugs = thPinboard.relatedBugs;
|
$scope.relatedBugs = thPinboard.relatedBugs;
|
||||||
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
<div ng-controller="PluginCtrl">
|
<div ng-controller="PluginCtrl" class="full-height">
|
||||||
<div ng-controller="PinboardCtrl">
|
<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>
|
<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"
|
<div id="bottom-left-panel"
|
||||||
ng-class="{'with-pinboard-abs': hasPinnedJobs(), 'without-pinboard-abs': !hasPinnedJobs()}">
|
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">
|
<div class="panel-head">
|
||||||
<table class="table-super-condensed table-striped">
|
<table class="table-super-condensed table-striped {{resultStatusShading}}">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="small">Result</th>
|
<th class="small">Result</th>
|
||||||
<td class="small {{ resultStatusClass }}">{{ job.result }}</td>
|
<td class="small">{{ job.result }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="small">Machine name</th>
|
<th class="small">Machine name</th>
|
||||||
|
@ -22,9 +26,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div id="bottom-center-panel"
|
<div id="bottom-center-panel"
|
||||||
|
class="full-height"
|
||||||
ng-class="{'with-pinboard-margin': hasPinnedJobs(), 'without-pinboard-margin': !hasPinnedJobs()}">
|
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">
|
<div class="panel-body resizable">
|
||||||
<tabset class="tabs-below bottom-panel-tabs">
|
<tabset class="tabs-below bottom-panel-tabs">
|
||||||
<tab ng-repeat="(tab_id, tab) in tabs" active="tab.active" disabled="tab.disabled">
|
<tab ng-repeat="(tab_id, tab) in tabs" active="tab.active" disabled="tab.disabled">
|
||||||
|
@ -38,6 +46,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div id="bottom-menu"
|
<div id="bottom-menu"
|
||||||
class="without-pinboard-abs">
|
class="without-pinboard-abs">
|
||||||
<div class="btn-group-vertical bottom-menu-group">
|
<div class="btn-group-vertical bottom-menu-group">
|
||||||
|
@ -65,7 +75,9 @@
|
||||||
ng-click="save()">
|
ng-click="save()">
|
||||||
<span class="glyphicon glyphicon-floppy-disk pull-left"></span> save
|
<span class="glyphicon glyphicon-floppy-disk pull-left"></span> save
|
||||||
</button>
|
</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>
|
<span class="caret"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu pull-right" role="menu">
|
<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>
|
<li><a ng-click="saveBugsOnly()"><i class="fa fa-bug"></i> bugs only</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</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"
|
<div class="btn btn-default btn-xs"
|
||||||
ng-show="logs"
|
ng-show="logs"
|
||||||
ng-disabled="artifacts.length>0">
|
ng-disabled="artifacts.length>0">
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
treeherder.controller('SimilarJobsPluginCtrl',
|
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");
|
$log.debug("similar jobs plugin initialized");
|
||||||
|
|
||||||
|
// do the jobs retrieval based on the user selection
|
||||||
$scope.update_similar_jobs = function(newValue) {
|
$scope.get_similar_jobs = function(){
|
||||||
if(newValue){$scope.similar_jobs_count = 20;}
|
var options = {
|
||||||
if($scope.job.id){
|
|
||||||
var options = {
|
|
||||||
count: $scope.similar_jobs_count
|
count: $scope.similar_jobs_count
|
||||||
};
|
};
|
||||||
angular.forEach($scope.similar_jobs_filters, function(value, key){
|
angular.forEach($scope.similar_jobs_filters, function(value, key){
|
||||||
|
@ -18,14 +20,25 @@ treeherder.controller('SimilarJobsPluginCtrl',
|
||||||
});
|
});
|
||||||
$log.log(options);
|
$log.log(options);
|
||||||
ThJobModel.get_list(options).then(function(data){
|
ThJobModel.get_list(options).then(function(data){
|
||||||
|
$log.log(data);
|
||||||
$scope.similar_jobs = 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.result_status_info = thResultStatusInfo;
|
||||||
|
$scope.$on(thEvents.jobDetailLoaded, $scope.update_similar_jobs);
|
||||||
$scope.$watch('job.job_guid', $scope.update_similar_jobs, true);
|
|
||||||
$scope.similar_jobs = [];
|
$scope.similar_jobs = [];
|
||||||
$scope.similar_jobs_filters = {
|
$scope.similar_jobs_filters = {
|
||||||
"machine_id": false,
|
"machine_id": false,
|
||||||
|
@ -40,11 +53,48 @@ treeherder.controller('SimilarJobsPluginCtrl',
|
||||||
return thResultStatusInfo(resultState).btnClass;
|
return thResultStatusInfo(resultState).btnClass;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// this is triggered by the show more link
|
||||||
$scope.show_more = function(){
|
$scope.show_more = function(){
|
||||||
$scope.similar_jobs_count += 20;
|
$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">
|
<div class="" ng-controller="SimilarJobsPluginCtrl">
|
||||||
<form role="form">
|
<div class="row">
|
||||||
<div class="checkbox">
|
<div class="col-xs-2">
|
||||||
<label>
|
<form role="form">
|
||||||
<input ng-change="update_similar_jobs()" type="checkbox" ng-model="similar_jobs_filters.machine_id"/>
|
<div class="checkbox">
|
||||||
Same machine: {{ job.machine_name }}
|
|
||||||
</label>
|
<input ng-change="update_similar_jobs()" type="checkbox" ng-model="similar_jobs_filters.machine_id"/>
|
||||||
</div>
|
<small>Same machine</small>
|
||||||
<div class="checkbox">
|
|
||||||
<label>
|
</div>
|
||||||
<input ng-change="update_similar_jobs()" type="checkbox" ng-model="similar_jobs_filters.job_type_id"/>
|
<div class="checkbox">
|
||||||
Same job type: {{ job.job_group_name }} {{ job.job_type_name }} ({{ job.job_type_symbol }})
|
|
||||||
</label>
|
<input ng-change="update_similar_jobs()" type="checkbox" ng-model="similar_jobs_filters.job_type_id"/>
|
||||||
</div>
|
<small>Same job type</small>
|
||||||
<div class="checkbox">
|
|
||||||
<label>
|
</div>
|
||||||
<input ng-change="update_similar_jobs()" type="checkbox" ng-model="similar_jobs_filters.build_platform_id"/>
|
<div class="checkbox">
|
||||||
Same platform: {{ job.platform }}
|
|
||||||
</label>
|
<input ng-change="update_similar_jobs()" type="checkbox" ng-model="similar_jobs_filters.build_platform_id"/>
|
||||||
</div>
|
<small>Same platform</small>
|
||||||
</form>
|
|
||||||
<p>
|
</div>
|
||||||
<button class="btn btn-similar-jobs btn-xs" ng-class="button_class(job)"
|
</form>
|
||||||
ng-repeat="job in similar_jobs | orderBy: -submit_timestamp">
|
</div>
|
||||||
{{job.job_type_symbol}}
|
<div class="col-xs-10">
|
||||||
</button>
|
<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>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
treeherder.controller('TinderboxPluginCtrl',
|
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");
|
$log.debug("Tinderbox plugin initialized");
|
||||||
var update_job_info = function(newValue, oldValue){
|
var update_job_info = function(newValue, oldValue){
|
||||||
$scope.tinderbox_lines = [];
|
$scope.tinderbox_lines = [];
|
||||||
|
|
|
@ -3,17 +3,17 @@
|
||||||
<tr ng-repeat="line in tinderbox_lines_parsed">
|
<tr ng-repeat="line in tinderbox_lines_parsed">
|
||||||
<th>{{line.title}}</th>
|
<th>{{line.title}}</th>
|
||||||
<td ng-switch on="line.type">
|
<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">
|
<ul ng-switch-when="TalosResult">
|
||||||
<li>Datazilla:
|
<li>Datazilla:
|
||||||
<ul>
|
<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>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li>Graphserver:
|
<li>Graphserver:
|
||||||
<ul>
|
<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>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
Загрузка…
Ссылка в новой задаче