Skip to content

Commit

Permalink
Separate Chat and Toolkit Login web view asset state event stream (#361)
Browse files Browse the repository at this point in the history
* Pass populated AWS_CA_BUNDLE env var to Flare (#341)

* Add UI notification to alert user of deprecated manifest version (#312)

* Revert commit 'Add UI notification to alert user of deprecated manifest version'

* Revert 'Pass populated AWS_CA_BUNDLE env var to Flare'

* Rebase changes from Browser Provider PR

* Fix ViewRouter

* Integrate browser based views in ViewContainer

* Fix checkstyle issues

* Fix AmazonQBrowserProvider tests

* Fix bug due to display sync exec call

* Add semaphore locking to container to prevent race conditions

* Add browser focus handling

* Separate Chat and Toolkit Login webview asset event stream

* Separate Chat and Toolkit Login webview asset event stream

* Add state checking methods to LspState

* Clean up code

* Remove unnecessary parent assignment from chat webview

* Refactor ViewVisibilityManager to default to one view

* Add accidentally removed telemetry emissions

* Fix issue in a comment due to stash

* Update AssetProviders to publish state everytime content is fetched

* Update plugin descriptor (#360)

* Remove business logic from asset provider constructors

* Remove redundant methods to check state

---------

Co-authored-by: Jonathan Breedlove <[email protected]>
Co-authored-by: Nicolas <[email protected]>
  • Loading branch information
3 people authored Feb 12, 2025
1 parent c29e968 commit 93a06b8
Show file tree
Hide file tree
Showing 11 changed files with 120 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,4 @@ public enum LspState {

ACTIVE, FAILED, PENDING;

public boolean hasFailed() {
return this == FAILED;
}

public boolean isActive() {
return this == ACTIVE;
}

public boolean isPending() {
return this == PENDING;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@ public final class ChatWebViewAssetProvider extends WebViewAssetProvider {

private WebviewAssetServer webviewAssetServer;

public ChatWebViewAssetProvider() {
Optional<String> content = getContent();
Activator.getEventBroker().post(WebViewAssetState.class,
content.isPresent() ? WebViewAssetState.RESOLVED : WebViewAssetState.DEPENDENCY_MISSING);
}

@Override
public Optional<String> getContent() {
Optional<String> content = resolveContent();
Activator.getEventBroker().post(ChatWebViewAssetState.class,
content.isPresent() ? ChatWebViewAssetState.RESOLVED : ChatWebViewAssetState.DEPENDENCY_MISSING);
return content;
}

private Optional<String> resolveContent() {
var chatAsset = resolveJsPath();
if (!chatAsset.isPresent()) {
return Optional.empty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@

package software.aws.toolkits.eclipse.amazonq.providers.assets;

public enum WebViewAssetState {
RESOLVED, DEPENDENCY_MISSING;
public enum ChatWebViewAssetState {

public boolean isDependencyMissing() {
return this == DEPENDENCY_MISSING;
}
RESOLVED, DEPENDENCY_MISSING;

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,16 @@ public final class ToolkitLoginWebViewAssetProvider extends WebViewAssetProvider
private WebviewAssetServer webviewAssetServer;
private static final ThemeDetector THEME_DETECTOR = new ThemeDetector();

public ToolkitLoginWebViewAssetProvider() {
Optional<String> content = getContent();
Activator.getEventBroker().post(WebViewAssetState.class,
content.isPresent() ? WebViewAssetState.RESOLVED : WebViewAssetState.DEPENDENCY_MISSING);
}

@Override
public Optional<String> getContent() {
Optional<String> content = resolveContent();
Activator.getEventBroker().post(ToolkitLoginWebViewAssetState.class,
content.isPresent() ? ToolkitLoginWebViewAssetState.RESOLVED
: ToolkitLoginWebViewAssetState.DEPENDENCY_MISSING);
return content;
}

private Optional<String> resolveContent() {
try {
URL jsFile = PluginUtils.getResource("webview/build/assets/js/getStart.js");
String decodedPath = URLDecoder.decode(jsFile.getPath(), StandardCharsets.UTF_8);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.eclipse.amazonq.providers.assets;

public enum ToolkitLoginWebViewAssetState {

RESOLVED, DEPENDENCY_MISSING;

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@
package software.aws.toolkits.eclipse.amazonq.providers.browser;

public enum BrowserCompatibilityState {
COMPATIBLE, DEPENDENCY_MISSING;

public boolean isDependencyMissing() {
return this == DEPENDENCY_MISSING;
}
COMPATIBLE, DEPENDENCY_MISSING;

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class AmazonQChatWebview extends AmazonQView implements ChatUiRequestList
private Browser browser;
private volatile boolean canDisposeState = false;
private WebViewAssetProvider webViewAssetProvider;
private Optional<String> content;

public AmazonQChatWebview() {
super();
Expand All @@ -42,6 +43,7 @@ public AmazonQChatWebview() {
this.chatCommunicationManager = ChatCommunicationManager.getInstance();
this.actionHandler = new AmazonQChatViewActionHandler(chatCommunicationManager);
this.webViewAssetProvider = new ChatWebViewAssetProvider();
this.content = this.webViewAssetProvider.getContent();
this.chatTheme = new ChatTheme();
}

Expand Down Expand Up @@ -111,7 +113,9 @@ public void completed(final ProgressEvent event) {
}
});

Optional<String> content = webViewAssetProvider.getContent();
if (!content.isPresent()) {
content = webViewAssetProvider.getContent();
}
browser.setText(content.get());

return parent;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

package software.aws.toolkits.eclipse.amazonq.views;

import java.util.Optional;

import org.eclipse.swt.browser.BrowserFunction;
import org.eclipse.swt.browser.ProgressAdapter;
import org.eclipse.swt.browser.ProgressEvent;
Expand All @@ -23,6 +25,7 @@ public final class ToolkitLoginWebview extends AmazonQView {
private final WebViewAssetProvider webViewAssetProvider;
private final ViewCommandParser commandParser;
private final ViewActionHandler actionHandler;
private Optional<String> content;

private boolean isViewVisible = false;

Expand All @@ -31,6 +34,7 @@ public ToolkitLoginWebview() {
this.commandParser = new LoginViewCommandParser();
this.actionHandler = new LoginViewActionHandler();
this.webViewAssetProvider = new ToolkitLoginWebViewAssetProvider();
this.content = this.webViewAssetProvider.getContent();
}

@Override
Expand Down Expand Up @@ -80,6 +84,12 @@ public Object function(final Object[] arguments) {
amazonQCommonActions = getAmazonQCommonActions();
browser.setText(webViewAssetProvider.getContent().get());

if (!content.isPresent()) {
content = webViewAssetProvider.getContent();
}

browser.setText(content.get());

return parent;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@

import software.aws.toolkits.eclipse.amazonq.lsp.auth.model.AuthState;
import software.aws.toolkits.eclipse.amazonq.lsp.manager.LspState;
import software.aws.toolkits.eclipse.amazonq.providers.assets.WebViewAssetState;
import software.aws.toolkits.eclipse.amazonq.providers.assets.ChatWebViewAssetState;
import software.aws.toolkits.eclipse.amazonq.providers.assets.ToolkitLoginWebViewAssetState;
import software.aws.toolkits.eclipse.amazonq.providers.browser.BrowserCompatibilityState;

public record PluginState(AuthState authState, LspState lspState, BrowserCompatibilityState browserCompatibilityState,
WebViewAssetState webViewAssetState) {
ChatWebViewAssetState chatWebViewAssetState, ToolkitLoginWebViewAssetState toolkitLoginWebViewAssetState) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,19 @@
import software.aws.toolkits.eclipse.amazonq.lsp.auth.model.AuthState;
import software.aws.toolkits.eclipse.amazonq.lsp.manager.LspState;
import software.aws.toolkits.eclipse.amazonq.plugin.Activator;
import software.aws.toolkits.eclipse.amazonq.providers.assets.WebViewAssetState;
import software.aws.toolkits.eclipse.amazonq.providers.assets.ChatWebViewAssetState;
import software.aws.toolkits.eclipse.amazonq.providers.assets.ToolkitLoginWebViewAssetState;
import software.aws.toolkits.eclipse.amazonq.providers.browser.BrowserCompatibilityState;

/**
* Routes to appropriate views based on the combined auth and lsp states (plugin
* state). This router observes plugin state changes and updates the active view
* accordingly, broadcasting view changes through the event broker.
* Routes to appropriate views based on the plugin's combined state by evaluating conditions in priority order:
* 1. Browser compatibility and dependencies
* 2. Language Server Protocol (LSP) status
* 3. Chat and login UI asset availability
* 4. Authentication state
*
* Observes changes in all states and automatically updates the active view when any state changes.
* Broadcasts view transitions through the event broker system.
*/
public final class ViewRouter implements EventObserver<PluginState> {

Expand All @@ -28,7 +34,8 @@ public final class ViewRouter implements EventObserver<PluginState> {
* observables directly from the event broker and combines them to create the
* PluginState stream.
*
* @param builder The builder containing auth and lsp state observables
* @param builder The builder containing auth and lsp state, browser
* compatibility and asset state observables.
*/
private ViewRouter(final Builder builder) {
if (builder.authStateObservable == null) {
Expand All @@ -44,19 +51,26 @@ private ViewRouter(final Builder builder) {
.ofObservable(BrowserCompatibilityState.class);
}

if (builder.webViewAssetStateObservable == null) {
builder.webViewAssetStateObservable = Activator.getEventBroker().ofObservable(WebViewAssetState.class);
if (builder.chatWebViewAssetStateObservable == null) {
builder.chatWebViewAssetStateObservable = Activator.getEventBroker()
.ofObservable(ChatWebViewAssetState.class);
}

if (builder.toolkitLoginWebViewAssetStateObservable == null) {
builder.toolkitLoginWebViewAssetStateObservable = Activator.getEventBroker()
.ofObservable(ToolkitLoginWebViewAssetState.class);
}
/**
* Combines all state observables into a single stream that emits a new PluginState
* whenever any individual state changes. The combined stream:
* - Waits for initial events from all observables before emitting
* - Creates new PluginState from latest values of each observable upon update to any single stream
*/
Observable.combineLatest(builder.authStateObservable, builder.lspStateObservable,
builder.browserCompatibilityStateObservable, builder.webViewAssetStateObservable, PluginState::new)
builder.browserCompatibilityStateObservable, builder.chatWebViewAssetStateObservable,
builder.toolkitLoginWebViewAssetStateObservable, PluginState::new)
.observeOn(Schedulers.computation()).subscribe(this::onEvent);

}

public static Builder builder() {
Expand All @@ -66,7 +80,7 @@ public static Builder builder() {
/**
* Handles plugin state changes by refreshing the active view.
*
* @param pluginState The current combined state auth and lsp state of the plugin
* @param pluginState Current combined state of the plugin
*/
@Override
public void onEvent(final PluginState pluginState) {
Expand All @@ -81,18 +95,19 @@ public void onEvent(final PluginState pluginState) {
* 3. Chat UI Asset Missing: have chat assets been fetched and available?
* 4. Authentication Logged out: if user logged out, needs to login again.
* 5. Authentication Expired: if auth has expired, needs to be refreshed.
* 5. Chat View: happy path.
* 6. Chat View: happy path.
*
* @param pluginState The current combined auth and lsp state of the plugin
* @param pluginState Current combined state of the plugin
*/
private void refreshActiveView(final PluginState pluginState) {
AmazonQViewType newActiveView;

if (pluginState.browserCompatibilityState().isDependencyMissing()) {
if (pluginState.browserCompatibilityState() == BrowserCompatibilityState.DEPENDENCY_MISSING) {
newActiveView = AmazonQViewType.DEPENDENCY_MISSING_VIEW;
} else if (pluginState.lspState().hasFailed()) {
newActiveView = AmazonQViewType.LSP_STARTUP_FAILED_VIEW;
} else if (pluginState.webViewAssetState().isDependencyMissing()) {
} else if (pluginState.chatWebViewAssetState() == ChatWebViewAssetState.DEPENDENCY_MISSING
|| pluginState.toolkitLoginWebViewAssetState() == ToolkitLoginWebViewAssetState.DEPENDENCY_MISSING) {
newActiveView = AmazonQViewType.CHAT_ASSET_MISSING_VIEW;
} else if (pluginState.authState().isLoggedOut()) {
newActiveView = AmazonQViewType.TOOLKIT_LOGIN_VIEW;
Expand Down Expand Up @@ -130,7 +145,8 @@ public static final class Builder {
private Observable<AuthState> authStateObservable;
private Observable<LspState> lspStateObservable;
private Observable<BrowserCompatibilityState> browserCompatibilityStateObservable;
private Observable<WebViewAssetState> webViewAssetStateObservable;
private Observable<ChatWebViewAssetState> chatWebViewAssetStateObservable;
private Observable<ToolkitLoginWebViewAssetState> toolkitLoginWebViewAssetStateObservable;

public Builder withAuthStateObservable(final Observable<AuthState> authStateObservable) {
this.authStateObservable = authStateObservable;
Expand All @@ -148,13 +164,15 @@ public Builder withBrowserCompatibilityStateObservable(
return this;
}

public Builder withWebViewAssetStateObservable(final Observable<WebViewAssetState> webViewAssetState) {
this.webViewAssetStateObservable = webViewAssetState;
public Builder withChatWebViewAssetStateObservable(
final Observable<ChatWebViewAssetState> webViewAssetStateObservable) {
this.chatWebViewAssetStateObservable = webViewAssetStateObservable;
return this;
}

public Builder withWebViewAssetStateObservable(final Observable<WebViewAssetState> webViewAssetState) {
this.webViewAssetState = webViewAssetState;
public Builder withToolkitLoginWebViewAssetStateObservable(
final Observable<ToolkitLoginWebViewAssetState> webViewAssetStateObservable) {
this.toolkitLoginWebViewAssetStateObservable = webViewAssetStateObservable;
return this;
}

Expand Down
Loading

0 comments on commit 93a06b8

Please sign in to comment.