blob: 842dace4eba25f878b2a70c811e169c1b8c843d4 [file] [log] [blame]
/**
* @license
* Copyright 2019 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {
getAccountDisplayName,
getGroupDisplayName,
} from '../../utils/display-name-util';
import {RestApiService} from '../gr-rest-api/gr-rest-api';
import {
AccountInfo,
isReviewerAccountSuggestion,
isReviewerGroupSuggestion,
NumericChangeId,
ServerInfo,
SuggestedReviewerInfo,
Suggestion,
} from '../../types/common';
import {assertNever} from '../../utils/common-util';
import {AutocompleteSuggestion} from '../../elements/shared/gr-autocomplete/gr-autocomplete';
import {allSettled, isFulfilled} from '../../utils/async-util';
import {isDefined, ParsedChangeInfo} from '../../types/types';
import {accountKey} from '../../utils/account-util';
import {
AccountId,
ChangeInfo,
EmailAddress,
GroupId,
ReviewerState,
} from '../../api/rest-api';
import {throwingErrorCallback} from '../../elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper';
export interface ReviewerSuggestionsProvider {
getSuggestions(input: string): Promise<Suggestion[]>;
makeSuggestionItem(
suggestion: Suggestion
): AutocompleteSuggestion<SuggestedReviewerInfo>;
}
export class GrReviewerSuggestionsProvider
implements ReviewerSuggestionsProvider
{
private changes: (ChangeInfo | ParsedChangeInfo)[];
constructor(
private restApi: RestApiService,
private type: ReviewerState.REVIEWER | ReviewerState.CC,
private config: ServerInfo | undefined,
private loggedIn: boolean,
...changes: (ChangeInfo | ParsedChangeInfo)[]
) {
this.changes = changes;
}
/**
* Requests related suggestions.
*
* If the request fails the returned promise is rejected.
*/
async getSuggestions(input: string): Promise<Suggestion[]> {
if (!this.loggedIn) return [];
const resultsByChangeIndex = await allSettled(
this.changes.map(change =>
this.getSuggestionsForChange(change._number, input)
)
);
const suggestionsByChangeIndex = resultsByChangeIndex
.filter(isFulfilled)
.map(result => result.value)
.filter(isDefined);
if (suggestionsByChangeIndex.length !== resultsByChangeIndex.length) {
// one of the requests failed, so don't allow any suggestions.
return [];
}
// Pass the union of all the suggestions through each change, keeping only
// suggestions where either:
// A) the change had the suggestion too, or
// B) the suggestion is already a reviewer/CC on the change (depending on
// this.type).
return this.changes.reduce((suggestions, change, changeIndex) => {
const reviewerAndSuggestionKeys = new Set<
AccountId | EmailAddress | GroupId | undefined
>([
...(change.reviewers[this.type]?.map(accountKey) ?? []),
...suggestionsByChangeIndex[changeIndex].map(suggestionKey),
]);
return suggestions.filter(suggestion =>
reviewerAndSuggestionKeys.has(suggestionKey(suggestion))
);
}, uniqueSuggestions(suggestionsByChangeIndex.flat()));
}
makeSuggestionItem(
suggestion: Suggestion
): AutocompleteSuggestion<SuggestedReviewerInfo> {
if (isReviewerAccountSuggestion(suggestion)) {
// Reviewer is an account suggestion from getChangeSuggestedReviewers.
return {
name: getAccountDisplayName(this.config, suggestion.account),
value: suggestion,
};
}
if (isReviewerGroupSuggestion(suggestion)) {
// Reviewer is a group suggestion from getChangeSuggestedReviewers.
return {
name: getGroupDisplayName(suggestion.group),
value: suggestion,
};
}
if (isAccountSuggestion(suggestion)) {
// Reviewer is an account suggestion from getSuggestedAccounts.
return {
name: getAccountDisplayName(this.config, suggestion),
value: {account: suggestion, count: 1},
};
}
assertNever(suggestion, 'Received an incorrect suggestion');
}
private getSuggestionsForChange(
changeNumber: NumericChangeId,
input: string
): Promise<SuggestedReviewerInfo[] | undefined> {
return this.type === ReviewerState.REVIEWER
? this.restApi.getChangeSuggestedReviewers(
changeNumber,
input,
throwingErrorCallback
)
: this.restApi.getChangeSuggestedCCs(
changeNumber,
input,
throwingErrorCallback
);
}
}
function uniqueSuggestions(suggestions: Suggestion[]): Suggestion[] {
return suggestions.filter(
(suggestion, index) =>
index ===
suggestions.findIndex(
other => suggestionKey(suggestion) === suggestionKey(other)
)
);
}
function suggestionKey(suggestion: Suggestion) {
if (isReviewerAccountSuggestion(suggestion)) {
return accountKey(suggestion.account);
} else if (isReviewerGroupSuggestion(suggestion)) {
return suggestion.group.id;
} else if (isAccountSuggestion(suggestion)) {
return accountKey(suggestion);
}
return undefined;
}
function isAccountSuggestion(s: Suggestion): s is AccountInfo {
return (s as AccountInfo)._account_id !== undefined;
}