| <!-- |
| The common.js file must be included before this file. |
| |
| This in an HTML Import-able file that contains the definition |
| of the following elements: |
| |
| <arb-status-sk> |
| |
| To use this file import it: |
| |
| <link href="/res/imp/arb-status-sk.html" rel="import" /> |
| |
| Usage: |
| |
| <arb-status-sk></arb-status-sk> |
| |
| Properties: |
| reload - How often (in seconds) to reload data. |
| rollUser - User who creates the DEPS rolls. |
| |
| Methods: |
| None. |
| |
| Events: |
| None |
| --> |
| <link rel="import" href="/res/common/imp/human-date-sk.html"> |
| <link rel="import" href="/res/imp/bower_components/iron-flex-layout/iron-flex-layout-classes.html"> |
| <link rel="import" href="/res/imp/bower_components/paper-button/paper-button.html"> |
| <link rel="import" href="/res/imp/bower_components/paper-dialog/paper-dialog.html"> |
| <link rel="import" href="/res/imp/bower_components/paper-input/paper-input.html"> |
| <link rel="import" href="/res/imp/bower_components/paper-spinner/paper-spinner.html"> |
| <link rel="stylesheet" href="/res/common/css/md.css"> |
| <dom-module id="arb-status-sk"> |
| <style is="custom-style" include="iron-flex iron-flex-alignment iron-positioning"> |
| :host { |
| font-family: sans-serif; |
| } |
| #loadstatus { |
| font-size: 0.8em; |
| padding: 0px 15px; |
| } |
| a,a.visited { |
| color: #1f78b4; |
| } |
| .table { |
| border-collapse: collapse; |
| display: table; |
| } |
| .tr { |
| border-bottom: 1px solid #EEEEEE; |
| display: table-row; |
| } |
| .tr:hover { |
| background-color: #F5F5F5; |
| } |
| .td,.th { |
| display: table-cell; |
| padding: 10px; |
| } |
| .td { |
| color: #212121; |
| font-size: 0.813em; |
| } |
| .th { |
| color: #767676; |
| font-size: 0.75em; |
| } |
| .success { |
| color: #66A61E; |
| } |
| .failure { |
| color: #D95F02; |
| } |
| .unknown { |
| color: #666666; |
| } |
| .big { |
| font-size: 1.3em; |
| } |
| .small { |
| font-size: 0.8em; |
| } |
| .nowrap { |
| white-space: nowrap; |
| } |
| .trybot { |
| margin: 5px; |
| } |
| </style> |
| <template> |
| <div class="horizontal layout center" id="loadstatus"> |
| <paper-input type="number" value="{{reload}}" label="Reload (s)" prevent-invalid-input></paper-input> |
| <div class="flex"></div> |
| <div>Last loaded at <span>{{_lastLoaded}}</span></div> |
| </div> |
| <div id="statusDisplay"> |
| <div class="table"> |
| <div class="tr"> |
| <div class="td nowrap">Mode:</div> |
| <div class="td nowrap"> |
| <template is="dom-repeat" items="{{modeButtons}}"> |
| <button class$="{{_buttonClass(item)}}" on-tap="_buttonPressed" disabled$="{{_modeChangePending}}">{{item.label}}</button> |
| </template> |
| <paper-spinner active$="{{_modeChangePending}}"></paper-spinner> |
| </div> |
| </div> |
| <div class="tr"> |
| <div class="td nowrap">Mode Set:</div> |
| <div class="td nowrap unknown"> |
| <!-- No line break below, or we get a space before the colon, eg. |
| user@google.com : Mode change message --> |
| <span>[[modeChangeBy]]</span><template is="dom-if" if="{{modeChangeMsg}}"><span>: [[modeChangeMsg]]</span></template> |
| </div> |
| </div> |
| <div class="tr"> |
| <div class="td nowrap">Status:</div> |
| <div class="td nowrap"> |
| <span class$="{{_statusClass(status)}}"><span class="big">{{status}}</span></span> |
| </div> |
| </div> |
| <template is="dom-if" if="{{_computeShowError(_editRights,error)}}"> |
| <div class="tr"> |
| <div class="td nowrap">Error:</div> |
| <div class="td"><pre>{{error}}</pre></div> |
| </div> |
| </template> |
| <div class="tr"> |
| <div class="td nowrap">Current Roll:</div> |
| <div class="td"> |
| <div> |
| <template is="dom-if" if="{{_exists(currentRoll)}}"> |
| <a href="{{_issueURL(currentRoll)}}" class="big" target="_blank">{{currentRoll.subject}}</a> |
| </template> |
| <template is="dom-if" if="{{!_exists(currentRoll)}}"> |
| <span>(none)</span> |
| </template> |
| </div> |
| <div> |
| <template is="dom-repeat" items="{{currentRoll.tryResults}}"> |
| <div class="trybot"> |
| <template is="dom-if" if="{{_exists(item.url)}}"> |
| <a href="{{item.url}}" class$="{{_trybotClass(item)}}" target="_blank">{{item.builder}}</a> |
| </template> |
| <template is="dom-if" if="{{!_exists(item.url)}}"> |
| <span class="nowrap" class$="{{_trybotClass(item)}}">{{item.builder}}</span> |
| </template> |
| </div> |
| </template> |
| </div> |
| </div> |
| </div> |
| <template is="dom-if" if="{{_exists(lastRoll)}}"> |
| <div class="tr"> |
| <div class="td nowrap">Previous roll result:</div> |
| <div class="td"> |
| <span class$="{{_rollClass(lastRoll)}}">{{_rollResult(lastRoll)}}</span> |
| <a href="{{_issueURL(lastRoll)}}" target="_blank" class="small">(detail)</a> |
| </div> |
| </div> |
| </template> |
| <div class="tr"> |
| <div class="td nowrap">History:</div> |
| <div class="td"> |
| <div class="table"> |
| <div class="tr"> |
| <div class="th">Roll</div> |
| <div class="th">Last Modified</div> |
| <div class="th">Result</div> |
| </div> |
| <template is="dom-repeat" items="{{recent}}"> |
| <div class="tr"> |
| <div class="td"><a href="{{_issueURL(item)}}" target="_blank">{{item.subject}}</a></div> |
| <div class="td"><human-date-sk date="{{item.modified}}" diff></human-date-sk> ago</div> |
| <div class="td"><span class$="{{_rollClass(item)}}">{{_rollResult(item)}}</span></div> |
| </div> |
| </template> |
| </div> |
| </div> |
| </div> |
| <div class="tr"> |
| <div class="td nowrap">Full History:</div> |
| <div class="td"> |
| <a href$="{{_rollUserURL}}" target="_blank"> |
| {{_rollUserURL}} |
| </a> |
| </div> |
| </div> |
| </div> |
| </div> |
| <!-- Warning for future travelers: paper-dialog doesn't like to be inside of |
| divs. It's best left just inside the top-level <template> --> |
| <paper-dialog id="mode_change_dialog" modal on-iron-overlay-closed="_changeMode"> |
| <h2>Enter a message:</h2> |
| <paper-input type="text" id="mode_change_msg"></paper-input> |
| <paper-button dialog-dismiss>Cancel</paper-button> |
| <paper-button dialog-confirm>Submit</paper-button> |
| </paper-dialog> |
| </template> |
| <script> |
| Polymer({ |
| is: 'arb-status-sk', |
| properties: { |
| mode: { |
| type: String, |
| value: "(not yet loaded)", |
| readOnly: true, |
| }, |
| modeChangeBy: { |
| type: String, |
| value: "", |
| readOnly: true, |
| }, |
| modeChangeMsg: { |
| type: String, |
| value: "", |
| readOnly: true, |
| }, |
| status: { |
| type: String, |
| value: "(not yet loaded)", |
| readOnly: true, |
| }, |
| currentRoll: { |
| type: Object, |
| value: null, |
| readOnly: true, |
| }, |
| error: { |
| type: String, |
| value: null, |
| readOnly: true, |
| }, |
| gerritUrl: { |
| type: String, |
| value: "", |
| readOnly: true, |
| }, |
| lastRoll: { |
| type: Object, |
| value: null, |
| readOnly: true, |
| }, |
| recent: { |
| type: Array, |
| value: function() { return []; }, |
| readOnly: true, |
| }, |
| reload: { |
| type: Number, |
| observer: "_reloadChanged", |
| value: 60, |
| }, |
| rollUser: { |
| type: String, |
| value: "skia-deps-roller@chromium.org", |
| }, |
| _rollUserURL: { |
| type: String, |
| computed: "_computeRollUserURL(rollUser,gerritUrl)", |
| }, |
| initialSelectedMode: { |
| type: Number, |
| value: 0, |
| readOnly: true, |
| }, |
| modeButtons: { |
| type: Array, |
| value: function() { return []; }, |
| readOnly: true, |
| }, |
| validModes: { |
| type: Array, |
| value: function() { return []; }, |
| readOnly: true, |
| }, |
| _editRights: { |
| type: Boolean, |
| value: false, |
| }, |
| _lastLoaded: { |
| type: String, |
| value: "not yet loaded", |
| }, |
| _modeChangePending: { |
| type: Boolean, |
| value: false, |
| }, |
| _selectedMode: { |
| type: String, |
| value: "", |
| }, |
| _timeout: { |
| type: Object, |
| value: null, |
| }, |
| }, |
| |
| ready: function() { |
| sk.Login.then(function(status) { |
| this._editRights = status.IsAGoogler; |
| }.bind(this)); |
| this._reload(); |
| }, |
| |
| _buttonClass: function(mode) { |
| return mode.class; |
| }, |
| |
| _buttonLabel: function(mode) { |
| return mode.label; |
| }, |
| |
| _buttonPressed: function(e) { |
| if (!this._editRights) { |
| sk.errorMessage("You must be logged in with an @google.com account to set the ARB mode."); |
| return |
| } |
| var mode = e.srcElement.innerHTML; |
| if (mode == this.mode) { |
| return; |
| } |
| this._selectedMode = mode; |
| this.$.mode_change_dialog.open(); |
| }, |
| |
| _changeMode: function(e) { |
| if (e.detail.canceled || !e.detail.confirmed) { |
| this._selectedMode = ""; |
| return; |
| } |
| var url = "/json/mode"; |
| var body = JSON.stringify({ |
| "message": this.$.mode_change_msg.value, |
| "mode": this._selectedMode, |
| }); |
| sk.errorMessage("Mode change in progress. This may take some time."); |
| this._modeChangePending = true; |
| sk.post(url, body).then(JSON.parse).then(function (json) { |
| this._update(json); |
| this._modeChangePending = false; |
| this.$.mode_change_msg.value; |
| sk.errorMessage("Success!"); |
| }.bind(this), function(err) { |
| this._modeChangePending = false; |
| sk.errorMessage("Failed to change the mode: " + err); |
| }); |
| }, |
| |
| _computeRollUserURL: function(rollUser) { |
| return this.gerritUrl + "/q/owner:" + rollUser; |
| }, |
| |
| _computeShowError: function(editRights, error) { |
| return editRights && error; |
| }, |
| |
| _issueURL: function(issue) { |
| if (issue) { |
| return this.gerritUrl + "/c/" + issue.issue; |
| } |
| }, |
| |
| _exists: function(obj) { |
| return !!obj; |
| }, |
| |
| _reloadChanged: function() { |
| this._resetTimeout(); |
| }, |
| |
| _resetTimeout: function() { |
| if (this._timeout) { |
| window.clearTimeout(this._timeout); |
| } |
| if (this.reload > 0) { |
| this._timeout = window.setTimeout(function () { |
| this._reload(); |
| }.bind(this), this.reload * 1000); |
| } |
| }, |
| |
| _reload: function() { |
| var url = "/json/status"; |
| console.log("Loading status from " + url); |
| |
| sk.get(url).then(JSON.parse).then(function(json) { |
| this._update(json); |
| }.bind(this)).catch(function(err) { |
| sk.errorMessage(err); |
| this._resetTimeout(); |
| }.bind(this)); |
| }, |
| |
| _rollClass: function(roll) { |
| if (!roll) { |
| return "unknown"; |
| } |
| return { |
| "succeeded": "success", |
| "failed": "failure", |
| "in progress": "unknown", |
| "dry run succeeded": "success", |
| "dry run failed": "failure", |
| "dry run in progress": "unknown", |
| }[roll.result] || "unknown"; |
| }, |
| |
| _rollResult: function(roll) { |
| if (!roll) { |
| return "unknown"; |
| } |
| return roll.result; |
| }, |
| |
| _statusClass: function(status) { |
| return { |
| "error": "failure", |
| "in progress": "unknown", |
| "stopped": "failure", |
| "up to date": "success", |
| "dry run failed": "failure", |
| "dry run succeeded": "success", |
| "dry run in progress": "unknown", |
| }[status] || ""; |
| }, |
| |
| _trybotClass: function(trybot) { |
| if (trybot.status == "STARTED") { |
| return "unknown"; |
| } else if (trybot.status == "COMPLETED") { |
| return { |
| "CANCELED": "failure", |
| "FAILURE": "failure", |
| "SUCCESS": "success", |
| }[trybot.result] || ""; |
| } else { |
| return "unknown"; |
| } |
| }, |
| |
| _update: function(json) { |
| this._setCurrentRoll(json.currentRoll); |
| this._setError(json.error); |
| this._setGerritUrl(json.gerritUrl); |
| this._setLastRoll(json.lastRoll); |
| this._setMode(json.mode.mode); |
| this._setModeChangeBy(json.mode.user); |
| this._setModeChangeMsg(json.mode.message); |
| this._setRecent(json.recent); |
| this._setInitialSelectedMode(json.validModes.indexOf(json.mode).toString()); |
| this._setStatus(json.status); |
| this._setValidModes(json.validModes); |
| var modeButtons = []; |
| for (var i = 0; i < this.validModes.length; i++) { |
| var m = this.validModes[i]; |
| modeButtons.push({"label": m, "class": m == this.mode ? "action" : ""}); |
| } |
| this._setModeButtons(modeButtons); |
| |
| this._lastLoaded = new Date().toLocaleTimeString(); |
| this._resetTimeout(); |
| console.log("Reloaded status."); |
| }, |
| }); |
| </script> |
| </dom-module> |