Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce ViewRouter Component #337

Merged
merged 31 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
13c55ac
improved handling of lsp failure state
Dec 12, 2024
548a9da
add static mock to lsp connection test case
Dec 13, 2024
374e549
Integrate Event Broker in LspStatusManager
taldekar Jan 27, 2025
6a9f8d3
Add ViewRouter POC
taldekar Jan 28, 2025
d1746a4
Fix code formatting bug
taldekar Jan 28, 2025
091b6e9
Add method to retrieve observables
taldekar Jan 29, 2025
fa11959
Add listeners for combined state streams and view update request
taldekar Jan 29, 2025
f934e6c
Add listeners for combined state streams and view update request
taldekar Jan 29, 2025
a095805
Add listeners for combined state streams and view update request
taldekar Jan 29, 2025
4ad24a1
Add functionality for hot event streams that track latest event (#340)
taldekar Jan 29, 2025
d8b62b4
Remove active view update request listener
taldekar Jan 30, 2025
eca096f
Add ViewRouter tests
taldekar Jan 30, 2025
d1096a6
Remove public constructor
taldekar Jan 30, 2025
24f9c21
Remove public constructor and unused event in tests
taldekar Jan 30, 2025
eb53a8c
Add comments
taldekar Jan 30, 2025
fb1600e
Add documentation for EventBroker
taldekar Jan 30, 2025
da7b9f9
Remove ViewRouter initialization
taldekar Jan 30, 2025
6cea24d
Remove LspInitializingView from ID enum
taldekar Jan 30, 2025
17c0ebb
Add documentation for ViewRouter
taldekar Jan 30, 2025
f4da1d3
Refactor and enhance EventBroker tests
taldekar Jan 30, 2025
65c07f7
Refactor ViewRouter tests for clarity
taldekar Jan 30, 2025
9f01b0b
Remove PluginState class into separate file
taldekar Jan 30, 2025
ebdbcbf
Add documentation to subscription management logic
taldekar Jan 31, 2025
0276e79
Add support for notifying multiple late-subscribers over time of lat…
taldekar Jan 31, 2025
78d7439
Remove CODE_REFERENCE_VIEW
taldekar Jan 31, 2025
77fc653
Rename newActiveViewId to newActiveView
taldekar Jan 31, 2025
581b0eb
Rename ViewId class to AmazonQViewType
taldekar Jan 31, 2025
b395464
Revert "Integrate Event Broker in LspStatusManager"
taldekar Jan 31, 2025
c49a354
Revert "improved handling of lsp failure state"
taldekar Jan 31, 2025
4ed1bf1
Refactor EventBroker and enhance tests (#343)
taldekar Feb 4, 2025
3b95fcd
Merge branch 'feature/viewRefactor' into taldekar/ViewRouter
taldekar Feb 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugin/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Require-Bundle: org.eclipse.core.runtime;bundle-version="3.31.0",
org.apache.commons.logging;bundle-version="1.2.0",
slf4j.api;bundle-version="2.0.13",
org.apache.commons.lang3;bundle-version="3.14.0"
Bundle-Classpath: target/classes/,
Bundle-Classpath: .,
target/dependency/annotations-2.28.26.jar,
target/dependency/apache-client-2.28.26.jar,
target/dependency/auth-2.28.26.jar,
Expand Down
12 changes: 12 additions & 0 deletions plugin/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@
icon="icons/AmazonQ.png"
class="software.aws.toolkits.eclipse.amazonq.views.ChatAssetMissingView">
</view>
<view
id="software.aws.toolkits.eclipse.amazonq.views.LspStartUpFailedView"
name="Amazon Q"
icon="icons/AmazonQ.png"
class="software.aws.toolkits.eclipse.amazonq.views.LspStartUpFailedView">
</view>
<view
id="software.aws.toolkits.eclipse.amazonq.views.ToolkitLoginWebview"
name="Amazon Q"
Expand Down Expand Up @@ -142,6 +148,12 @@
relationship="stack"
visible="false">
</view>
<view
id="software.aws.toolkits.eclipse.amazonq.views.LspStartUpFailedView"
relative="software.aws.toolkits.eclipse.amazonq.views.ToolkitLoginWebview"
relationship="stack"
visible="false">
</view>
</perspectiveExtension>
</extension>
<extension
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,33 @@

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

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.schedulers.Schedulers;
import io.reactivex.rxjava3.subjects.PublishSubject;
import io.reactivex.rxjava3.subjects.BehaviorSubject;
import io.reactivex.rxjava3.subjects.Subject;
import software.aws.toolkits.eclipse.amazonq.broker.api.EventObserver;

public final class EventBroker {

private final Subject<Object> eventBus = PublishSubject.create().toSerialized();
private final Subject<Object> eventBus;
private final Map<Class<?>, Observable<?>> cachedObservables;

public EventBroker() {
eventBus = BehaviorSubject.create().toSerialized(); // serialize for thread safety
eventBus.subscribeOn(Schedulers.computation()); // publish on dedicated thread

cachedObservables = new ConcurrentHashMap<>();
/*
* Create ConnectableObservable stream before publishing event and connect to it
* eagerly to ensure that events get published to it regardless of whether the
* stream has subscribers:
*/
eventBus.doOnNext(event -> getOrCreateObservable(event.getClass())).subscribe();
}

public <T> void post(final T event) {
if (event == null) {
Expand All @@ -21,17 +38,20 @@ public <T> void post(final T event) {
eventBus.onNext(event);
}

@SuppressWarnings("unchecked")
private <T> Observable<T> getOrCreateObservable(final Class<T> eventType) { // maintain stateful observables
return (Observable<T>) cachedObservables.computeIfAbsent(eventType,
type -> eventBus.ofType(eventType).replay(1).autoConnect(-1)); // connect to stream immediately
}

public <T> Disposable subscribe(final Class<T> eventType, final EventObserver<T> observer) {
Consumer<T> consumer = new Consumer<>() {
@Override
public void accept(final T event) {
observer.onEvent(event);
}
};

return eventBus.ofType(eventType)
.observeOn(Schedulers.computation())
.subscribe(consumer);
}
return getOrCreateObservable(eventType)
.observeOn(Schedulers.computation()) // subscribe on dedicated thread
.subscribe(observer::onEvent);
}

public <T> Observable<T> ofObservable(final Class<T> eventType) {
return getOrCreateObservable(eventType);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import software.aws.toolkits.eclipse.amazonq.chat.models.GenericCommandParams;
import software.aws.toolkits.eclipse.amazonq.chat.models.SendToPromptParams;
import software.aws.toolkits.eclipse.amazonq.chat.models.TriggerType;
import software.aws.toolkits.eclipse.amazonq.lsp.manager.LspStatusManager;
import software.aws.toolkits.eclipse.amazonq.plugin.Activator;
import software.aws.toolkits.eclipse.amazonq.telemetry.ToolkitTelemetryProvider;
import software.aws.toolkits.eclipse.amazonq.telemetry.metadata.ExceptionMetadata;
Expand All @@ -27,7 +28,7 @@ public abstract class AbstractQChatEditorActionsHandler extends AbstractHandler
@Override
public final boolean isEnabled() {
try {
return Activator.getLoginService().getAuthState().isLoggedIn();
return Activator.getLoginService().getAuthState().isLoggedIn() && !LspStatusManager.getInstance().lspFailed();
} catch (Exception e) {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import software.aws.toolkits.eclipse.amazonq.plugin.Activator;

import software.aws.toolkits.eclipse.amazonq.lsp.manager.LspStatusManager;
import software.aws.toolkits.eclipse.amazonq.views.ViewVisibilityManager;

public class QOpenLoginViewHandler extends AbstractHandler {
@Override
public final Object execute(final ExecutionEvent event) {
if (Activator.getLoginService().getAuthState().isLoggedIn()) {
ViewVisibilityManager.showChatView("statusBar");
if (LspStatusManager.getInstance().lspFailed()) {
ViewVisibilityManager.showLspStartUpFailedView("statusBar");
} else {
ViewVisibilityManager.showLoginView("statusBar");
ViewVisibilityManager.showDefaultView("statusBar");
}
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,39 @@
import software.amazon.awssdk.utils.StringUtils;
import software.aws.toolkits.eclipse.amazonq.lsp.encryption.DefaultLspEncryptionManager;
import software.aws.toolkits.eclipse.amazonq.lsp.manager.LspManager;
import software.aws.toolkits.eclipse.amazonq.lsp.manager.LspStatusManager;
import software.aws.toolkits.eclipse.amazonq.lsp.manager.fetcher.RecordLspSetupArgs;
import software.aws.toolkits.eclipse.amazonq.plugin.Activator;
import software.aws.toolkits.eclipse.amazonq.preferences.AmazonQPreferencePage;
import software.aws.toolkits.eclipse.amazonq.providers.LspManagerProvider;
import software.aws.toolkits.eclipse.amazonq.telemetry.LanguageServerTelemetryProvider;
import software.aws.toolkits.eclipse.amazonq.telemetry.metadata.ExceptionMetadata;
import software.aws.toolkits.eclipse.amazonq.util.ProxyUtil;
import software.aws.toolkits.telemetry.TelemetryDefinitions.Result;
import software.aws.toolkits.eclipse.amazonq.plugin.Activator;
import software.aws.toolkits.eclipse.amazonq.preferences.AmazonQPreferencePage;

public class QLspConnectionProvider extends AbstractLspConnectionProvider {

public QLspConnectionProvider() throws IOException {
super();
LanguageServerTelemetryProvider.setAllStartPoint(Instant.now());
LspManager lspManager = LspManagerProvider.getInstance();
var lspInstallResult = lspManager.getLspInstallation();
try {
LanguageServerTelemetryProvider.setAllStartPoint(Instant.now());
LspManager lspManager = LspManagerProvider.getInstance();
var lspInstallResult = lspManager.getLspInstallation();

setWorkingDirectory(lspInstallResult.getServerDirectory());

setWorkingDirectory(lspInstallResult.getServerDirectory());
var serverCommand = Paths.get(lspInstallResult.getServerDirectory(), lspInstallResult.getServerCommand());
List<String> commands = new ArrayList<>();
commands.add(serverCommand.toString());
commands.add(lspInstallResult.getServerCommandArgs());
commands.add("--stdio");
commands.add("--set-credentials-encryption-key");
setCommands(commands);
} catch (Exception e) {
LspStatusManager.getInstance().setToFailed();
throw(e);
}

var serverCommand = Paths.get(lspInstallResult.getServerDirectory(), lspInstallResult.getServerCommand());
List<String> commands = new ArrayList<>();
commands.add(serverCommand.toString());
commands.add(lspInstallResult.getServerCommandArgs());
commands.add("--stdio");
commands.add("--set-credentials-encryption-key");
setCommands(commands);
}

@Override
Expand Down Expand Up @@ -70,10 +77,12 @@ public final void start() throws IOException {

lspEncryption.initializeEncryptedCommunication(serverStdIn);
} catch (Exception e) {
LspStatusManager.getInstance().setToFailed();
emitInitFailure(ExceptionMetadata.scrubException(e));
Activator.getLogger().error("Error occured while initializing communication with Amazon Q Lsp Server", e);
}
} catch (Exception e) {
LspStatusManager.getInstance().setToFailed();
emitInitFailure(ExceptionMetadata.scrubException(e));
throw e;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.eclipse.amazonq.lsp.manager;

public enum LspState {
ACTIVE,
FAILED,
PENDING
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.eclipse.amazonq.lsp.manager;

import software.aws.toolkits.eclipse.amazonq.plugin.Activator;
import software.aws.toolkits.eclipse.amazonq.views.ViewVisibilityManager;

public final class LspStatusManager {

private static final LspStatusManager INSTANCE;
private LspState lspState;

static {
INSTANCE = new LspStatusManager();
}

private LspStatusManager() {
lspState = LspState.PENDING;
Activator.getEventBroker().post(lspState);
}

public static LspStatusManager getInstance() {
return INSTANCE;
}


public boolean lspFailed() {
return (lspState == LspState.FAILED);
}

public void setToActive() {
lspState = LspState.ACTIVE;
Activator.getEventBroker().post(lspState);
ViewVisibilityManager.showDefaultView("restart");
}

public void setToFailed() {
if (lspState != LspState.FAILED) {
ViewVisibilityManager.showLspStartUpFailedView("update");
lspState = LspState.FAILED;
}

Activator.getEventBroker().post(lspState);
}
public LspState getLspState() {
return lspState;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import software.aws.toolkits.eclipse.amazonq.util.DefaultCodeReferenceLoggingService;
import software.aws.toolkits.eclipse.amazonq.util.LoggingService;
import software.aws.toolkits.eclipse.amazonq.util.PluginLogger;
import software.aws.toolkits.eclipse.amazonq.views.router.ViewRouter;

public class Activator extends AbstractUIPlugin {

Expand All @@ -32,6 +33,7 @@ public class Activator extends AbstractUIPlugin {
private static CodeReferenceLoggingService codeReferenceLoggingService;
private static PluginStore pluginStore;
private static EventBroker eventBroker = new EventBroker();
private static ViewRouter viewRouter = ViewRouter.builder().build();

public Activator() {
super();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.eclipse.lsp4j.services.LanguageServer;

import software.aws.toolkits.eclipse.amazonq.lsp.AmazonQLspServer;
import software.aws.toolkits.eclipse.amazonq.lsp.manager.LspStatusManager;
import software.aws.toolkits.eclipse.amazonq.lsp.manager.fetcher.RecordLspSetupArgs;
import software.aws.toolkits.eclipse.amazonq.telemetry.LanguageServerTelemetryProvider;
import software.aws.toolkits.telemetry.TelemetryDefinitions.Result;
Expand Down Expand Up @@ -52,6 +53,7 @@ public void setAmazonQServer(final LanguageServer server) {
future.complete(server);
}
emitInitializeMetric();
LspStatusManager.getInstance().setToActive();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import software.aws.toolkits.eclipse.amazonq.configuration.PluginStoreKeys;
import software.aws.toolkits.eclipse.amazonq.lsp.AwsServerCapabiltiesProvider;
import software.aws.toolkits.eclipse.amazonq.lsp.auth.model.AuthState;
import software.aws.toolkits.eclipse.amazonq.lsp.manager.LspStatusManager;
import software.aws.toolkits.eclipse.amazonq.lsp.model.ChatOptions;
import software.aws.toolkits.eclipse.amazonq.lsp.model.QuickActions;
import software.aws.toolkits.eclipse.amazonq.lsp.model.QuickActionsCommandGroup;
Expand Down Expand Up @@ -141,7 +142,7 @@ public final void onEvent(final AuthState authState) {
// chat view
if (browser != null && !browser.isDisposed() && !chatStateManager.hasPreservedState()) {
Optional<String> content = getContent();
if (!content.isPresent()) {
if (!content.isPresent() && !LspStatusManager.getInstance().lspFailed()) {
canDisposeState = true;
ViewVisibilityManager.showChatAssetMissingView("update");
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.eclipse.amazonq.views;
import java.util.concurrent.CompletableFuture;

import software.aws.toolkits.eclipse.amazonq.lsp.manager.LspStatusManager;

public final class LspStartUpFailedView extends BaseView {
public static final String ID = "software.aws.toolkits.eclipse.amazonq.views.LspStartUpFailedView";

private static final String ICON_PATH = "icons/AmazonQ64.png";
private static final String HEADER_LABEL = "Language Server failed to start.";
private static final String DETAIL_MESSAGE = "Restart Eclipse or review error logs for troubleshooting";

/* TODO: After refactor of LSP error handling is completed,
* add logic to base detail_message on error code returned from exception
* */
@Override
protected String getIconPath() {
return ICON_PATH;
}

@Override
protected String getHeaderLabel() {
return HEADER_LABEL;
}

@Override
protected String getDetailMessage() {
return DETAIL_MESSAGE;
}

@Override
protected void showAlternateView() {
ViewVisibilityManager.showDefaultView("restart");
}

@Override
protected CompletableFuture<Boolean> isViewDisplayable() {
return CompletableFuture.completedFuture(LspStatusManager.getInstance().lspFailed());
}

}
Loading