Let users know when a file can be approved by anyone
Change-Id: I9ff255db97ab15582164ba6e1198e2b432e10bb7
diff --git a/ui/code-owners-service.js b/ui/code-owners-service.js
index 5f6bc92..633ffbc 100644
--- a/ui/code-owners-service.js
+++ b/ui/code-owners-service.js
@@ -109,7 +109,7 @@
listOwnersForPath(changeId, path) {
return this.restApi.get(
`/changes/${changeId}/revisions/current/code_owners` +
- `/${encodeURIComponent(path)}?limit=5&o=DETAILS&resolve-all-users=true`
+ `/${encodeURIComponent(path)}?limit=5&o=DETAILS`
);
}
@@ -319,11 +319,7 @@
try {
const owners = await this.codeOwnerApi.listOwnersForPath(changeId,
filePath);
- // In the upcoming backend changes, the api will return an object instead
- // of array. While this transition is in progress, the frontend supports
- // both API - the old one and the new one.
- this._fetchedOwners.get(filePath).owners = new Set(
- owners instanceof Array ? owners : owners.code_owners);
+ this._fetchedOwners.get(filePath).owners = owners;
} catch (e) {
this._fetchedOwners.get(filePath).error = e;
}
@@ -566,14 +562,11 @@
if (!file.info.owners) {
continue;
}
- const owners = [...file.info.owners];
- const ownersKey = owners
- .map(owner => owner.account._account_id)
- .sort()
- .join(',');
+
+ const ownersKey = this._getOwnersGroupKey(file.info.owners);
ownersFilesMap.set(
ownersKey,
- ownersFilesMap.get(ownersKey) || {files: [], owners}
+ ownersFilesMap.get(ownersKey) || {files: [], owners: file.info.owners}
);
ownersFilesMap.get(ownersKey).files.push(fileInfo);
}
@@ -600,6 +593,17 @@
return groupedItems;
}
+ _getOwnersGroupKey(owners) {
+ if (owners.owned_by_all_users) {
+ return '__owned_by_all_users__';
+ }
+ const code_owners = owners.code_owners;
+ return code_owners
+ .map(owner => owner.account._account_id)
+ .sort()
+ .join(',');
+ }
+
getGroupName(files) {
const fileName = files[0].path.split('/').pop();
return {
diff --git a/ui/suggest-owners.js b/ui/suggest-owners.js
index 2395476..91c6f96 100644
--- a/ui/suggest-owners.js
+++ b/ui/suggest-owners.js
@@ -165,11 +165,19 @@
flex: 1;
}
.fetch-error-content,
+ .owned-by-all-users-content,
.no-owners-content {
line-height: 26px;
flex: 1;
padding-left: var(--spacing-m);
}
+
+ .owned-by-all-users-content iron-icon {
+ width: 16px;
+ height: 16px;
+ padding-top: 5px;
+ }
+
.fetch-error-content {
color: var(--error-text-color);
}
@@ -179,6 +187,7 @@
.no-owners-content a iron-icon {
width: 16px;
height: 16px;
+ padding-top: 5px;
}
gr-account-label {
display: inline-block;
@@ -267,7 +276,7 @@
</div>
</template>
<template is="dom-if" if="[[!suggestion.error]]">
- <template is="dom-if" if="[[!suggestion.owners.length]]">
+ <template is="dom-if" if="[[!_areOwnersFound(suggestion.owners)]]">
<div class="no-owners-content">
<span>Not found</span>
<a on-click="_reportDocClick" href="https://gerrit.googlesource.com/plugins/code-owners/+/master/resources/Documentation/how-to-use.md#no-code-owners-found" target="_blank">
@@ -275,22 +284,30 @@
</a>
</div>
</template>
- <ul class="suggested-owners">
- <template
- is="dom-repeat"
- items="[[suggestion.owners]]"
- as="owner"
- index-as="ownerIndex"
- >
- <gr-account-label
- data-suggestion-index$="[[suggestionIndex]]"
- data-owner-index$="[[ownerIndex]]"
- account="[[owner.account]]"
- selected$="[[isSelected(owner)]]"
- on-click="toggleAccount">
- </gr-account-label>
- </template>
- </ul>
+ <template is="dom-if" if="[[suggestion.owners.owned_by_all_users]]">
+ <div class="owned-by-all-users-content">
+ <iron-icon icon="gr-icons:info" ></iron-icon>
+ <span>[[_getOwnedByAllUsersContent(isLoading, suggestedOwners)]]</span>
+ </div>
+ </template>
+ <template is="dom-if" if="[[!suggestion.owners.owned_by_all_users]]">
+ <ul class="suggested-owners">
+ <template
+ is="dom-repeat"
+ items="[[suggestion.owners.code_owners]]"
+ as="owner"
+ index-as="ownerIndex"
+ >
+ <gr-account-label
+ data-suggestion-index$="[[suggestionIndex]]"
+ data-owner-index$="[[ownerIndex]]"
+ account="[[owner.account]]"
+ selected$="[[isSelected(owner)]]"
+ on-click="toggleAccount">
+ </gr-account-label>
+ </template>
+ </ul>
+ </template>
</template>
</li>
</template>
@@ -394,7 +411,8 @@
const reportDetails = suggestions.reduce((details, cur) => {
details.totalGroups++;
details.stats.push([cur.files.length,
- cur.owners ? cur.owners.length : 0]);
+ cur.owners && cur.owners.code_owners ?
+ cur.owners.code_owners.length : 0]);
return details;
}, {totalGroups: 0, stats: []});
this.reporting.reportLifeCycle(
@@ -407,9 +425,16 @@
_updateSuggestions(suggestions) {
// update group names and files, no modification on owners or error
- this.suggestedOwners = suggestions.map(suggestion => {
+ const suggestedOwners = suggestions.map(suggestion => {
return this.formatSuggestionInfo(suggestion);
});
+ // move owned_by_all_users to the bottom:
+ const index = suggestedOwners
+ .findIndex(suggestion => suggestion.owners.owned_by_all_users);
+ if (index >= 0) {
+ suggestedOwners.push(suggestedOwners.splice(index, 1)[0]);
+ }
+ this.suggestedOwners = suggestedOwners;
}
_onReviewerChanged(reviewers) {
@@ -421,7 +446,7 @@
const res = {};
res.groupName = suggestion.groupName;
res.files = suggestion.files.slice();
- res.owners = (suggestion.owners || []).map(owner => {
+ const codeOwners = (suggestion.owners.code_owners || []).map(owner => {
const updatedOwner = {...owner};
const reviewers = this.change.reviewers.REVIEWER;
if (
@@ -432,6 +457,11 @@
}
return updatedOwner;
});
+ res.owners = {
+ owned_by_all_users: !!suggestion.owners.owned_by_all_users,
+ code_owners: codeOwners,
+ };
+
res.error = suggestion.error;
return res;
}
@@ -467,7 +497,7 @@
toggleAccount(e) {
const grAccountLabel = e.currentTarget;
const owner = this.suggestedOwners[grAccountLabel.dataset.suggestionIndex]
- .owners[grAccountLabel.dataset.ownerIndex];
+ .owners.code_owners[grAccountLabel.dataset.ownerIndex];
if (this.isSelected(owner)) {
this.removeAccount(owner);
} else {
@@ -480,13 +510,13 @@
// update all occurences
this.suggestedOwners.forEach((suggestion, sId) => {
let hasSelected = false;
- suggestion.owners.forEach((owner, oId) => {
+ suggestion.owners.code_owners.forEach((owner, oId) => {
if (
accounts.some(account => account._account_id
=== owner.account._account_id)
) {
this.set(
- ['suggestedOwners', sId, 'owners', oId],
+ ['suggestedOwners', sId, 'owners', 'code_owners', oId],
{...owner,
selected: true,
}
@@ -494,11 +524,14 @@
hasSelected = true;
} else {
this.set(
- ['suggestedOwners', sId, 'owners', oId],
+ ['suggestedOwners', sId, 'owners', 'code_owners', oId],
{...owner, selected: false}
);
}
});
+ if (accounts.length > 0 && suggestion.owners.owned_by_all_users) {
+ hasSelected = true;
+ }
this.set(['suggestedOwners', sId, 'hasSelected'], hasSelected);
});
}
@@ -511,6 +544,21 @@
this.reporting.reportInteraction('code-owners-doc-click',
{section: 'no owners found'});
}
+
+ _areOwnersFound(owners) {
+ return owners.code_owners.length > 0 || !!owners.owned_by_all_users;
+ }
+
+ _getOwnedByAllUsersContent(isLoading, suggestedOwners) {
+ if (isLoading) {
+ return 'Any user can approve';
+ }
+ // If all users own all the files in the change suggestedOwners.length === 1
+ // (suggestedOwners - collection of owners groupbed by owners)
+ return suggestedOwners && suggestedOwners.length === 1 ?
+ 'Any user can approve. Please select a user manually' :
+ 'Any user from the other files can approve';
+ }
}
customElements.define(SuggestOwners.is, SuggestOwners);