| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| (function() { |
| 'use strict'; |
| |
| const DEFAULT_UPDATE_INTERVAL_MS = 1000 * 5; |
| const BASE_URL = 'https://cr-buildbucket.appspot.com/_ah/api/buildbucket/v1'; |
| |
| const MAX_UPDATE_INTERVAL_MS = 1000 * 60 * 5; |
| |
| Polymer({ |
| is: 'cr-buildbucket-view', |
| |
| properties: { |
| baseUrl: { |
| type: String, |
| value: BASE_URL, |
| }, |
| plugin: Object, |
| projectName: String, |
| change: Object, |
| revision: Object, |
| |
| _validPatchNums: { |
| type: Array, |
| computed: '_computeValidPatchNums(change, revision._number)', |
| }, |
| _builds: Array, |
| _pluginConfig: Object, |
| _expBuilds: { |
| type: Array, |
| value() { return []; }, |
| }, |
| _buildTags: { |
| type: Array, |
| computed: '_computeBuildTags(_pluginConfig, change._number, ' + |
| '_validPatchNums)', |
| }, |
| _loading: { |
| type: Boolean, |
| value: false, |
| }, |
| _loggedIn: { |
| type: Boolean, |
| value: false, |
| }, |
| _lastUpdate: String, |
| _showExpBuilds: { |
| type: Boolean, |
| value: false, |
| }, |
| _needsUpdate: { |
| type: Boolean, |
| value: false, |
| }, |
| _updateIntervalMs: { |
| type: Number, |
| value: DEFAULT_UPDATE_INTERVAL_MS, |
| }, |
| _updateTimeoutID: Number, |
| }, |
| |
| attached() { |
| this.listen(document, 'visibilitychange', '_handleVisibilityChange'); |
| if (!this.change) { |
| console.warn('element attached without change property set.'); |
| return; |
| } |
| this.reload(); |
| }, |
| |
| /** |
| * Reset the state of the element and fetch and display builds. |
| * |
| * @return {Promise} Resolves upon completion. |
| */ |
| reload() { |
| this._loading = true; |
| |
| const pluginName = this.plugin.getPluginName(); |
| const project = this.change.project; |
| |
| return this.$.client.getConfig(project, pluginName).then((config) => { |
| this._pluginConfig = config; |
| }).then(() => { |
| if (this._pluginConfig.buckets > 0) { |
| this._refreshBuilds().then(() => { this._loading = false; }); |
| } else { |
| console.log('Buildbucket plugin not configured for this project.'); |
| } |
| }); |
| }, |
| |
| _handleAuthChange(e) { |
| this._loggedIn = !!e.detail.loggedIn; |
| }, |
| |
| /** |
| * Fetch builds from Buildbucket and update the element state. |
| * |
| * @return {Promise} Resolves upon completion. |
| */ |
| _refreshBuilds() { |
| return this.$.client.getOAuthToken().then(() => { |
| return this.$.client.getBuilds(this._buildTags).then( |
| (allBuilds) => { |
| this._updateBuilds(allBuilds); |
| }).catch((err) => { |
| this._updateIntervalMs = Math.min(MAX_UPDATE_INTERVAL_MS, |
| (1 + Math.random()) * this._updateIntervalMs * 2); |
| console.error(err); |
| }).then(() => { |
| this._updateTimeoutID = |
| window.setTimeout(this._updateTimerFired.bind(this), |
| this._updateIntervalMs); |
| }); |
| }); |
| }, |
| |
| /** |
| * Update element state based on the given fetched builds. |
| * |
| * @param {Object[]} allBuilds Array of fetched build objects. |
| */ |
| _updateBuilds(allBuilds) { |
| const builds = []; |
| const experimentalBuilds = []; |
| allBuilds.forEach((b) => { |
| let params = {}; |
| try { |
| params = JSON.parse(b.parameters_json); |
| } catch (e) { |
| console.error(e); |
| } |
| if (params.properties && |
| params.properties.category === 'cq_experimental') { |
| experimentalBuilds.push(b); |
| } else { |
| builds.push(b); |
| } |
| }); |
| this._updateIntervalMs = DEFAULT_UPDATE_INTERVAL_MS; |
| this._lastUpdate = new Date().toLocaleTimeString(); |
| this._builds = builds; |
| this._expBuilds = experimentalBuilds; |
| }, |
| |
| _handleVisibilityChange(e) { |
| if (!document.hidden && this._needsUpdate) { |
| this._refreshBuilds(); |
| this._needsUpdate = false; |
| } |
| }, |
| |
| _updateTimerFired() { |
| if (document.hidden) { |
| this._needsUpdate = true; |
| return; |
| } |
| this._refreshBuilds(); |
| }, |
| |
| /** |
| * List numbers of patchsets (revisions) that are applicable. |
| * |
| * The reason why this is not just the current patchset number is because |
| * there may have been a succession of "trivial" changes before the current |
| * patchset. |
| * |
| * @param {Object} change A Gerrit ChangeInfo object. |
| * @param {number} patchNum Revision number of currently displayed patch. |
| * @return {number[]} Revision numbers for the displayed builds. |
| */ |
| _computeValidPatchNums(change, patchNum) { |
| const validKinds = ['TRIVIAL_REBASE', 'NO_CHANGE', 'NO_CODE_CHANGE']; |
| const revisions = []; |
| for (const revision in change.revisions) { |
| if (!change.revisions.hasOwnProperty(revision)) { continue; } |
| revisions.push(change.revisions[revision]); |
| } |
| revisions.sort((a, b) => { |
| // Reverse sort. |
| return b._number - a._number; |
| }); |
| const patchNums = []; |
| for (let i = 0; i < revisions.length; i++) { |
| if (i == 0 && change.status === 'MERGED') { |
| // Skip past the most recent patch on submitted CLs because the last |
| // patchset is always the autogenerated one, which may or may not |
| // count as a trivial change depending on the submit strategy. |
| continue; |
| } |
| if (revisions[i]._number > patchNum) { |
| // Patches after the one we're displaying don't count. |
| continue; |
| } |
| patchNums.push(revisions[i]._number); |
| if (validKinds.indexOf(revisions[i].kind) === -1) { |
| // If this revision was a non-trivial change, |
| // don't consider patchsets prior to it. |
| break; |
| } |
| } |
| return patchNums; |
| }, |
| |
| /** |
| * Construct a list of build tags used to query Buildbucket. |
| * |
| * @param {Object} config The config object for this project. |
| * @param {number} changeNum The legacy numeric ID of the change. |
| * @param {number[]} validPatchNums Patchset numbers to fetch builds for. |
| * @return {string[]} Build tags to use to query Buildbucket. |
| */ |
| _computeBuildTags(config, changeNum, validPatchNums) { |
| if (!config) { |
| return []; |
| } |
| return validPatchNums.map((patchNum) => { |
| return 'buildset:patch/gerrit/' + config.gerritHost + '/' + |
| changeNum + '/' + patchNum; |
| }); |
| }, |
| |
| _computeTitleText(builds) { |
| return (builds.length === 0 ? 'No ' : '') + 'Tryjobs'; |
| }, |
| |
| _computeExpBuildsButtonHidden(expBuilds) { |
| return !expBuilds || expBuilds.length === 0; |
| }, |
| |
| _computeExpBuildsButtonText(showExpBuilds) { |
| return (showExpBuilds ? 'Hide' : 'Show') + ' experimental tryjobs'; |
| }, |
| |
| _toggleExpBuilds(e) { |
| e.preventDefault(); |
| this._showExpBuilds = !this._showExpBuilds; |
| }, |
| |
| _computeChooseTryJobsHidden(buckets, loggedIn) { |
| return buckets == null || buckets.length === 0 || !loggedIn; |
| }, |
| |
| _computeGroupLuci(buckets) { |
| // Show the LUCI chip only if there are non-LUCI buckets. |
| return buckets.some(b => !b.name.startsWith('luci.')); |
| }, |
| |
| _handleChooseTryJobsTap(e) { |
| e.preventDefault(); |
| this.$.tryJobsPicker.open(); |
| }, |
| |
| _handleChooseTryJobsClose(e) { |
| e.preventDefault(); |
| this.$.tryJobsPicker.close(); |
| }, |
| |
| /** |
| * Construct a description string for the given patchset numbers. |
| * |
| * @param {number[]} validPatchNums List of patchset numbers. |
| * @return {string} A description to show in the UI. |
| * */ |
| _computePatchsetDescriptor(validPatchNums) { |
| if (!validPatchNums) { |
| return ''; |
| } else if (validPatchNums.length === 1) { |
| return 'Showing jobs from patch set ' + validPatchNums[0] + ', '; |
| } else { |
| return 'Showing jobs from patch sets ' + Math.min(...validPatchNums) + |
| '-' + Math.max(...validPatchNums) + ', '; |
| } |
| }, |
| }); |
| })(); |