Skip to content

Commit

Permalink
Trigger customization notifications on ide, stacking multiple notific…
Browse files Browse the repository at this point in the history
…ations vertically and refactor/add support for more methods in PluginStore (#48)

Notifications support (trigger on customization update), vertically stack notifications, add support for getObject/putObject in PluginStore and some refactoring

---------

Co-authored-by: Paras <[email protected]>
  • Loading branch information
pras0131 and Paras authored Oct 4, 2024
1 parent 68d772e commit 7e8933a
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@

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

import java.nio.charset.StandardCharsets;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import com.google.gson.Gson;

import software.aws.toolkits.eclipse.amazonq.util.PluginLogger;

public final class PluginStore {
private static final Preferences PREFERENCES = Preferences.userRoot().node("software.aws.toolkits.eclipse");
private static final Gson GSON = new Gson();
private PluginStore() {
// Prevent instantiation
}
Expand All @@ -31,4 +34,23 @@ public static void remove(final String key) {
PREFERENCES.remove(key);
}

public static <T> void putObject(final String key, final T value) {
String jsonValue = GSON.toJson(value);
byte[] byteValue = jsonValue.getBytes(StandardCharsets.UTF_8);
PREFERENCES.putByteArray(key, byteValue);
try {
PREFERENCES.flush();
} catch (BackingStoreException e) {
PluginLogger.warn(String.format("Error while saving entry to a preference store - key: %s, value: %s", key, value), e);
}
}

public static <T> T getObject(final String key, final Class<T> type) {
byte[] byteValue = PREFERENCES.getByteArray(key, null);
if (byteValue == null) {
return null;
}
String jsonValue = new String(byteValue, StandardCharsets.UTF_8);
return GSON.fromJson(jsonValue, type);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;

import org.eclipse.lsp4e.LanguageClientImpl;
Expand All @@ -18,6 +19,7 @@
import software.aws.toolkits.eclipse.amazonq.lsp.model.ConnectionMetadata;
import software.aws.toolkits.eclipse.amazonq.lsp.model.SsoProfileData;
import software.aws.toolkits.eclipse.amazonq.util.Constants;
import software.aws.toolkits.eclipse.amazonq.views.model.Customization;
import software.aws.toolkits.eclipse.amazonq.util.PluginLogger;
import software.aws.toolkits.eclipse.amazonq.util.ThreadingUtils;

Expand All @@ -42,9 +44,9 @@ public final CompletableFuture<List<Object>> configuration(final ConfigurationPa
List<Object> output = new ArrayList<>();
configurationParams.getItems().forEach(item -> {
if (item.getSection().equals(Constants.LSP_CONFIGURATION_KEY)) {
String customizationArn = PluginStore.get(Constants.CUSTOMIZATION_STORAGE_INTERNAL_KEY);
Customization storedCustomization = PluginStore.getObject(Constants.CUSTOMIZATION_STORAGE_INTERNAL_KEY, Customization.class);
Map<String, String> customization = new HashMap<>();
customization.put(Constants.LSP_CUSTOMIZATION_CONFIGURATION_KEY, customizationArn);
customization.put(Constants.LSP_CUSTOMIZATION_CONFIGURATION_KEY, Objects.nonNull(storedCustomization) ? storedCustomization.getArn() : null);
output.add(customization);
} else {
output.add(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,8 @@ private Constants() {
public static final String CUSTOMIZATION_STORAGE_INTERNAL_KEY = "aws.q.customization.eclipse";
public static final String LSP_CUSTOMIZATION_CONFIGURATION_KEY = "customization";
public static final String LSP_CONFIGURATION_KEY = "aws.q";
public static final String IDE_CUSTOMIZATION_NOTIFICATION_TITLE = "Amazon Q Customization";
public static final String IDE_CUSTOMIZATION_NOTIFICATION_BODY_TEMPLATE = "Amazon Q inline suggestions are now coming from the %s";
public static final String DEFAULT_Q_FOUNDATION_DISPLAY_NAME = "Amazon Q foundation (Default)";

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Monitor;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.mylyn.commons.ui.dialogs.AbstractNotificationPopup;

Expand All @@ -21,6 +24,11 @@ public final class ToolkitNotification extends AbstractNotificationPopup {
private final String title;
private final String description;
private Image infoIcon;
private static CopyOnWriteArrayList<ToolkitNotification> activeNotifications = new CopyOnWriteArrayList<>();
private static final int MAX_WIDTH = 400;
private static final int MIN_HEIGHT = 100;
private static final int PADDING_EDGE = 5;
private static final int NOTIFICATIONS_GAP = 5;

public ToolkitNotification(final Display display, final String title, final String description) {
super(display);
Expand Down Expand Up @@ -56,12 +64,50 @@ protected String getPopupShellTitle() {
}

@Override
protected Point getInitialSize() {
return new Point(60, 20);
protected void initializeBounds() {
Rectangle clArea = getPrimaryClientArea();
Point initialSize = getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT);
int height = Math.max(initialSize.y, MIN_HEIGHT);
int width = Math.min(initialSize.x, MAX_WIDTH);
Point size = new Point(width, height);
// Calculate the position for the new notification
int x = clArea.x + clArea.width - size.x - PADDING_EDGE;
int y = clArea.height + clArea.y - size.y - PADDING_EDGE;
for (ToolkitNotification notification : activeNotifications) {
if (!notification.getShell().isDisposed()) {
y -= notification.getShell().getSize().y + NOTIFICATIONS_GAP;
}
}
getShell().setLocation(x, y);
getShell().setSize(size);
activeNotifications.add(this);
}

private Rectangle getPrimaryClientArea() {
Monitor primaryMonitor = getShell().getDisplay().getPrimaryMonitor();
return primaryMonitor != null ? primaryMonitor.getClientArea() : getShell().getDisplay().getClientArea();
}

private void repositionNotifications() {
Rectangle clArea = getPrimaryClientArea();
Point initialSize = getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT);
int height = Math.max(initialSize.y, MIN_HEIGHT);
int width = Math.min(initialSize.x, MAX_WIDTH);
int x = clArea.x + clArea.width - width - PADDING_EDGE;
int y = clArea.height + clArea.y - height - PADDING_EDGE;
for (ToolkitNotification notification : activeNotifications) {
if (!notification.getShell().isDisposed()) {
Point size = notification.getShell().getSize();
notification.getShell().setLocation(x, y);
y -= size.y + NOTIFICATIONS_GAP;
}
}
}

@Override
public boolean close() {
activeNotifications.remove(this);
repositionNotifications();
if (this.infoIcon != null && !this.infoIcon.isDisposed()) {
this.infoIcon.dispose();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Font;
Expand All @@ -34,7 +37,9 @@
import software.aws.toolkits.eclipse.amazonq.util.Constants;
import software.aws.toolkits.eclipse.amazonq.util.PluginLogger;
import software.aws.toolkits.eclipse.amazonq.util.ThreadingUtils;
import software.aws.toolkits.eclipse.amazonq.util.ToolkitNotification;
import software.aws.toolkits.eclipse.amazonq.views.model.Customization;
import org.eclipse.mylyn.commons.ui.dialogs.AbstractNotificationPopup;

public final class CustomizationDialog extends Dialog {

Expand All @@ -45,7 +50,7 @@ public final class CustomizationDialog extends Dialog {
private Font boldFont;
private List<Customization> customizationsResponse;
private ResponseSelection responseSelection;
private String selectedCustomisationArn;
private Customization selectedCustomization;

public enum ResponseSelection {
AMAZON_Q_FOUNDATION_DEFAULT,
Expand Down Expand Up @@ -80,6 +85,10 @@ private final class CustomRadioButton extends Composite {
public Button getRadioButton() {
return radioButton;
}

public Label getTextLabel() {
return textLabel;
}
}

public CustomizationDialog(final Shell parentShell) {
Expand All @@ -102,12 +111,19 @@ public ResponseSelection getResponseSelection() {
return this.responseSelection;
}

public void setSelectedCustomizationArn(final String arn) {
this.selectedCustomisationArn = arn;
public void setSelectedCustomization(final Customization customization) {
this.selectedCustomization = customization;
}

public Customization getSelectedCustomization() {
return this.selectedCustomization;
}

public String getSelectedCustomizationArn() {
return this.selectedCustomisationArn;
private void showNotification(final String customizationName) {
AbstractNotificationPopup notification = new ToolkitNotification(Display.getCurrent(),
Constants.IDE_CUSTOMIZATION_NOTIFICATION_TITLE,
String.format(Constants.IDE_CUSTOMIZATION_NOTIFICATION_BODY_TEMPLATE, customizationName));
notification.open();
}

@Override
Expand All @@ -118,16 +134,17 @@ protected void createButtonsForButtonBar(final Composite parent) {

@Override
protected void okPressed() {
PluginLogger.info(String.format("Select pressed with responseSelection:%s and selectedArn:%s", this.responseSelection, this.selectedCustomisationArn));
if (this.responseSelection.equals(ResponseSelection.AMAZON_Q_FOUNDATION_DEFAULT)) {
PluginStore.remove(Constants.CUSTOMIZATION_STORAGE_INTERNAL_KEY);
} else if (this.selectedCustomisationArn != null) {
PluginStore.put(Constants.CUSTOMIZATION_STORAGE_INTERNAL_KEY, this.selectedCustomisationArn);
Display.getCurrent().asyncExec(() -> showNotification(Constants.DEFAULT_Q_FOUNDATION_DISPLAY_NAME));
} else if (Objects.nonNull(this.getSelectedCustomization()) && StringUtils.isNotBlank(this.getSelectedCustomization().getName())) {
PluginStore.putObject(Constants.CUSTOMIZATION_STORAGE_INTERNAL_KEY, this.getSelectedCustomization());
Map<String, Object> updatedSettings = new HashMap<>();
Map<String, String> internalMap = new HashMap<>();
internalMap.put(Constants.LSP_CUSTOMIZATION_CONFIGURATION_KEY, this.selectedCustomisationArn);
internalMap.put(Constants.LSP_CUSTOMIZATION_CONFIGURATION_KEY, this.getSelectedCustomization().getArn());
updatedSettings.put(Constants.LSP_CONFIGURATION_KEY, internalMap);
ThreadingUtils.executeAsyncTask(() -> CustomizationUtil.triggerChangeConfigurationNotification(updatedSettings));
Display.getCurrent().asyncExec(() -> showNotification(String.format("%s customization", this.getSelectedCustomization().getName())));
}
super.okPressed();
}
Expand Down Expand Up @@ -179,10 +196,10 @@ private void updateComboOnUIThread(final List<Customization> customizations) {
int defaultSelectedDropdownIndex = -1;
for (int index = 0; index < customizations.size(); index++) {
addFormattedOption(combo, customizations.get(index).getName(), customizations.get(index).getDescription());
combo.setData(String.format("%s", index), customizations.get(index).getArn());
combo.setData(String.format("%s", index), customizations.get(index));
if (this.responseSelection.equals(ResponseSelection.CUSTOMIZATION)
&& StringUtils.isNotBlank(this.selectedCustomisationArn)
&& this.selectedCustomisationArn.equals(customizations.get(index).getArn())) {
&& Objects.nonNull(this.getSelectedCustomization())
&& this.getSelectedCustomization().getArn().equals(customizations.get(index).getArn())) {
defaultSelectedDropdownIndex = index;
}
}
Expand All @@ -197,9 +214,9 @@ private void updateComboOnUIThread(final List<Customization> customizations) {
public void widgetSelected(final SelectionEvent e) {
int selectedIndex = combo.getSelectionIndex();
String selectedOption = combo.getItem(selectedIndex);
String selectedCustomizationArn = (String) combo.getData(String.valueOf(selectedIndex));
CustomizationDialog.this.selectedCustomisationArn = selectedCustomizationArn;
PluginLogger.info(String.format("Selected option:%s with arn:%s", selectedOption, selectedCustomizationArn));
Customization selectedCustomization = (Customization) combo.getData(String.valueOf(selectedIndex));
CustomizationDialog.this.setSelectedCustomization(selectedCustomization);
PluginLogger.info(String.format("Selected option:%s with arn:%s", selectedOption, selectedCustomization.getArn()));
}
});
}
Expand Down Expand Up @@ -254,7 +271,17 @@ protected Control createDialogArea(final Composite parent) {
public void widgetSelected(final SelectionEvent e) {
customizationButton.getRadioButton().setSelection(false);
responseSelection = ResponseSelection.AMAZON_Q_FOUNDATION_DEFAULT;
selectedCustomisationArn = null;
setSelectedCustomization(null);
combo.setEnabled(false);
}
});
defaultAmazonQFoundationButton.getTextLabel().addMouseListener(new MouseAdapter() {
@Override
public void mouseDown(final MouseEvent e) {
customizationButton.getRadioButton().setSelection(false);
defaultAmazonQFoundationButton.getRadioButton().setSelection(true);
responseSelection = ResponseSelection.AMAZON_Q_FOUNDATION_DEFAULT;
setSelectedCustomization(null);
combo.setEnabled(false);
}
});
Expand All @@ -266,6 +293,15 @@ public void widgetSelected(final SelectionEvent e) {
combo.setEnabled(true);
}
});
customizationButton.getTextLabel().addMouseListener(new MouseAdapter() {
@Override
public void mouseDown(final MouseEvent e) {
defaultAmazonQFoundationButton.getRadioButton().setSelection(false);
customizationButton.getRadioButton().setSelection(true);
responseSelection = ResponseSelection.CUSTOMIZATION;
combo.setEnabled(true);
}
});
createDropdownForCustomizations(container);
createSeparator(container);
return container;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

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

import java.util.Objects;
import org.eclipse.jface.action.ContributionItem;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
Expand All @@ -13,11 +14,11 @@
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IViewSite;
import jakarta.inject.Inject;
import software.amazon.awssdk.utils.StringUtils;
import software.aws.toolkits.eclipse.amazonq.configuration.PluginStore;
import software.aws.toolkits.eclipse.amazonq.util.AuthStatusChangedListener;
import software.aws.toolkits.eclipse.amazonq.util.Constants;
import software.aws.toolkits.eclipse.amazonq.views.CustomizationDialog;
import software.aws.toolkits.eclipse.amazonq.views.model.Customization;
import software.aws.toolkits.eclipse.amazonq.views.CustomizationDialog.ResponseSelection;

public final class CustomizationDialogContributionItem extends ContributionItem implements AuthStatusChangedListener {
Expand Down Expand Up @@ -53,13 +54,13 @@ public void fill(final Menu menu, final int index) {
@Override
public void widgetSelected(final SelectionEvent e) {
CustomizationDialog dialog = new CustomizationDialog(shell);
String storedCustomizationArn = PluginStore.get(Constants.CUSTOMIZATION_STORAGE_INTERNAL_KEY);
if (StringUtils.isBlank(storedCustomizationArn)) {
Customization storedCustomization = PluginStore.getObject(Constants.CUSTOMIZATION_STORAGE_INTERNAL_KEY, Customization.class);
if (Objects.isNull(storedCustomization)) {
dialog.setResponseSelection(ResponseSelection.AMAZON_Q_FOUNDATION_DEFAULT);
dialog.setSelectedCustomizationArn(null);
dialog.setSelectedCustomization(null);
} else {
dialog.setResponseSelection(ResponseSelection.CUSTOMIZATION);
dialog.setSelectedCustomizationArn(storedCustomizationArn);
dialog.setSelectedCustomization(storedCustomization);
}
dialog.open();
}
Expand Down

0 comments on commit 7e8933a

Please sign in to comment.