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 395631b
Show file tree
Hide file tree
Showing 6 changed files with 511 additions and 173 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,218 @@ 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 HyperlinkLabel urlLabel;
// private TextFieldWithStoredHistory urlTextField;

// protected JComponent createCenterPanel() {
// JPanel panel = new JPanel(new GridBagLayout());
// GridBagConstraints constraints = new GridBagConstraints();
// constraints.gridx = 0;
// constraints.gridy = 0;
// constraints.anchor = GridBagConstraints.WEST;
//
// // URL Label
// urlLabel = new HyperlinkLabel(QUARKUS_CODE_URL);
// urlLabel.setHyperlinkText(QUARKUS_CODE_URL);
// urlLabel.setHyperlinkTarget(QUARKUS_CODE_URL);
//
// // Modify Icon
// JLabel modifyIcon = new JLabel(AllIcons.General.Inline_edit);
// modifyIcon.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
// modifyIcon.setToolTipText("Modify");
// modifyIcon.addMouseListener(new MouseAdapter() {
// @Override
// public void mouseClicked(MouseEvent e) {
// toggleEditing();
// }
// });
//
// // Server URL label
// JBLabel serverUrlLabel = new JBLabel("Quarkus Code URL:");
// serverUrlLabel.setLabelFor(urlLabel);
//
// // Server URL text field with history
// urlTextField = new TextFieldWithStoredHistory(ENDPOINT_URL_HISTORY);
// urlTextField.setText(urlLabel.getText());
// urlTextField.setToolTipText("Enter URL");
// urlTextField.setMinimumAndPreferredWidth(200);
//
// urlTextField.addFocusListener(new FocusAdapter() {
// @Override
// public void focusLost(FocusEvent e) {
// if (!urlTextField.isVisible() ||
// //ignore if we lost focus in favor of a subcomponent
// isChildOf(e.getOppositeComponent(), urlTextField)) {
// return;
// }
// toggleEditing();
// urlLabel.setHyperlinkText(urlTextField.getText());
// urlLabel.setHyperlinkTarget(urlTextField.getText());
// }
//
// boolean isChildOf(Component component, Component parent) {
// Component currentComponent = component;
// while (currentComponent != null) {
// if (currentComponent == urlTextField) {
// return true;
// }
// currentComponent = currentComponent.getParent();
// }
// return false;
// }
// });
// urlTextField.addActionListener(new ActionListener() {
// @Override
// public void actionPerformed(ActionEvent e) {
// urlLabel.setHyperlinkText(urlTextField.getText());
// urlLabel.setHyperlinkTarget(urlTextField.getText());
// toggleEditing();
// }
// });
// toggleEditing();
//
// panel.add(serverUrlLabel, constraints);
// constraints.gridx++;
// panel.add(urlLabel, constraints);
// panel.add(urlTextField, constraints);
// constraints.gridx++;
// panel.add(modifyIcon, constraints);
// constraints.gridx++;
// panel.add(spinner, constraints);
// constraints.gridx = 1;
// constraints.gridy++;
//
// return panel;
// }

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);
}


// private void toggleEditing() {
// if (urlTextField.isVisible()) {
// urlTextField.setEnabled(false);
// urlTextField.setVisible(false);
// urlLabel.setVisible(true);
// loadingRequest = loadStreams();
// } else {
// urlTextField.setText(urlLabel.getText());
// urlTextField.setVisible(true);
// urlTextField.setEnabled(true);
// urlTextField.requestFocus();
// urlTextField.selectText();
// urlLabel.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;
}
}
Loading

0 comments on commit 395631b

Please sign in to comment.