Merge "Fix race condition when linking to specific line"
diff --git a/java/com/google/gerrit/httpd/ProjectOAuthFilter.java b/java/com/google/gerrit/httpd/ProjectOAuthFilter.java
index 9d58d09..8645f9e 100644
--- a/java/com/google/gerrit/httpd/ProjectOAuthFilter.java
+++ b/java/com/google/gerrit/httpd/ProjectOAuthFilter.java
@@ -36,6 +36,7 @@
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.AuthResult;
+import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -155,7 +156,6 @@
return true;
}
}
- authRequest = authRequestFactory.createForExternalUser(authInfo.username);
Optional<AccountState> who =
accountCache.getByUsername(authInfo.username).filter(a -> a.account().isActive());
if (!who.isPresent()) {
@@ -167,6 +167,16 @@
}
Account account = who.get().account();
+ Optional<ExternalId> extId =
+ who.get().externalIds().stream()
+ .filter(e -> e.key().scheme().equals(authInfo.exportName))
+ .findAny();
+ if (extId.isPresent()) {
+ authRequest = authRequestFactory.create(extId.get().key());
+ authRequest.setUserName(authInfo.username);
+ } else {
+ authRequest = authRequestFactory.createForExternalUser(authInfo.username);
+ }
authRequest.setEmailAddress(account.preferredEmail());
authRequest.setDisplayName(account.fullName());
authRequest.setPassword(authInfo.tokenOrSecret);
diff --git a/java/com/google/gerrit/server/account/AuthRequest.java b/java/com/google/gerrit/server/account/AuthRequest.java
index 03c6631..bb7ee97 100644
--- a/java/com/google/gerrit/server/account/AuthRequest.java
+++ b/java/com/google/gerrit/server/account/AuthRequest.java
@@ -25,6 +25,7 @@
import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import java.util.Objects;
import java.util.Optional;
/**
@@ -215,4 +216,37 @@
public void setActive(Boolean isActive) {
this.active = isActive;
}
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ active,
+ authPlugin,
+ authProvider,
+ authProvidesAccountActiveStatus,
+ displayName,
+ emailAddress,
+ externalId,
+ password,
+ skipAuthentication,
+ userName);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (!(obj instanceof AuthRequest)) return false;
+ AuthRequest other = (AuthRequest) obj;
+ return active == other.active
+ && Objects.equals(authPlugin, other.authPlugin)
+ && Objects.equals(authProvider, other.authProvider)
+ && authProvidesAccountActiveStatus == other.authProvidesAccountActiveStatus
+ && Objects.equals(displayName, other.displayName)
+ && Objects.equals(emailAddress, other.emailAddress)
+ && Objects.equals(externalId, other.externalId)
+ && Objects.equals(password, other.password)
+ && skipAuthentication == other.skipAuthentication
+ && Objects.equals(userName, other.userName);
+ }
}
diff --git a/javatests/com/google/gerrit/httpd/ProjectOAuthFilterTest.java b/javatests/com/google/gerrit/httpd/ProjectOAuthFilterTest.java
index 3ce35de..7f8e2f1 100644
--- a/javatests/com/google/gerrit/httpd/ProjectOAuthFilterTest.java
+++ b/javatests/com/google/gerrit/httpd/ProjectOAuthFilterTest.java
@@ -37,7 +37,9 @@
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.AuthResult;
import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdFactory;
import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
+import com.google.gerrit.server.account.externalids.storage.notedb.ExternalIdFactoryNoteDbImpl;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.util.http.testutil.FakeHttpServletRequest;
import com.google.gerrit.util.http.testutil.FakeHttpServletResponse;
@@ -72,11 +74,7 @@
String.format("%s:%s", AUTH_USER, OAUTH_TOKEN).getBytes(StandardCharsets.UTF_8));
private static final OAuthUserInfo OAUTH_USER_INFO =
new OAuthUserInfo(
- String.format("oauth-test:%s", AUTH_USER),
- AUTH_USER,
- "johndoe@example.com",
- "John Doe",
- null);
+ String.format("test:%s", AUTH_USER), AUTH_USER, "johndoe@example.com", "John Doe", null);
@Mock private DynamicItem<WebSession> webSessionItem;
@@ -100,6 +98,7 @@
private FakeHttpServletRequest req;
private HttpServletResponse res;
private AuthResult authSuccessful;
+ private ExternalIdFactory extIdFactory;
private ExternalIdKeyFactory extIdKeyFactory;
private AuthRequest.Factory authRequestFactory;
private Config gerritConfig = new Config();
@@ -110,6 +109,7 @@
res = new FakeHttpServletResponse();
extIdKeyFactory = new ExternalIdKeyFactory(new ExternalIdKeyFactory.ConfigImpl(authConfig));
+ extIdFactory = new ExternalIdFactoryNoteDbImpl(extIdKeyFactory, authConfig);
authRequestFactory = new AuthRequest.Factory(extIdKeyFactory);
authSuccessful =
@@ -288,14 +288,22 @@
oAuthFilter.init(null);
oAuthFilter.doFilter(req, res, chain);
- verify(accountManager).authenticate(any());
+ AuthRequest expected =
+ authRequestFactory.create(
+ ExternalIdKeyFactory.parse(OAUTH_USER_INFO.getExternalId(), false));
+ expected.setUserName(OAUTH_USER_INFO.getUserName());
+ expected.setPassword(OAUTH_TOKEN);
+ expected.setAuthPlugin("oauth");
+ expected.setAuthProvider("test");
+
+ verify(accountManager).authenticate(eq(expected));
verify(chain, never()).doFilter(any(), any());
assertThat(res.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
}
private void initAccount() throws Exception {
- initAccount(ImmutableSet.of());
+ initAccount(ImmutableSet.of(extIdFactory.create("test", AUTH_USER, AUTH_ACCOUNT_ID)));
}
private void initAccount(Collection<ExternalId> extIds) throws Exception {
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
index d77b420..e4ddfd1 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
@@ -114,6 +114,7 @@
import {pluginLoaderToken} from '../../shared/gr-js-api-interface/gr-plugin-loader';
import {modalStyles} from '../../../styles/gr-modal-styles';
import {subscribe} from '../../lit/subscription-controller';
+import {chatModelToken} from '../../../models/chat/chat-model';
import {userModelToken} from '../../../models/user/user-model';
import {ParsedChangeInfo} from '../../../types/types';
import {configModelToken} from '../../../models/config/config-model';
@@ -500,6 +501,10 @@
@state() threadsWithUnappliedSuggestions?: CommentThread[];
+ @state() chatCapabilitiesLoaded = false;
+
+ private aiChatLoadingCleanup?: () => void;
+
private readonly restApiService = getAppContext().restApiService;
private readonly reporting = getAppContext().reportingService;
@@ -508,6 +513,8 @@
private readonly getPluginLoader = resolve(this, pluginLoaderToken);
+ private readonly getChatModel = resolve(this, chatModelToken);
+
private readonly getUserModel = resolve(this, userModelToken);
private readonly getConfigModel = resolve(this, configModelToken);
@@ -524,6 +531,11 @@
super();
subscribe(
this,
+ () => this.getChatModel().capabilitiesLoaded$,
+ x => (this.chatCapabilitiesLoaded = x)
+ );
+ subscribe(
+ this,
() => this.getChangeModel().latestPatchNum$,
x => (this.latestPatchNum = x)
);
@@ -912,6 +924,15 @@
this.menuActions = this.computeMenuActions();
}
+ override updated(changedProperties: PropertyValues) {
+ super.updated(changedProperties);
+
+ if (this.chatCapabilitiesLoaded && this.aiChatLoadingCleanup) {
+ this.aiChatLoadingCleanup();
+ this.aiChatLoadingCleanup = undefined;
+ }
+ }
+
reload() {
if (!this.changeNum || !this.latestPatchNum || !this.change) {
return Promise.resolve();
@@ -1517,6 +1538,15 @@
break;
}
case AI_CHAT_ACTION.__key: {
+ if (!this.chatCapabilitiesLoaded) {
+ try {
+ this.aiChatLoadingCleanup =
+ this.setLoadingOnButtonWithKey(AI_CHAT_ACTION);
+ } catch (e) {
+ // This is expected if the button is not found in the DOM.
+ }
+ return;
+ }
fire(this, 'ai-chat', {});
break;
}
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts
index d7b1997..d858d84 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts
@@ -64,6 +64,7 @@
ChangeModel,
changeModelToken,
} from '../../../models/change/change-model';
+import {ChatModel, chatModelToken} from '../../../models/chat/chat-model';
import {GrAutogrowTextarea} from '../../shared/gr-autogrow-textarea/gr-autogrow-textarea';
// TODO(dhruvsri): remove use of _populateRevertMessage as it's private
@@ -72,6 +73,7 @@
let navigateResetStub: SinonStubbedMember<
ChangeModel['navigateToChangeResetReload']
>;
+ let chatModel: ChatModel;
suite('basic tests', () => {
setup(async () => {
@@ -140,6 +142,7 @@
testResolver(changeModelToken),
'navigateToChangeResetReload'
);
+ chatModel = testResolver(chatModelToken);
await element.updateComplete;
await element.reload();
@@ -345,6 +348,36 @@
element.revisionActions = {};
assert.isFalse(await isLoading());
});
+
+ test('chatCapabilitiesLoaded', async () => {
+ stubFlags('isEnabled').returns(true);
+ element.aiPluginsRegistered = true;
+ chatModel.updateState({
+ models: undefined,
+ actions: undefined,
+ modelsLoadingError: undefined,
+ actionsLoadingError: undefined,
+ });
+ element.requestUpdate();
+ await element.updateComplete;
+ const chatButton = queryAndAssert(
+ element,
+ 'gr-button[data-action-key="chat"]'
+ );
+ assert.isFalse(chatButton.hasAttribute('loading'));
+
+ (chatButton as HTMLElement).click();
+ await element.updateComplete;
+ assert.isTrue(chatButton.hasAttribute('loading'));
+
+ chatModel.updateState({
+ models: {models: [], default_model_id: 'foo'},
+ actions: {actions: [], default_action_id: 'bar'},
+ });
+ element.requestUpdate();
+ await element.updateComplete;
+ assert.isFalse(chatButton.hasAttribute('loading'));
+ });
});
test('show-revision-actions event should fire', async () => {
diff --git a/polygerrit-ui/app/elements/chat-panel/splash-page.ts b/polygerrit-ui/app/elements/chat-panel/splash-page.ts
index ee3eaa8..f7cf759 100644
--- a/polygerrit-ui/app/elements/chat-panel/splash-page.ts
+++ b/polygerrit-ui/app/elements/chat-panel/splash-page.ts
@@ -41,6 +41,8 @@
@state() documentationUrl?: string;
+ @state() capabilitiesLoaded = false;
+
@property({type: Boolean}) isChangePrivate = false;
private readonly getChatModel = resolve(this, chatModelToken);
@@ -76,6 +78,11 @@
);
subscribe(
this,
+ () => this.getChatModel().capabilitiesLoaded$,
+ x => (this.capabilitiesLoaded = x)
+ );
+ subscribe(
+ this,
() => this.getUserModel().account$,
x => (this.account = x)
);
@@ -226,6 +233,7 @@
}
private renderContent() {
+ if (!this.capabilitiesLoaded) return '';
if (this.isChangePrivate) {
return html`
<div class="background-request-container">
diff --git a/polygerrit-ui/app/models/chat/chat-model.ts b/polygerrit-ui/app/models/chat/chat-model.ts
index 1630862..21528d6 100644
--- a/polygerrit-ui/app/models/chat/chat-model.ts
+++ b/polygerrit-ui/app/models/chat/chat-model.ts
@@ -315,6 +315,14 @@
chatState => chatState.errorMessage
);
+ readonly capabilitiesLoaded$: Observable<boolean> = select(
+ this.state$,
+ state =>
+ !!state.modelsLoadingError ||
+ !!state.actionsLoadingError ||
+ (!!state.models && !!state.actions)
+ );
+
readonly userInput$: Observable<string> = select(
this.state$,
chatState => chatState.draftUserMessage.content
@@ -342,7 +350,8 @@
});
this.pluginsModel.aiCodeReviewPlugins$.subscribe(
- plugins => (this.plugin = plugins[0].provider)
+ plugins =>
+ (this.plugin = plugins.length > 0 ? plugins[0].provider : undefined)
);
this.filesModel.files$.subscribe(files => (this.files = files ?? []));
this.changeModel.change$.subscribe(change => {