| // Copyright (C) 2017 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. |
| |
| Gerrit.install(function(self) { |
| // TODO: ideally we want to turn off find owners when code owners enabled |
| // but since they are two plugins right now, we just keep find owners around |
| // for a while |
| // const ENABLED_EXPERIMENTS = window.ENABLED_EXPERIMENTS || []; |
| // if (ENABLED_EXPERIMENTS.includes('UiFeature__plugin_code_owners')) { |
| // // Disable if experiment UiFeature__plugin_code_owners is enabled |
| // // Also hide previous 'Find Owners' button under 'MORE'. |
| // function removeFindOwnerFromSecondaryActions() { |
| // var changeActions = self.changeActions(); |
| // changeActions.setActionHidden('revision', 'find-owners~find-owners', true); |
| // } |
| // self.on('showchange', removeFindOwnerFromSecondaryActions); |
| // return; |
| // } |
| |
| // If context.popup API exists and popup content is small, |
| // use the API and set useContextPopup, |
| // otherwise, use pageDiv and set its visibility. |
| var useContextPopup = false; |
| var pageDiv = document.createElement('div'); |
| document.body.appendChild(pageDiv); |
| function hideFindOwnersPage() { |
| pageDiv.style.visibility = 'hidden'; |
| } |
| |
| // Temporary functions - checks if code owners are enabled for a change |
| function getIsCodeOwnerPluginEnabledForChange(change) { |
| const codeOwners = window.__gerrit_code_owners_plugin; |
| if (!codeOwners) { |
| // Code-owners plugin is not installed |
| return Promise.resolve(false); |
| } |
| return new Promise(resolve => { |
| |
| const stateChangedListener = () => { |
| if (codeOwners.state.change?.id === change.id) { |
| // The code-owners plugin might have a delay with updating its |
| // state. Ensure that it contains state for the requested change. |
| codeOwners.stateChanged.removeEventListener('state-changed', |
| stateChangedListener); |
| return void resolve(codeOwners.state.branchState !== 'DISABLED'); |
| } |
| } |
| codeOwners.stateChanged.addEventListener('state-changed', |
| stateChangedListener); |
| // If state has been already updated, the 'state-changed' event is not |
| // fired. Call stateChangedListener to check curent state. |
| stateChangedListener(); |
| }); |
| } |
| |
| function popupFindOwnersPage(context, change, revision, onSubmit) { |
| const PADDING = 5; |
| const LARGE_PAGE_STYLE = Gerrit.css( |
| 'visibility: hidden;' + |
| 'background: rgba(200, 200, 200, 0.95);' + |
| 'border: 3px solid;' + |
| 'border-color: #c8c8c8;' + |
| 'border-radius: 3px;' + |
| 'position: fixed;' + |
| 'z-index: 100;' + |
| 'overflow: auto;' + |
| 'padding: ' + PADDING + 'px;'); |
| const BUTTON_STYLE = Gerrit.css( |
| 'background-color: #4d90fe;' + |
| 'border: 2px solid;' + |
| 'border-color: #4d90fe;' + |
| 'margin: 2px 10px 2px 10px;' + |
| 'text-align: center;' + |
| 'font-size: 8pt;' + |
| 'font-weight: bold;' + |
| 'color: #fff;' + |
| '-webkit-border-radius: 2px;' + |
| 'cursor: pointer;'); |
| const createElWithText = (tagName, text) => { |
| const el = document.createElement(tagName); |
| if (text) el.textContent = text; |
| return el; |
| }; |
| |
| const createBulletEl = () => { |
| return createElWithText('small', '\u2605'); // a Black Star |
| }; |
| const createIsExemptedEl = () => { |
| return createElWithText('b', 'This change is Exempt-From-Owner-Approval.'); |
| }; |
| |
| const createNoOwnerEl = () => { |
| return createElWithText('b', 'No owner was found for changed files.'); |
| }; |
| |
| const createOnSubmitHeader = () => { |
| return createElWithText( |
| 'b', 'WARNING: Need owner Code-Review vote before submit.'); |
| }; |
| |
| const createOwnersHeaderEl = () => { |
| return createElWithText('b', 'Owners in alphabetical order:'); |
| }; |
| |
| const createSelectReviewersEl = (minCRVote) => { |
| const el = document.createElement('p'); |
| el.appendChild(createElWithText( |
| 'b', |
| 'Check the box before owner names to select reviewers, ' + |
| 'then click the "Apply" button.')); |
| el.appendChild(createElWithText('br')); |
| const smallEl = createElWithText( |
| 'small', |
| 'If owner-approval requirement is enabled, ' + |
| `each file needs at least one Code-Review +${minCRVote} vote from an owner. ` + |
| 'Owners listed after a file are ordered by their importance. ' + |
| '(Or declare "'); |
| const smallerEl = createElWithText('b', 'Exempt-From-Owner-Approval:'); |
| smallerEl.style.fontSize = '80%'; |
| smallEl.appendChild(smallerEl); |
| smallEl.appendChild(createElWithText('i', 'reasons...')); |
| smallEl.appendChild(document.createTextNode('" in the commit message.)')); |
| el.appendChild(smallEl); |
| el.appendChild(createElWithText('br')); |
| return el; |
| }; |
| |
| // Changed files are put into groups. |
| // Each group has a unique list of owners and |
| // belongs to one of the 5 types based on owner status. |
| // Groups of a type are displayed in one HTML section. |
| // Group type names are mapped to ordered numbers starting from 0. |
| const GROUP_TYPE = { |
| 'NEED_REVIEWER': 0, // no owner in Reviewers list yet |
| 'NEED_APPROVAL': 1, // no owner Code-Review +1 yet |
| 'STAR_APPROVED': 2, // has '*', no need of owner vote |
| 'OWNER_APPROVED': 3, // has owner approval |
| 'HAS_NO_OWNER': 4, // no owner at all, only shown with other types |
| }; |
| const NUM_GROUP_TYPES = 5; |
| |
| const createGroupTypeHeaderEl = (text) => { |
| const el = createElWithText('span'); |
| el.appendChild(createElWithText('hr')); |
| el.appendChild(createElWithText('b', text)); |
| el.appendChild(createElWithText('br')); |
| return el; |
| }; |
| const HTML_GROUP_TYPE_HEADER = [ |
| createGroupTypeHeaderEl( |
| 'Files with owners but no owner is in the Reviewers list:'), |
| createGroupTypeHeaderEl( |
| 'Files with owners but no Code-Review vote from an owner:'), |
| createGroupTypeHeaderEl( |
| 'Files with owners but can be approved by anyone (*):'), |
| createGroupTypeHeaderEl('Files with required Code-Review vote from owners:'), |
| createGroupTypeHeaderEl('Files without any named owner:'), |
| ]; |
| |
| const GROUP_TYPE_DIV_ID = [ |
| 'FindOwners:NeedReviewer', |
| 'FindOwners:NeedApproval', |
| 'FindOwners:StarApproved', |
| 'FindOwners:OwnerApproved', |
| 'FindOwners:HasNoOwner', |
| ]; |
| |
| const APPLY_BUTTON_ID = 'FindOwners:Apply'; |
| const CHECKBOX_ID = 'FindOwners:CheckBox'; |
| const HEADER_DIV_ID = 'FindOwners:Header'; |
| const OWNERS_DIV_ID = 'FindOwners:Owners'; |
| |
| // Aliases to values in the context. |
| const branch = change.branch; |
| const changeId = change._number; |
| const changeOwner = change.owner; |
| const message = revision.commit.message; |
| const project = change.project; |
| |
| var minVoteLevel = 1; // could be changed by server returned results. |
| var reviewerId = {}; // map from a reviewer's email to account id. |
| var reviewerVote = {}; // map from a reviewer's email to Code-Review vote. |
| |
| // addList and removeList are used only under applySelections. |
| var addList = []; // remain emails to add to reviewers |
| var removeList = []; // remain emails to remove from reviewers |
| var needRefresh = false; // true if to refresh after checkAddRemoveLists |
| |
| function getElement(id) { |
| return document.getElementById(id); |
| } |
| function restApiGet(url, callback) { |
| self.restApi().get('/../..' + url).then(callback); |
| } |
| function restApiPost(url, data, callback) { |
| self.restApi().post('/../..' + url, data).then(callback); |
| } |
| function restApiDelete(url, callback, errMessage) { |
| self.restApi().delete('/../..' + url).then(callback).catch((e) => { |
| alert(errMessage); |
| }); |
| } |
| function getReviewers(change, callBack) { |
| restApiGet('/changes/' + change + '/reviewers', callBack); |
| } |
| function setupReviewersMap(reviewerList) { |
| reviewerId = {}; |
| reviewerVote = {}; |
| reviewerList.forEach(function(reviewer) { |
| if ('email' in reviewer && '_account_id' in reviewer) { |
| reviewerId[reviewer.email] = reviewer._account_id; |
| reviewerVote[reviewer.email] = 0; |
| if ('approvals' in reviewer && 'Code-Review' in reviewer.approvals) { |
| reviewerVote[reviewer.email] = |
| parseInt(reviewer.approvals['Code-Review']); |
| // The 'Code-Review' values could be "-2", "-1", " 0", "+1", "+2" |
| } |
| } |
| }); |
| // Give CL author a default minVoteLevel vote. |
| if (changeOwner != null && 'email' in changeOwner && |
| '_account_id' in changeOwner && |
| (!(changeOwner.email in reviewerId) || |
| reviewerVote[changeOwner.email] == 0)) { |
| reviewerId[changeOwner.email] = changeOwner._account_id; |
| reviewerVote[changeOwner.email] = minVoteLevel; |
| } |
| } |
| function checkAddRemoveLists() { |
| // Gerrit.post and delete are asynchronous. |
| // Do one at a time, with checkAddRemoveLists as callBack. |
| for (var i = 0; i < addList.length; i++) { |
| var email = addList[i]; |
| if (!(email in reviewerId)) { |
| addList = addList.slice(i + 1, addList.length); |
| // A post request can fail if given reviewer email is invalid. |
| // Gerrit core UI shows the error dialog and does not provide |
| // a way for plugins to handle the error yet. |
| needRefresh = true; |
| restApiPost( |
| '/changes/' + changeId + '/reviewers', {'reviewer': email}, |
| checkAddRemoveLists); |
| return; |
| } |
| } |
| for (var i = 0; i < removeList.length; i++) { |
| var email = removeList[i]; |
| if (email in reviewerId) { |
| removeList = removeList.slice(i + 1, removeList.length); |
| needRefresh = true; |
| restApiDelete( |
| '/changes/' + changeId + '/reviewers/' + reviewerId[email], |
| checkAddRemoveLists, 'Cannot delete reviewer: ' + email); |
| return; |
| } |
| } |
| hideFindOwnersPage(); |
| if (needRefresh) { |
| needRefresh = false; |
| (!!Gerrit.refresh) ? Gerrit.refresh() : location.reload(); |
| } |
| callServer(showFindOwnersResults); |
| } |
| function applyGetReviewers(reviewerList) { |
| setupReviewersMap(reviewerList); |
| checkAddRemoveLists(); // update and pop up window at the end |
| } |
| function hasStar(owners) { |
| return owners.some(function(owner) { |
| return owner == '*'; |
| }); |
| } |
| function hasNamedOwner(owners) { |
| return owners.some(function(owner) { |
| return owner != '*'; |
| }); |
| } |
| function hasOwnerReviewer(reviewers, owners) { |
| return owners.some(function(owner) { |
| return owner in reviewers; |
| }); |
| } |
| function hasOwnerApproval(votes, owners) { |
| var foundApproval = false; |
| for (var j = 0; j < owners.length; j++) { |
| if (owners[j] in votes) { |
| var v = votes[owners[j]]; |
| if (v < 0) { |
| return false; // cannot have any negative vote |
| } |
| foundApproval |= v >= minVoteLevel; |
| } |
| } |
| return foundApproval; |
| } |
| function isExemptedFromOwnerApproval() { |
| return message.match(/(Exempted|Exempt)-From-Owner-Approval:/); |
| } |
| function strElement(s) { |
| var e = document.createElement('span'); |
| e.innerHTML = s; |
| return e; |
| } |
| function newButton(name, action) { |
| var b = document.createElement('button'); |
| b.appendChild(document.createTextNode(name)); |
| b.className = BUTTON_STYLE; |
| b.onclick = action; |
| b.style.display = 'inline'; |
| b.style.float = 'right'; |
| return b; |
| } |
| function showJsonLines(args, key, obj) { |
| showBoldKeyValueLines(args, key, JSON.stringify(obj, null, 2)); |
| } |
| function showBoldKeyValueLines(args, key, value) { |
| args.push( |
| createElWithText('hr'), strElement('<b>' + key + '</b>:'), |
| createElWithText('br')); |
| value.split('\n').forEach(function(line) { |
| args.push(strElement(line), createElWithText('br')); |
| }); |
| } |
| function showDebugMessages(result, args) { |
| function addKeyValue(key, value) { |
| args.push(strElement('<b>' + key + '</b>: ' + value + '<br>')); |
| } |
| args.push(createElWithText('hr')); |
| addKeyValue('changeId', changeId); |
| addKeyValue('project', project); |
| addKeyValue('branch', branch); |
| addKeyValue('changeOwner.email', changeOwner.email); |
| addKeyValue('Gerrit.url', Gerrit.url()); |
| addKeyValue('self.url', self.url()); |
| showJsonLines(args, 'changeOwner', change.owner); |
| showBoldKeyValueLines(args, 'commit.message', message); |
| showJsonLines(args, 'Client reviewers Ids', reviewerId); |
| showJsonLines(args, 'Client reviewers Votes', reviewerVote); |
| Object.keys(result).forEach(function(k) { |
| showJsonLines(args, 'Server.' + k, result[k]); |
| }); |
| } |
| function showFilesAndOwners(result, args) { |
| var sortedOwners = result.owners.map(function(ownerInfo) { |
| return ownerInfo.email; |
| }); |
| var groups = {}; // a map from group_name to |
| // {'type': 0..(NUM_GROUP_TYPES-1), |
| // 'size': num_of_files_in_this_group, |
| // 'owners': space_separated_owner_emails} |
| var header = emptyDiv(HEADER_DIV_ID); |
| var groupTypeDiv = Array(NUM_GROUP_TYPES); |
| for (var i = 0; i < NUM_GROUP_TYPES; i++) { |
| groupTypeDiv[i] = emptyDiv(GROUP_TYPE_DIV_ID[i]); |
| } |
| |
| var cancelButton = newButton('Cancel', hideFindOwnersPage); |
| header.appendChild(cancelButton); |
| addApplyButton(); |
| |
| var ownersDiv = emptyDiv(OWNERS_DIV_ID); |
| var numCheckBoxes = 0; |
| var owner2boxes = {}; // owner name ==> array of checkbox id |
| var owner2email = {}; // owner name ==> email address |
| minVoteLevel = |
| ('minOwnerVoteLevel' in result ? result.minOwnerVoteLevel : 1); |
| |
| function addApplyButton() { |
| var apply = newButton('Apply', doApplyButton); |
| apply.id = APPLY_BUTTON_ID; |
| apply.style.display = 'none'; |
| header.appendChild(apply); |
| } |
| function emptyDiv(id) { |
| var e = document.createElement('div'); |
| e.id = id; |
| e.style.display = 'none'; |
| args.push(e); |
| return e; |
| } |
| function colorSpan(str, color) { |
| const el = createElWithText('span', str) |
| el.style.color = color; |
| return el; |
| } |
| function doApplyButton() { |
| addList = []; |
| removeList = []; |
| // add each owner's email address to addList or removeList |
| Object.keys(owner2boxes).forEach(function(owner) { |
| (getElement(owner2boxes[owner][0]).checked ? addList : removeList) |
| .push(owner2email[owner]); |
| }); |
| getReviewers(changeId, applyGetReviewers); |
| } |
| function clickBox(event) { |
| var name = event.target.value; |
| var checked = event.target.checked; |
| var others = owner2boxes[name]; |
| others.forEach(function(id) { |
| getElement(id).checked = checked; |
| }); |
| getElement(APPLY_BUTTON_ID).style.display = 'inline'; |
| } |
| function addGroupsToDiv(div, keys, titleEl) { |
| if (keys.length <= 0) { |
| div.style.display = 'none'; |
| return; |
| } |
| div.innerHTML = ''; |
| div.style.display = 'inline'; |
| div.appendChild(titleEl); |
| function addOwner(itemDiv, ownerEmail) { |
| if (ownerEmail == '*') { |
| return; // no need to list/select '*' |
| } |
| numCheckBoxes++; |
| var name = ownerEmail.replace(/@[^ ]*/g, ''); |
| owner2email[name] = ownerEmail; |
| var id = CHECKBOX_ID + ':' + numCheckBoxes; |
| if (!(name in owner2boxes)) { |
| owner2boxes[name] = []; |
| } |
| owner2boxes[name].push(id); |
| |
| var box = document.createElement('input'); |
| box.type = 'checkbox'; |
| box.checked = (ownerEmail in reviewerId); |
| box.id = id; |
| box.value = name; |
| box.onclick = clickBox; |
| itemDiv.appendChild(createElWithText('span', '\u00a0\u00a0')); |
| var nobr = document.createElement('nobr'); |
| nobr.appendChild(box); |
| nobr.appendChild(strElement(name)); |
| itemDiv.appendChild(nobr); |
| } |
| keys.forEach(function(key) { |
| var owners = groups[key].owners; // string of owner emails |
| var numFiles = groups[key].size; |
| |
| var itemEl = createElWithText('span'); |
| itemEl.appendChild(createBulletEl()); |
| itemEl.appendChild(document.createTextNode('\u00a0')); |
| itemEl.appendChild(createElWithText('b', key)); |
| itemEl.appendChild(document.createTextNode( |
| ((numFiles > 1) ? (' (' + numFiles + ' files)') : ''))); |
| var setOfOwners = new Set(owners.split(' ')); |
| function add2list(list, email) { |
| if (setOfOwners.has(email)) { |
| list.push(email); |
| } |
| return list; |
| } |
| var reducedList = sortedOwners.reduce(add2list, []); |
| if (hasNamedOwner(reducedList)) { |
| itemEl.appendChild(document.createTextNode(':')); |
| } |
| |
| let itemDiv = document.createElement('div'); |
| itemDiv.style.paddingTop = '0.5em'; |
| itemDiv.appendChild(itemEl); |
| itemDiv.appendChild(createElWithText('br')); |
| reducedList.forEach(addOwner.bind(this, itemDiv)); |
| div.appendChild(itemDiv); |
| }); |
| div.lastElementChild.style.paddingBottom = '0.5em'; |
| } |
| function addOwnersDiv(div, titleEl) { |
| div.textContent = ''; |
| div.style.display = 'inline'; |
| div.appendChild(titleEl); |
| div.appendChild(createElWithText('br')); |
| function compareOwnerInfo(o1, o2) { |
| return o1.email.localeCompare(o2.email); |
| } |
| result.owners.sort(compareOwnerInfo).forEach(function(ownerInfo) { |
| var email = ownerInfo.email; |
| var emailEl = createElWithText('span', email); |
| if (email != '*') { // do not list special email * |
| var vote = reviewerVote[email]; |
| if ((email in reviewerVote) && vote != 0) { |
| if (vote > 0) { |
| emailEl.appendChild( |
| colorSpan('\u00a0(+' + vote + ')', 'green')); |
| } else { |
| emailEl.appendChild(colorSpan('\u00a0(' + vote + ')', 'red')); |
| } |
| } |
| const spanEl = createElWithText('span'); |
| spanEl.appendChild(document.createTextNode('\u00a0\u00a0')); |
| spanEl.appendChild(emailEl); |
| spanEl.appendChild(createElWithText('br')); |
| div.appendChild(spanEl); |
| } |
| }); |
| } |
| function updateDivContent() { |
| var listOfGroup = Array(NUM_GROUP_TYPES); |
| for (var i = 0; i < NUM_GROUP_TYPES; i++) { |
| listOfGroup[i] = []; |
| } |
| Object.keys(groups).sort().forEach(function(key) { |
| listOfGroup[groups[key].type].push(key); |
| }); |
| |
| // Add message to header div and make visible. |
| let headerMessageDiv = document.createElement('div'); |
| if (isExemptedFromOwnerApproval()) { |
| headerMessageDiv.appendChild(createIsExemptedEl()); |
| } else { |
| if (onSubmit) { |
| headerMessageDiv.appendChild(createOnSubmitHeader()); |
| } |
| headerMessageDiv.appendChild(createSelectReviewersEl(minVoteLevel)); |
| } |
| header.appendChild(headerMessageDiv); |
| header.style.display = 'inline'; |
| |
| numCheckBoxes = 0; |
| owner2boxes = {}; |
| for (var i = 0; i < NUM_GROUP_TYPES; i++) { |
| addGroupsToDiv( |
| groupTypeDiv[i], listOfGroup[i], HTML_GROUP_TYPE_HEADER[i]); |
| } |
| addOwnersDiv(ownersDiv, createOwnersHeaderEl()); |
| } |
| function createGroups() { |
| var owners2group = {}; // owner list to group name |
| var firstNoOwnerFile = null; |
| var keysOfFile2Owners = Object.keys(result.file2owners); |
| keysOfFile2Owners.sort().forEach(function(name) { |
| var splitOwners = result.file2owners[name]; |
| var owners = splitOwners.join(' '); |
| if (owners in owners2group) { |
| groups[owners2group[owners]].size += 1; |
| } else { |
| owners2group[owners] = name; |
| var type; |
| if (!hasNamedOwner(splitOwners)) { |
| firstNoOwnerFile = name; |
| type = GROUP_TYPE.HAS_NO_OWNER; |
| } else if (hasOwnerApproval(reviewerVote, splitOwners)) { |
| type = GROUP_TYPE.OWNER_APPROVED; |
| } else if (hasStar(splitOwners)) { |
| type = GROUP_TYPE.STAR_APPROVED; |
| } else if (!hasOwnerReviewer(reviewerId, splitOwners)) { |
| type = GROUP_TYPE.NEED_REVIEWER; |
| } else { |
| type = GROUP_TYPE.NEED_APPROVAL; |
| } |
| groups[name] = {'type': type, 'size': 1, 'owners': owners}; |
| } |
| }); |
| var numNoOwnerFiles = result.files.length - keysOfFile2Owners.length; |
| if (keysOfFile2Owners.length > 0 && numNoOwnerFiles > 0) { |
| if (!!firstNoOwnerFile) { |
| // count other files as HAS_NO_OWNER |
| groups[firstNoOwnerFile].size += numNoOwnerFiles; |
| } else { |
| // use one of the no-owner-file as group name |
| for (var i = 0; i < result.files.length; i++) { |
| var name = result.files[i]; |
| if (!(name in result.file2owners) && |
| !(('./' + name) in result.file2owners)) { |
| groups[name] = { |
| 'type': GROUP_TYPE.HAS_NO_OWNER, |
| 'size': numNoOwnerFiles, |
| 'owners': '*', |
| }; |
| break; |
| } |
| } |
| } |
| } |
| } |
| createGroups(); |
| updateDivContent(); |
| } |
| function showFindOwnersResults(result) { |
| function prepareElements() { |
| var elems = []; |
| var textEl = |
| Object.keys(result.file2owners).length <= 0 ? createNoOwnerEl() : null; |
| useContextPopup = !!context && !!textEl && !!context.popup; |
| if (!!textEl) { |
| if (useContextPopup) { |
| elems.push(createElWithText('hr'), textEl, createElWithText('hr')); |
| var onClick = function() { |
| context.hide(); |
| }; |
| elems.push( |
| context.button('OK', {onclick: onClick}), createElWithText('hr')); |
| } else { |
| elems.push(textEl, newButton('OK', hideFindOwnersPage)); |
| } |
| } else { |
| showFilesAndOwners(result, elems); |
| if (result.addDebugMsg) { |
| showDebugMessages(result, elems); |
| } |
| } |
| return elems; |
| } |
| function popupWindow(reviewerList) { |
| setupReviewersMap(reviewerList); |
| var elems = prepareElements(); |
| if (useContextPopup) { |
| context.popup(context.div.apply(this, elems)); |
| } else { |
| while (pageDiv.firstChild) { |
| pageDiv.removeChild(pageDiv.firstChild); |
| } |
| elems.forEach(function(e) { |
| pageDiv.appendChild(e); |
| }); |
| pageDiv.className = LARGE_PAGE_STYLE; |
| // Calculate required height, limited to 85% of window height, |
| // and required width, limited to 75% of window width. |
| var maxHeight = Math.round(window.innerHeight * 0.85); |
| var maxWidth = Math.round(window.innerWidth * 0.75); |
| pageDiv.style.top = '5%'; |
| pageDiv.style.height = 'auto'; |
| pageDiv.style.left = '10%'; |
| pageDiv.style.width = 'auto'; |
| var rect = pageDiv.getBoundingClientRect(); |
| if (rect.width > maxWidth) { |
| pageDiv.style.width = maxWidth + 'px'; |
| rect = pageDiv.getBoundingClientRect(); |
| } |
| pageDiv.style.left = |
| Math.round((window.innerWidth - rect.width) / 2) + 'px'; |
| if (rect.height > maxHeight) { |
| pageDiv.style.height = maxHeight + 'px'; |
| rect = pageDiv.getBoundingClientRect(); |
| } |
| pageDiv.style.top = |
| Math.round((window.innerHeight - rect.height) / 2) + 'px'; |
| pageDiv.style.visibility = 'visible'; |
| } |
| } |
| getReviewers(changeId, popupWindow); |
| } |
| function callServer(callBack) { |
| // Use the plugin REST API; pass only changeId; |
| // let server get current patch set, project and branch info. |
| restApiGet('/changes/' + changeId + '/owners', callBack); |
| } |
| |
| callServer(showFindOwnersResults); |
| } |
| // Stores information about the last change passed to onShowChangePolyGerrit |
| let lastChangeInfo = null; |
| function onSubmit(change, revision) { |
| // onShowChangePolyGerrit is always called after all plugins are loaded. |
| // However, onSubmit can be called earlier (thought it is unlikely) and |
| // lastChangeInfo can be null. |
| if(lastChangeInfo && lastChangeInfo.ownerPlugin === 'code-owners') { |
| return true; |
| } |
| const OWNER_REVIEW_LABEL = 'Owner-Review-Vote'; |
| if (change.labels.hasOwnProperty(OWNER_REVIEW_LABEL)) { |
| if(lastChangeInfo && lastChangeInfo.ownerPlugin === 'find-owners') { |
| popupFindOwnersPage(null, change, revision, true); |
| } else { |
| // Usually, the api returns required config very fast, so this is |
| // a very rare when user will see this message. |
| document.dispatchEvent( |
| new CustomEvent('show-error', { |
| detail: { |
| message: |
| 'Some requests to code-owners are still in progress, ' + |
| 'please try to submit again in a few seconds.\n' + |
| 'The host has both find-owners and code-owners plugin enabled.' + |
| 'The Submit is not possible until requests are completed. ', |
| composed: true, |
| bubbles: true, |
| } |
| } |
| ) |
| ); |
| } |
| return false; |
| } |
| return true; // Okay to submit. |
| } |
| var actionKey = null; |
| function onShowChangePolyGerrit(change, revision) { |
| if (lastChangeInfo) { |
| // If previous request was too slow, user can have enough time for |
| // switching to another view. Cancel previos request. |
| lastChangeInfo.cancelled = true; |
| } |
| const changeInfo = { |
| change, |
| cancelled: false, |
| } |
| lastChangeInfo = changeInfo; |
| var changeActions = self.changeActions(); |
| // Hide previous 'Find Owners' button under 'MORE'. |
| changeActions.setActionHidden('revision', 'find-owners~find-owners', true); |
| if (!!actionKey) { |
| changeActions.removeTapListener(actionKey); |
| changeActions.remove(actionKey); |
| } |
| getIsCodeOwnerPluginEnabledForChange(change).then((codeOwnersEnabled) => { |
| changeInfo.ownerPlugin = codeOwnersEnabled ? 'code-owners' : 'find-owners'; |
| if (changeInfo.cancelled || changeInfo.ownerPlugin === 'code-owners') { |
| return; |
| } |
| actionKey = changeActions.add('revision', '[FIND OWNERS]'); |
| changeActions.setIcon(actionKey, 'robot'); |
| changeActions.setTitle(actionKey, 'Find owners of changed files'); |
| changeActions.addTapListener(actionKey, (e) => { |
| if (e) e.stopPropagation(); |
| |
| popupFindOwnersPage(null, change, revision, false); |
| }); |
| }); |
| } |
| function onClick(event) { |
| if (pageDiv.style.visibility != 'hidden' && !useContextPopup) { |
| var x = event.clientX; |
| var y = event.clientY; |
| var rect = pageDiv.getBoundingClientRect(); |
| if (x < rect.left || x >= rect.left + rect.width || y < rect.top || |
| y >= rect.top + rect.height) { |
| hideFindOwnersPage(); |
| } |
| } |
| } |
| if (window.Polymer) { |
| self.on('showchange', onShowChangePolyGerrit); |
| } else { |
| console.log('WARNING, no [FIND OWNERS] button'); |
| } |
| // When the "Submit" button is clicked, call onSubmit. |
| self.on('submitchange', onSubmit); |
| // Clicks outside the pop up window should close the window. |
| document.body.addEventListener('click', onClick); |
| // Leaving page should close the window. |
| window.addEventListener('popstate', hideFindOwnersPage); |
| }); |