| <!-- |
| 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. |
| |
| Methods: |
| None. |
| |
| Events: |
| None |
| --> |
| <link rel="import" href="/res/common/imp/human-date-sk.html"> |
| <link rel="import" href="/res/common/imp/styles-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-dropdown-menu/paper-dropdown-menu.html"> |
| <link rel="import" href="/res/imp/bower_components/paper-input/paper-input.html"> |
| <link rel="import" href="/res/imp/bower_components/paper-item/paper-item.html"> |
| <link rel="import" href="/res/imp/bower_components/paper-listbox/paper-listbox.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 include="iron-flex iron-flex-alignment iron-positioning styles-sk"> |
| :host { |
| font-family: sans-serif; |
| } |
| #loadstatus { |
| font-size: 0.8em; |
| padding: 0px 15px; |
| } |
| a,a.visited { |
| color: #1f78b4; |
| } |
| .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"> |
| <template is="dom-if" if="{{_exists(parentWaterfall)}}"> |
| <div class="tr"> |
| <div class="td nowrap">Parent Repo Build Status</div> |
| <div class="td nowrap unknown"> |
| <span class="big"><a href$="{{parentWaterfall}}" target="_blank">{{parentWaterfall}}</a></span> |
| </div> |
| </div> |
| </template> |
| <div class="tr"> |
| <div class="td nowrap">Current Mode:</div> |
| <div class="td nowrap unknown"> |
| <span class="big">{{mode}}</span> |
| </div> |
| </div> |
| <div class="tr"> |
| <div class="td nowrap">Set By:</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">Change Mode:</div> |
| <div class="td nowrap"> |
| <template is="dom-repeat" items="{{modeButtons}}"> |
| <button class$="{{_buttonClass(item)}}" on-tap="_buttonPressed" disabled$="{{_modeChangePending}}" value="{{item.value}}">{{item.label}}</button> |
| </template> |
| <paper-spinner active$="{{_modeChangePending}}"></paper-spinner> |
| </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> |
| <template is="dom-if" if="{{_isThrottled(status)}}"> |
| <span>until <human-date-sk date="[[throttledUntil]]" seconds></human-date-sk></span> |
| <button on-tap="_unthrottle">Force Unthrottle</button> |
| </template> |
| </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> |
| <template is="dom-if" if="{{!_isCQ(item)}}"> |
| <span class="nowrap small">({{item.category}})</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$="{{fullHistoryUrl}}" target="_blank"> |
| {{fullHistoryUrl}} |
| </a> |
| </div> |
| </div> |
| <div class="tr"> |
| <div class="td nowrap">Strategy for choosing next roll revision:</div> |
| <div class="td nowrap"> |
| <paper-dropdown-menu id="strategyDropDown"> |
| <paper-listbox id="strategyMenu" class="dropdown-content" selected="{{_selectedStrategy}}" id="strategy_listbox" attr-for-selected="value"> |
| <template is="dom-repeat" items="[[validStrategies]]"> |
| <paper-item value="[[item]]">[[item]]</paper-item> |
| </template> |
| </paper-listbox> |
| </paper-dropdown-menu> |
| </div> |
| </div> |
| <div class="tr"> |
| <div class="td nowrap">Set By:</div> |
| <div class="td nowrap unknown"> |
| <!-- No line break below, or we get a space before the colon, eg. |
| user@google.com : Strategy change message --> |
| <span>[[strategyChangeBy]]</span><template is="dom-if" if="{{strategyChangeMsg}}"><span>: [[strategyChangeMsg]]</span></template> |
| </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> |
| <paper-dialog id="strategy_change_dialog" modal on-iron-overlay-closed="_changeStrategy"> |
| <h2>Enter a message:</h2> |
| <paper-input type="text" id="strategy_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, |
| }, |
| fullHistoryUrl: { |
| type: String, |
| value: "", |
| readOnly: true, |
| }, |
| issueUrlBase: { |
| 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, |
| }, |
| initialSelectedMode: { |
| type: Number, |
| value: 0, |
| readOnly: true, |
| }, |
| modeButtons: { |
| type: Array, |
| value: function() { return []; }, |
| readOnly: true, |
| }, |
| parentWaterfall: { |
| type: String, |
| value: null, |
| readOnly: true, |
| }, |
| throttledUntil: { |
| type: Number, |
| value: 0, |
| readOnly: true, |
| }, |
| strategy: { |
| type: String, |
| value: "(not yet loaded)", |
| readOnly: true, |
| }, |
| strategyChangeBy: { |
| type: String, |
| value: "", |
| readOnly: true, |
| }, |
| strategyChangeMsg: { |
| type: String, |
| value: "", |
| readOnly: true, |
| }, |
| validModes: { |
| type: Array, |
| value: function() { return []; }, |
| readOnly: true, |
| }, |
| validStrategies: { |
| 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: "", |
| }, |
| _selectedStrategy: { |
| type: String, |
| value: "", |
| observer: "_selectedStrategyChanged", |
| }, |
| _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; |
| }, |
| |
| _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.value; |
| 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 = window.location.pathname + "/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); |
| }); |
| }, |
| |
| _changeStrategy: function(e) { |
| if (e.detail.canceled || !e.detail.confirmed) { |
| return; |
| } |
| var url = window.location.pathname + "/json/strategy"; |
| var body = JSON.stringify({ |
| "message": this.$.strategy_change_msg.value, |
| "strategy": this._selectedStrategy, |
| }); |
| sk.errorMessage("Strategy change in progress. This may take some time."); |
| sk.post(url, body).then(JSON.parse).then(function (json) { |
| this._update(json); |
| this.$.strategy_change_msg.value = ""; |
| sk.errorMessage("Success!"); |
| }.bind(this), function(err) { |
| sk.errorMessage("Failed to change the strategy: " + err); |
| }); |
| }, |
| |
| _computeShowError: function(editRights, error) { |
| return editRights && error; |
| }, |
| |
| _issueURL: function(issue) { |
| if (issue) { |
| return this.issueUrlBase + issue.issue; |
| } |
| }, |
| |
| _exists: function(obj) { |
| return !!obj; |
| }, |
| |
| _getModeButtonLabel: function(currentMode, mode) { |
| // TODO(borenet): This is a hack; it doesn't respect this.validModes. |
| return { |
| "running": { |
| "stopped": "stop", |
| "dry run": "switch to dry run", |
| }, |
| "stopped": { |
| "running": "resume", |
| "dry run": "switch to dry run", |
| }, |
| "dry run": { |
| "running": "switch to normal mode", |
| "stopped": "stop", |
| }, |
| }[currentMode][mode]; |
| }, |
| |
| _isCQ: function(tryjob) { |
| return tryjob.category === "cq"; |
| }, |
| |
| _isThrottled: function(st) { |
| return st.indexOf("throttle") >= 0; |
| }, |
| |
| _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 = window.location.pathname + "/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": "fg-success", |
| "failed": "fg-failure", |
| "in progress": "fg-unknown", |
| "dry run succeeded": "fg-success", |
| "dry run failed": "fg-failure", |
| "dry run in progress": "fg-unknown", |
| }[roll.result] || "fg-unknown"; |
| }, |
| |
| _rollResult: function(roll) { |
| if (!roll) { |
| return "unknown"; |
| } |
| return roll.result; |
| }, |
| |
| _statusClass: function(status) { |
| return { |
| "idle": "fg-unknown", |
| "active": "fg-unknown", |
| "success": "fg-success", |
| "failure": "fg-failure", |
| "throttled": "fg-failure", |
| "dry run idle": "fg-unknown", |
| "dry run active": "fg-unknown", |
| "dry run success": "fg-success", |
| "dry run success; leaving open": "fg-success", |
| "dry run failure": "fg-failure", |
| "dry run throttled": "fg-failure", |
| "stopped": "fg-failure", |
| }[status] || ""; |
| }, |
| |
| _selectedStrategyChanged: function(e) { |
| if (!this._selectedStrategy || this._selectedStrategy == this.strategy) { |
| return; |
| } |
| if (!this._editRights) { |
| sk.errorMessage("You must be logged in with an @google.com account to set the ARB strategy."); |
| return |
| } |
| this.$.strategy_change_dialog.open(); |
| }, |
| |
| _trybotClass: function(trybot) { |
| if (trybot.status == "STARTED") { |
| return "fg-unknown"; |
| } else if (trybot.status == "COMPLETED") { |
| return { |
| "CANCELED": "fg-failure", |
| "FAILURE": "fg-failure", |
| "SUCCESS": "fg-success", |
| }[trybot.result] || ""; |
| } else { |
| return "fg-unknown"; |
| } |
| }, |
| |
| _unthrottle: function() { |
| var url = window.location.pathname + "/json/unthrottle"; |
| console.log("Unthrottling: " + url); |
| |
| sk.post(url).then(function(json) { |
| sk.errorMessage("Successfully unthrottled the roller. May take a minute or so to start up.") |
| }.bind(this)).catch(function(err) { |
| sk.errorMessage(err); |
| }.bind(this)); |
| }, |
| |
| _update: function(json) { |
| this._setCurrentRoll(json.currentRoll); |
| this._setError(json.error); |
| this._setFullHistoryUrl(json.fullHistoryUrl); |
| this._setIssueUrlBase(json.issueUrlBase); |
| this._setLastRoll(json.lastRoll); |
| this._setMode(json.mode.mode); |
| this._setModeChangeBy(json.mode.user); |
| this._setModeChangeMsg(json.mode.message); |
| this._setParentWaterfall(json.parentWaterfall); |
| this._setRecent(json.recent); |
| this._setInitialSelectedMode(json.validModes.indexOf(json.mode).toString()); |
| this._setStatus(json.status); |
| this._setStrategy(json.strategy.strategy); |
| this._setStrategyChangeBy(json.strategy.user); |
| this._setStrategyChangeMsg(json.strategy.message); |
| this._setThrottledUntil(json.throttledUntil); |
| this._setValidModes(json.validModes); |
| this._setValidStrategies(json.validStrategies); |
| var modeButtons = []; |
| for (var i = 0; i < this.validModes.length; i++) { |
| var m = this.validModes[i]; |
| if (m != this.mode) { |
| modeButtons.push({ |
| "label": this._getModeButtonLabel(this.mode, m), |
| "value": m, |
| }); |
| } |
| } |
| this._setModeButtons(modeButtons); |
| |
| this._lastLoaded = new Date().toLocaleTimeString(); |
| this._resetTimeout(); |
| this._selectedStrategy = json.strategy.strategy |
| console.log("Reloaded status."); |
| }, |
| }); |
| </script> |
| </dom-module> |