Skip to content

Commit

Permalink
perf: improve Quarkus wizard latency
Browse files Browse the repository at this point in the history
Signed-off-by: Fred Bricon <[email protected]>
  • Loading branch information
fbricon committed Sep 25, 2023
1 parent ae4ede8 commit a0874dd
Show file tree
Hide file tree
Showing 6 changed files with 392 additions and 179 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import com.intellij.openapi.util.Key;
import com.redhat.devtools.intellij.quarkus.module.QuarkusExtensionsModel;
import com.redhat.devtools.intellij.quarkus.module.QuarkusModel;
import com.redhat.devtools.intellij.quarkus.tool.ToolDelegate;

public class QuarkusConstants {
Expand All @@ -24,6 +25,7 @@ public class QuarkusConstants {
public final static Key<String> WIZARD_PATH_KEY = Key.create(QuarkusConstants.class.getPackage().getName() + ".path");
public final static Key<QuarkusExtensionsModel> WIZARD_EXTENSIONS_MODEL_KEY = Key.create(QuarkusConstants.class.getPackage().getName() + ".model");
public final static Key<String> WIZARD_ENDPOINT_URL_KEY = Key.create(QuarkusConstants.class.getPackage().getName() + ".endpointURL");
public final static Key<QuarkusModel> WIZARD_QUARKUS_STREAMS = Key.create(QuarkusConstants.class.getPackage().getName() + ".streams");

public static final String CONFIG_ROOT_ANNOTATION = "io.quarkus.runtime.annotations.ConfigRoot";
public static final String CONFIG_GROUP_ANNOTATION = "io.quarkus.runtime.annotations.ConfigGroup";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,42 +15,57 @@
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.ide.util.projectWizard.ModuleWizardStep;
import com.intellij.ide.util.projectWizard.WizardContext;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.progress.EmptyProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.ui.ComponentWithBrowseButton;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Disposer;
import com.intellij.ui.HyperlinkLabel;
import com.intellij.ui.IdeBorderFactory;
import com.intellij.ui.TextFieldWithStoredHistory;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBRadioButton;
import com.intellij.util.ui.AsyncProcessIcon;
import com.intellij.util.ui.FormBuilder;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.components.BorderLayoutPanel;
import com.redhat.devtools.intellij.quarkus.QuarkusConstants;

import javax.swing.ButtonGroup;
import javax.swing.JComponent;
import javax.swing.JPanel;
import java.awt.BorderLayout;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import static com.redhat.devtools.intellij.quarkus.QuarkusConstants.QUARKUS_CODE_URL;
import static com.redhat.devtools.intellij.quarkus.module.QuarkusModelRegistry.DEFAULT_TIMEOUT_IN_SEC;

public class QuarkusCodeEndpointChooserStep extends ModuleWizardStep {
public class QuarkusCodeEndpointChooserStep extends ModuleWizardStep implements Disposable {
private static final String LAST_ENDPOINT_URL = "quarkus.code.endpoint.url.last";
private static final String ENDPOINT_URL_HISTORY = "quarkus.code.endpoint.url.history";
private final WizardContext wizardContext;
private final JBRadioButton defaultRadioButton = new JBRadioButton("Default:", true);
private final JBRadioButton customRadioButton = new JBRadioButton("Custom:", false);
private final TextFieldWithStoredHistory endpointURL = new TextFieldWithStoredHistory(ENDPOINT_URL_HISTORY);
private final ComponentWithBrowseButton<TextFieldWithStoredHistory> customUrlWithBrowseButton;
private final JPanel component;

private Future<QuarkusModel> loadingRequest = null;

private QuarkusModel streams = null;

private AsyncProcessIcon spinner = new AsyncProcessIcon("Loading Quarkus data...");

QuarkusCodeEndpointChooserStep(WizardContext wizardContext) {
Disposer.register(wizardContext.getDisposable(), this);
this.customUrlWithBrowseButton = new ComponentWithBrowseButton(this.endpointURL, new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
Expand All @@ -62,6 +77,13 @@ public void actionPerformed(ActionEvent e) {

}
});

endpointURL.addActionListener(e -> {
if (customRadioButton.isSelected()) {
updateCustomUrl();
}
});

this.wizardContext = wizardContext;
String lastServiceUrl = PropertiesComponent.getInstance().getValue(LAST_ENDPOINT_URL, QUARKUS_CODE_URL);
if (!lastServiceUrl.equals(QUARKUS_CODE_URL)) {
Expand All @@ -75,18 +97,27 @@ public void actionPerformed(ActionEvent e) {
List<String> history = this.endpointURL.getHistory();
history.remove(QUARKUS_CODE_URL);
this.endpointURL.setHistory(history);
this.updateCustomUrl();
this.component = createComponent();
}

@Override
public void _init() {
super._init();
SwingUtilities.invokeLater(() -> {
this.updateCustomUrl();
});
}

@Override
public JComponent getComponent() {
return component;
}

public JPanel createComponent() {
ButtonGroup group = new ButtonGroup();
group.add(this.defaultRadioButton);
group.add(this.customRadioButton);
ActionListener listener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
QuarkusCodeEndpointChooserStep.this.updateCustomUrl();
}
};
ActionListener listener = e -> QuarkusCodeEndpointChooserStep.this.updateCustomUrl();
this.defaultRadioButton.addActionListener(listener);
this.customRadioButton.addActionListener(listener);
FormBuilder builder = new FormBuilder();
Expand All @@ -102,7 +133,8 @@ public void actionPerformed(ActionEvent e) {
this.customUrlWithBrowseButton.setButtonIcon(AllIcons.Actions.Preview);
customPanel.addToCenter(this.customUrlWithBrowseButton);
builder.addComponent(customPanel);
builder.addTooltip("Make sure your network connection is active before continuing.");
builder.addTooltip("Make sure your network connection is active.");
builder.addComponentFillVertically(spinner, 10);
JPanel panel = new JPanel(new BorderLayout());
panel.add(builder.getPanel(), "North");
panel.setBorder(JBUI.Borders.emptyLeft(20));
Expand All @@ -115,34 +147,117 @@ private void updateCustomUrl() {
this.endpointURL.getTextEditor().setEditable(custom);
this.endpointURL.setEnabled(custom);
this.customUrlWithBrowseButton.setButtonEnabled(custom);
if (loadingRequest != null) {
loadingRequest.cancel(true);
}
loadingRequest = loadStreams();
}

private Future<QuarkusModel> loadStreams() {
final String endpoint = getSelectedEndpointUrl();
if (endpoint == null || endpoint.isBlank()) {
return null;
}
showSpinner();
return ApplicationManager.getApplication().executeOnPooledThread(() -> {
try {
return QuarkusModelRegistry.INSTANCE.loadStreams(endpoint, new EmptyProgressIndicator());
} catch (Exception e) {
ApplicationManager.getApplication().invokeLater(() -> {
Messages.showErrorDialog("Failed to load Quarkus platform versions from "+endpoint+":\n"+ e.getMessage(), "Error loading Quarkus platform versions");
}, ModalityState.stateForComponent(getComponent()) );
} finally {
ApplicationManager.getApplication().invokeLater(() -> {
hideSpinner();
}, ModalityState.stateForComponent(getComponent()));
}
return null;
});
}

private void showSpinner() {
spinner.setVisible(true);
spinner.resume();
}

private void hideSpinner() {
spinner.suspend();
spinner.setVisible(false);
}

@Override
public boolean validate() throws ConfigurationException {
if (this.defaultRadioButton.isSelected()) {
return true;
} else {
if (this.customRadioButton.isSelected()) {
String serviceUrl = this.endpointURL.getText();
if (serviceUrl.isEmpty()) {
throw new ConfigurationException("Quarkus Code endpoint URL must be set");
} else if (!serviceUrl.startsWith("http://") && !serviceUrl.startsWith("https://")) {
} else if (!serviceUrl.matches("^https?://.*")) {
throw new ConfigurationException("Invalid custom Quarkus Code endpoint URL");
} else {
try {
QuarkusModelRegistry.INSTANCE.load(serviceUrl, new EmptyProgressIndicator());
return true;
} catch (IOException var3) {
throw new ConfigurationException("Invalid Custom Quarkus Code endpoint URL");
}
}
}
try {
boolean requestComplete = checkRequestComplete();
if (!requestComplete) {
return false;
}
streams = loadingRequest.get(DEFAULT_TIMEOUT_IN_SEC, TimeUnit.SECONDS);
if (streams == null) {
throw new ConfigurationException("Failed to load Quarkus platform streams");
}
} catch (Exception e) {
throw new ConfigurationException("Failed to load Quarkus platform streams", e, "Quarkus streams loading failure");
}
return true;
}

private boolean checkRequestComplete() throws ExecutionException, InterruptedException {
if (loadingRequest == null) {
loadingRequest = loadStreams();
}
if (loadingRequest.isDone()){
return true;
}
ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
// Wait for the loading to finish
try {
loadingRequest.get(DEFAULT_TIMEOUT_IN_SEC, TimeUnit.SECONDS);
} catch (Exception e) {
ApplicationManager.getApplication().invokeLater(() -> {
String message = e.getMessage();
if (e instanceof TimeoutException) {
message = "Process timeout (exceeded "+DEFAULT_TIMEOUT_IN_SEC+" seconds)";
}
if (message == null) {
message = e.getClass().getName();
}
Messages.showErrorDialog("Failed to load Quarkus platform versions from "+getSelectedEndpointUrl()+":\n"+ message, "Error loading Quarkus platform versions");
}, ModalityState.stateForComponent(getComponent()) );
}
}, "Loading Quarkus platform streams...", true, wizardContext.getProject());
return false;
}

@Override
public void updateDataModel() {
String endpointURL = this.customRadioButton.isSelected() ? this.endpointURL.getText() : QUARKUS_CODE_URL;
String endpointURL = getSelectedEndpointUrl();
if (!Comparing.strEqual(this.wizardContext.getUserData(QuarkusConstants.WIZARD_ENDPOINT_URL_KEY), endpointURL)) {
this.endpointURL.addCurrentTextToHistory();
this.wizardContext.putUserData(QuarkusConstants.WIZARD_ENDPOINT_URL_KEY, endpointURL);
this.wizardContext.putUserData(QuarkusConstants.WIZARD_QUARKUS_STREAMS, streams);
PropertiesComponent.getInstance().setValue(LAST_ENDPOINT_URL, endpointURL);
}
}

private String getSelectedEndpointUrl() {
return this.customRadioButton.isSelected() ? this.endpointURL.getText() : QUARKUS_CODE_URL;
}

@Override
public void dispose() {
if (loadingRequest != null) {
loadingRequest.cancel(true);
loadingRequest = null;
}
streams = null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@
******************************************************************************/
package com.redhat.devtools.intellij.quarkus.module;

import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.progress.ProgressIndicator;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

public class QuarkusModel {
public class QuarkusModel implements Disposable {
private String baseURL;

private List<QuarkusStream> streams;
Expand All @@ -32,12 +36,26 @@ public List<QuarkusStream> getStreams() {
return streams;
}

//Used in Unit test
public QuarkusExtensionsModel getExtensionsModel(String key, ProgressIndicator indicator) throws IOException {
try {
return ApplicationManager.getApplication().executeOnPooledThread(() -> loadExtensionsModel(key, indicator)).get();
} catch (InterruptedException | ExecutionException e) {
throw new IOException(e);
}
}

public QuarkusExtensionsModel loadExtensionsModel(String key, ProgressIndicator indicator) throws IOException {
QuarkusExtensionsModel extensionsModel = extensionsModelMap.get(key);
if (extensionsModel == null) {
extensionsModel = QuarkusModelRegistry.loadExtensionsModel(baseURL, key, indicator);
extensionsModelMap.put(key, extensionsModel);
}
return extensionsModel;
}

@Override
public void dispose() {
extensionsModelMap.clear();
}
}
Loading

0 comments on commit a0874dd

Please sign in to comment.