 f97fc6e320
			
		
	
	f97fc6e320
	
	
	
		
			
			* Replace the following global variables with named imports: - Gerrit._pluginLoader - Gerrit._endpoints * Update gr-app-global-var-init.js * Move reset plugins function to test-utils.js Note: This is not a recommended approach. pluginLoader and endpoints must be moved to appContext (see todo in the files gr-plugin-loader.js and gr-plugin-endpoints.js) Change-Id: Ifc3454c293fc19830f12088168e83748f67d7cff
		
			
				
	
	
		
			443 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			443 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * @license
 | |
|  * Copyright (C) 2015 The Android Open Source Project
 | |
|  *
 | |
|  * Licensed under the Apache License, Version 2.0 (the "License");
 | |
|  * you may not use this file except in compliance with the License.
 | |
|  * You may obtain a copy of the License at
 | |
|  *
 | |
|  * http://www.apache.org/licenses/LICENSE-2.0
 | |
|  *
 | |
|  * Unless required by applicable law or agreed to in writing, software
 | |
|  * distributed under the License is distributed on an "AS IS" BASIS,
 | |
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
|  * See the License for the specific language governing permissions and
 | |
|  * limitations under the License.
 | |
|  */
 | |
| 
 | |
| import '../../../scripts/bundled-polymer.js';
 | |
| import '../../../styles/gr-change-list-styles.js';
 | |
| import '../../shared/gr-cursor-manager/gr-cursor-manager.js';
 | |
| import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
 | |
| import '../gr-change-list-item/gr-change-list-item.js';
 | |
| import '../../../styles/shared-styles.js';
 | |
| import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.js';
 | |
| import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
 | |
| import {afterNextRender} from '@polymer/polymer/lib/utils/render-status.js';
 | |
| import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js';
 | |
| import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
 | |
| import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
 | |
| import {PolymerElement} from '@polymer/polymer/polymer-element.js';
 | |
| import {htmlTemplate} from './gr-change-list_html.js';
 | |
| import {appContext} from '../../../services/app-context.js';
 | |
| import {BaseUrlBehavior} from '../../../behaviors/base-url-behavior/base-url-behavior.js';
 | |
| import {ChangeTableBehavior} from '../../../behaviors/gr-change-table-behavior/gr-change-table-behavior.js';
 | |
| import {URLEncodingBehavior} from '../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.js';
 | |
| import {KeyboardShortcutBehavior} from '../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.js';
 | |
| import {RESTClientBehavior} from '../../../behaviors/rest-client-behavior/rest-client-behavior.js';
 | |
| import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
 | |
| import {pluginEndpoints} from '../../shared/gr-js-api-interface/gr-plugin-endpoints.js';
 | |
| 
 | |
| const NUMBER_FIXED_COLUMNS = 3;
 | |
| const CLOSED_STATUS = ['MERGED', 'ABANDONED'];
 | |
| const LABEL_PREFIX_INVALID_PROLOG = 'Invalid-Prolog-Rules-Label-Name--';
 | |
| const MAX_SHORTCUT_CHARS = 5;
 | |
| 
 | |
| /**
 | |
|  * @extends Polymer.Element
 | |
|  */
 | |
| class GrChangeList extends mixinBehaviors( [
 | |
|   BaseUrlBehavior,
 | |
|   ChangeTableBehavior,
 | |
|   KeyboardShortcutBehavior,
 | |
|   RESTClientBehavior,
 | |
|   URLEncodingBehavior,
 | |
| ], GestureEventListeners(
 | |
|     LegacyElementMixin(
 | |
|         PolymerElement))) {
 | |
|   static get template() { return htmlTemplate; }
 | |
| 
 | |
|   static get is() { return 'gr-change-list'; }
 | |
|   /**
 | |
|    * Fired when next page key shortcut was pressed.
 | |
|    *
 | |
|    * @event next-page
 | |
|    */
 | |
| 
 | |
|   /**
 | |
|    * Fired when previous page key shortcut was pressed.
 | |
|    *
 | |
|    * @event previous-page
 | |
|    */
 | |
| 
 | |
|   static get properties() {
 | |
|     return {
 | |
|     /**
 | |
|      * The logged-in user's account, or an empty object if no user is logged
 | |
|      * in.
 | |
|      */
 | |
|       account: {
 | |
|         type: Object,
 | |
|         value: null,
 | |
|       },
 | |
|       /**
 | |
|        * An array of ChangeInfo objects to render.
 | |
|        * https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#change-info
 | |
|        */
 | |
|       changes: {
 | |
|         type: Array,
 | |
|         observer: '_changesChanged',
 | |
|       },
 | |
|       /**
 | |
|        * ChangeInfo objects grouped into arrays. The sections and changes
 | |
|        * properties should not be used together.
 | |
|        *
 | |
|        * @type {!Array<{
 | |
|        *   name: string,
 | |
|        *   query: string,
 | |
|        *   results: !Array<!Object>
 | |
|        * }>}
 | |
|        */
 | |
|       sections: {
 | |
|         type: Array,
 | |
|         value() { return []; },
 | |
|       },
 | |
|       labelNames: {
 | |
|         type: Array,
 | |
|         computed: '_computeLabelNames(sections)',
 | |
|       },
 | |
|       _dynamicHeaderEndpoints: {
 | |
|         type: Array,
 | |
|       },
 | |
|       selectedIndex: {
 | |
|         type: Number,
 | |
|         notify: true,
 | |
|       },
 | |
|       showNumber: Boolean, // No default value to prevent flickering.
 | |
|       showStar: {
 | |
|         type: Boolean,
 | |
|         value: false,
 | |
|       },
 | |
|       showReviewedState: {
 | |
|         type: Boolean,
 | |
|         value: false,
 | |
|       },
 | |
|       keyEventTarget: {
 | |
|         type: Object,
 | |
|         value() { return document.body; },
 | |
|       },
 | |
|       changeTableColumns: Array,
 | |
|       visibleChangeTableColumns: Array,
 | |
|       preferences: Object,
 | |
|       _config: Object,
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   static get observers() {
 | |
|     return [
 | |
|       '_sectionsChanged(sections.*)',
 | |
|       '_computePreferences(account, preferences, _config)',
 | |
|     ];
 | |
|   }
 | |
| 
 | |
|   keyboardShortcuts() {
 | |
|     return {
 | |
|       [this.Shortcut.CURSOR_NEXT_CHANGE]: '_nextChange',
 | |
|       [this.Shortcut.CURSOR_PREV_CHANGE]: '_prevChange',
 | |
|       [this.Shortcut.NEXT_PAGE]: '_nextPage',
 | |
|       [this.Shortcut.PREV_PAGE]: '_prevPage',
 | |
|       [this.Shortcut.OPEN_CHANGE]: '_openChange',
 | |
|       [this.Shortcut.TOGGLE_CHANGE_REVIEWED]: '_toggleChangeReviewed',
 | |
|       [this.Shortcut.TOGGLE_CHANGE_STAR]: '_toggleChangeStar',
 | |
|       [this.Shortcut.REFRESH_CHANGE_LIST]: '_refreshChangeList',
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   constructor() {
 | |
|     super();
 | |
|     this.flagsService = appContext.flagsService;
 | |
|   }
 | |
| 
 | |
|   /** @override */
 | |
|   created() {
 | |
|     super.created();
 | |
|     this.addEventListener('keydown',
 | |
|         e => this._scopedKeydownHandler(e));
 | |
|   }
 | |
| 
 | |
|   /** @override */
 | |
|   ready() {
 | |
|     super.ready();
 | |
|     this._ensureAttribute('tabindex', 0);
 | |
|     this.$.restAPI.getConfig().then(config => {
 | |
|       this._config = config;
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   /** @override */
 | |
|   attached() {
 | |
|     super.attached();
 | |
|     Gerrit.awaitPluginsLoaded().then(() => {
 | |
|       this._dynamicHeaderEndpoints = pluginEndpoints.getDynamicEndpoints(
 | |
|           'change-list-header');
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Iron-a11y-keys-behavior catches keyboard events globally. Some keyboard
 | |
|    * events must be scoped to a component level (e.g. `enter`) in order to not
 | |
|    * override native browser functionality.
 | |
|    *
 | |
|    * Context: Issue 7294
 | |
|    */
 | |
|   _scopedKeydownHandler(e) {
 | |
|     if (e.keyCode === 13) {
 | |
|       // Enter.
 | |
|       this._openChange(e);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   _lowerCase(column) {
 | |
|     return column.toLowerCase();
 | |
|   }
 | |
| 
 | |
|   _computePreferences(account, preferences, config) {
 | |
|     // Polymer 2: check for undefined
 | |
|     if ([account, preferences, config].some(arg => arg === undefined)) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     this.changeTableColumns = this.columnNames;
 | |
|     this.showNumber = false;
 | |
|     this.visibleChangeTableColumns = this.getEnabledColumns(this.columnNames,
 | |
|         config, this.flagsService.enabledExperiments);
 | |
| 
 | |
|     if (account) {
 | |
|       this.showNumber = !!(preferences &&
 | |
|           preferences.legacycid_in_change_table);
 | |
|       if (preferences.change_table &&
 | |
|           preferences.change_table.length > 0) {
 | |
|         const prefColumns = this.getVisibleColumns(preferences.change_table);
 | |
|         this.visibleChangeTableColumns = this.getEnabledColumns(prefColumns,
 | |
|             config, this.flagsService.enabledExperiments);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   _computeColspan(changeTableColumns, labelNames) {
 | |
|     if (!changeTableColumns || !labelNames) return;
 | |
|     return changeTableColumns.length + labelNames.length +
 | |
|         NUMBER_FIXED_COLUMNS;
 | |
|   }
 | |
| 
 | |
|   _computeLabelNames(sections) {
 | |
|     if (!sections) { return []; }
 | |
|     let labels = [];
 | |
|     const nonExistingLabel = function(item) {
 | |
|       return !labels.includes(item);
 | |
|     };
 | |
|     for (const section of sections) {
 | |
|       if (!section.results) { continue; }
 | |
|       for (const change of section.results) {
 | |
|         if (!change.labels) { continue; }
 | |
|         const currentLabels = Object.keys(change.labels);
 | |
|         labels = labels.concat(currentLabels.filter(nonExistingLabel));
 | |
|       }
 | |
|     }
 | |
|     return labels.sort();
 | |
|   }
 | |
| 
 | |
|   _computeLabelShortcut(labelName) {
 | |
|     if (labelName.startsWith(LABEL_PREFIX_INVALID_PROLOG)) {
 | |
|       labelName = labelName.slice(LABEL_PREFIX_INVALID_PROLOG.length);
 | |
|     }
 | |
|     return labelName.split('-')
 | |
|         .reduce((a, i) => {
 | |
|           if (!i) { return a; }
 | |
|           return a + i[0].toUpperCase();
 | |
|         }, '')
 | |
|         .slice(0, MAX_SHORTCUT_CHARS);
 | |
|   }
 | |
| 
 | |
|   _changesChanged(changes) {
 | |
|     this.sections = changes ? [{results: changes}] : [];
 | |
|   }
 | |
| 
 | |
|   _processQuery(query) {
 | |
|     let tokens = query.split(' ');
 | |
|     const invalidTokens = ['limit:', 'age:', '-age:'];
 | |
|     tokens = tokens.filter(token => !invalidTokens
 | |
|         .some(invalidToken => token.startsWith(invalidToken)));
 | |
|     return tokens.join(' ');
 | |
|   }
 | |
| 
 | |
|   _sectionHref(query) {
 | |
|     return GerritNav.getUrlForSearchQuery(this._processQuery(query));
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Maps an index local to a particular section to the absolute index
 | |
|    * across all the changes on the page.
 | |
|    *
 | |
|    * @param {number} sectionIndex index of section
 | |
|    * @param {number} localIndex index of row within section
 | |
|    * @return {number} absolute index of row in the aggregate dashboard
 | |
|    */
 | |
|   _computeItemAbsoluteIndex(sectionIndex, localIndex) {
 | |
|     let idx = 0;
 | |
|     for (let i = 0; i < sectionIndex; i++) {
 | |
|       idx += this.sections[i].results.length;
 | |
|     }
 | |
|     return idx + localIndex;
 | |
|   }
 | |
| 
 | |
|   _computeItemSelected(sectionIndex, index, selectedIndex) {
 | |
|     const idx = this._computeItemAbsoluteIndex(sectionIndex, index);
 | |
|     return idx == selectedIndex;
 | |
|   }
 | |
| 
 | |
|   _computeItemNeedsReview(account, change, showReviewedState) {
 | |
|     return showReviewedState && !change.reviewed &&
 | |
|         !change.work_in_progress &&
 | |
|         this.changeIsOpen(change) &&
 | |
|         (!account || account._account_id != change.owner._account_id);
 | |
|   }
 | |
| 
 | |
|   _computeItemHighlight(account, change) {
 | |
|     // Do not show the assignee highlight if the change is not open.
 | |
|     if (!change ||!change.assignee ||
 | |
|         !account ||
 | |
|         CLOSED_STATUS.indexOf(change.status) !== -1) {
 | |
|       return false;
 | |
|     }
 | |
|     return account._account_id === change.assignee._account_id;
 | |
|   }
 | |
| 
 | |
|   _nextChange(e) {
 | |
|     if (this.shouldSuppressKeyboardShortcut(e) ||
 | |
|         this.modifierPressed(e)) { return; }
 | |
| 
 | |
|     e.preventDefault();
 | |
|     this.$.cursor.next();
 | |
|   }
 | |
| 
 | |
|   _prevChange(e) {
 | |
|     if (this.shouldSuppressKeyboardShortcut(e) ||
 | |
|         this.modifierPressed(e)) { return; }
 | |
| 
 | |
|     e.preventDefault();
 | |
|     this.$.cursor.previous();
 | |
|   }
 | |
| 
 | |
|   _openChange(e) {
 | |
|     if (this.shouldSuppressKeyboardShortcut(e) ||
 | |
|         this.modifierPressed(e)) { return; }
 | |
| 
 | |
|     e.preventDefault();
 | |
|     GerritNav.navigateToChange(this._changeForIndex(this.selectedIndex));
 | |
|   }
 | |
| 
 | |
|   _nextPage(e) {
 | |
|     if (this.shouldSuppressKeyboardShortcut(e) ||
 | |
|         this.modifierPressed(e) && !this.isModifierPressed(e, 'shiftKey')) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     e.preventDefault();
 | |
|     this.dispatchEvent(new CustomEvent('next-page', {
 | |
|       composed: true, bubbles: true,
 | |
|     }));
 | |
|   }
 | |
| 
 | |
|   _prevPage(e) {
 | |
|     if (this.shouldSuppressKeyboardShortcut(e) ||
 | |
|         this.modifierPressed(e) && !this.isModifierPressed(e, 'shiftKey')) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     e.preventDefault();
 | |
|     this.dispatchEvent(new CustomEvent('previous-page', {
 | |
|       composed: true, bubbles: true,
 | |
|     }));
 | |
|   }
 | |
| 
 | |
|   _toggleChangeReviewed(e) {
 | |
|     if (this.shouldSuppressKeyboardShortcut(e) ||
 | |
|         this.modifierPressed(e)) { return; }
 | |
| 
 | |
|     e.preventDefault();
 | |
|     this._toggleReviewedForIndex(this.selectedIndex);
 | |
|   }
 | |
| 
 | |
|   _toggleReviewedForIndex(index) {
 | |
|     const changeEls = this._getListItems();
 | |
|     if (index >= changeEls.length || !changeEls[index]) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     const changeEl = changeEls[index];
 | |
|     changeEl.toggleReviewed();
 | |
|   }
 | |
| 
 | |
|   _refreshChangeList(e) {
 | |
|     if (this.shouldSuppressKeyboardShortcut(e)) { return; }
 | |
| 
 | |
|     e.preventDefault();
 | |
|     this._reloadWindow();
 | |
|   }
 | |
| 
 | |
|   _reloadWindow() {
 | |
|     window.location.reload();
 | |
|   }
 | |
| 
 | |
|   _toggleChangeStar(e) {
 | |
|     if (this.shouldSuppressKeyboardShortcut(e) ||
 | |
|         this.modifierPressed(e)) { return; }
 | |
| 
 | |
|     e.preventDefault();
 | |
|     this._toggleStarForIndex(this.selectedIndex);
 | |
|   }
 | |
| 
 | |
|   _toggleStarForIndex(index) {
 | |
|     const changeEls = this._getListItems();
 | |
|     if (index >= changeEls.length || !changeEls[index]) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     const changeEl = changeEls[index];
 | |
|     changeEl.shadowRoot
 | |
|         .querySelector('gr-change-star').toggleStar();
 | |
|   }
 | |
| 
 | |
|   _changeForIndex(index) {
 | |
|     const changeEls = this._getListItems();
 | |
|     if (index < changeEls.length && changeEls[index]) {
 | |
|       return changeEls[index].change;
 | |
|     }
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   _getListItems() {
 | |
|     return Array.from(
 | |
|         dom(this.root).querySelectorAll('gr-change-list-item'));
 | |
|   }
 | |
| 
 | |
|   _sectionsChanged() {
 | |
|     // Flush DOM operations so that the list item elements will be loaded.
 | |
|     afterNextRender(this, () => {
 | |
|       this.$.cursor.stops = this._getListItems();
 | |
|       this.$.cursor.moveToStart();
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   _isOutgoing(section) {
 | |
|     return !!section.isOutgoing;
 | |
|   }
 | |
| 
 | |
|   _isEmpty(section) {
 | |
|     return !section.results.length;
 | |
|   }
 | |
| }
 | |
| 
 | |
| customElements.define(GrChangeList.is, GrChangeList);
 |