Skip to content

fix: update plugin dependency checks and notifications, fixes #402 #428

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import de.php_perfect.intellij.ddev.DatabaseInfoChangedListener;
import de.php_perfect.intellij.ddev.cmd.DatabaseInfo;
import de.php_perfect.intellij.ddev.settings.DdevSettingsState;
import de.php_perfect.intellij.ddev.util.FeatureRequiredPlugins;
import de.php_perfect.intellij.ddev.util.PluginChecker;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand All @@ -30,6 +32,10 @@ public void onDatabaseInfoChanged(@Nullable DatabaseInfo databaseInfo) {
return;
}

if (PluginChecker.isMissingRequiredPlugins(this.project, FeatureRequiredPlugins.DATABASE, "database auto-registration")) {
return;
}

final DataSourceConfig.Type type = switch (databaseInfo.type()) {
case MYSQL -> DataSourceConfig.Type.MYSQL;
case MARIADB -> DataSourceConfig.Type.MARIADB;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import de.php_perfect.intellij.ddev.cmd.Description;
import de.php_perfect.intellij.ddev.dockerCompose.DdevComposeFileLoader;
import de.php_perfect.intellij.ddev.settings.DdevSettingsState;
import de.php_perfect.intellij.ddev.util.FeatureRequiredPlugins;
import de.php_perfect.intellij.ddev.util.PluginChecker;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand Down Expand Up @@ -33,6 +35,10 @@ public void onDescriptionChanged(@Nullable Description description) {
return;
}

if (PluginChecker.isMissingRequiredPlugins(this.project, FeatureRequiredPlugins.NODE_INTERPRETER, "Node.js interpreter auto-registration")) {
return;
}

final NodeInterpreterConfig nodeInterpreterConfig = new NodeInterpreterConfig(description.getName(), composeFile.getPath(), "node");
NodeInterpreterProvider.getInstance(this.project).configureNodeInterpreter(nodeInterpreterConfig);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ public interface DdevNotifier {

void notifyAlreadyLatestVersion();

void notifyMissingPlugin(@NotNull String pluginName);
void notifyMissingPlugin(@NotNull String pluginName, @NotNull String featureName);

void notifyMissingPlugins(@NotNull String pluginNames, @NotNull String featureName);

void notifyPhpInterpreterUpdated(@NotNull String phpVersion);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,25 @@ public void notifyAlreadyLatestVersion() {
}

@Override
public void notifyMissingPlugin(final @NotNull String pluginName) {
public void notifyMissingPlugin(final @NotNull String pluginName, final @NotNull String featureName) {
ApplicationManager.getApplication().invokeLater(() -> NotificationGroupManager.getInstance()
.getNotificationGroup(STICKY)
.createNotification(
DdevIntegrationBundle.message("notification.MissingPlugin.title"),
DdevIntegrationBundle.message("notification.MissingPlugin.text", pluginName),
DdevIntegrationBundle.message("notification.MissingPlugin.withFeature.text", pluginName, featureName),
NotificationType.WARNING
)
.addAction(new ManagePluginsAction())
.notify(this.project), ModalityState.nonModal());
}

@Override
public void notifyMissingPlugins(final @NotNull String pluginNames, final @NotNull String featureName) {
ApplicationManager.getApplication().invokeLater(() -> NotificationGroupManager.getInstance()
.getNotificationGroup(STICKY)
.createNotification(
DdevIntegrationBundle.message("notification.MissingPlugins.title"),
DdevIntegrationBundle.message("notification.MissingPlugins.withFeature.text", pluginNames, featureName),
NotificationType.WARNING
)
.addAction(new ManagePluginsAction())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
package de.php_perfect.intellij.ddev.php;

import com.intellij.ide.plugins.PluginManager;
import com.intellij.openapi.extensions.PluginId;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import de.php_perfect.intellij.ddev.cmd.Description;
import de.php_perfect.intellij.ddev.dockerCompose.DdevComposeFileLoader;
import de.php_perfect.intellij.ddev.notification.DdevNotifier;
import de.php_perfect.intellij.ddev.settings.DdevSettingsState;
import de.php_perfect.intellij.ddev.util.FeatureRequiredPlugins;
import de.php_perfect.intellij.ddev.util.PluginChecker;
import org.jetbrains.annotations.NotNull;

import java.util.List;

public final class ConfigurationProviderImpl implements ConfigurationProvider {
private static final List<String> REQUIRED_PLUGINS = List.of(
"org.jetbrains.plugins.phpstorm-remote-interpreter",
"org.jetbrains.plugins.phpstorm-docker",
"Docker"
);

private final @NotNull Project project;

Expand All @@ -41,15 +33,8 @@ public void configure(@NotNull Description description) {
return;
}

final var pluginManager = PluginManager.getInstance();

for (final String id : REQUIRED_PLUGINS) {
final PluginId pluginId = PluginId.findId(id);

if (pluginId == null || pluginManager.findEnabledPlugin(pluginId) == null) {
DdevNotifier.getInstance(this.project).notifyMissingPlugin(id);
return;
}
if (PluginChecker.isMissingRequiredPlugins(this.project, FeatureRequiredPlugins.PHP_INTERPRETER, "PHP interpreter auto-registration")) {
return;
}

final DdevInterpreterConfig ddevInterpreterConfig = new DdevInterpreterConfig(description.getName(), "php" + description.getPhpVersion(), composeFile.getPath());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.php_perfect.intellij.ddev.settings;

import com.intellij.icons.AllIcons;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
Expand All @@ -8,11 +9,16 @@
import com.intellij.ui.components.JBLabel;
import com.intellij.util.ui.FormBuilder;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.UIUtil;
import de.php_perfect.intellij.ddev.DdevIntegrationBundle;
import de.php_perfect.intellij.ddev.util.FeatureRequiredPlugins;
import de.php_perfect.intellij.ddev.util.PluginChecker;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import javax.swing.BoxLayout;
import java.awt.*;
import java.util.List;

public final class DdevSettingsComponent {
private final @NotNull JPanel jPanel;
Expand All @@ -32,7 +38,7 @@ public DdevSettingsComponent(Project project) {
checkForUpdatesComment.setForeground(UIManager.getColor("Component.infoForeground"));
checkForUpdatesComment.setBorder(JBUI.Borders.emptyLeft(24));
checkForUpdatesPanel.add(checkForUpdatesComment, BorderLayout.CENTER);

JPanel watchDdevPanel = new JPanel(new BorderLayout());
watchDdevPanel.add(this.watchDdevCheckbox, BorderLayout.NORTH);
JLabel watchDdevComment = new JLabel(DdevIntegrationBundle.message("settings.watchDdev.description"));
Expand All @@ -41,13 +47,35 @@ public DdevSettingsComponent(Project project) {
watchDdevComment.setBorder(JBUI.Borders.emptyLeft(24));
watchDdevPanel.add(watchDdevComment, BorderLayout.CENTER);

// Set up warning indicators and tooltips
@NotNull JPanel autoConfigureDataSourcePanel = new JPanel(new BorderLayout());
@NotNull JLabel dataSourceWarningLabel = new JLabel(AllIcons.General.Warning);
@NotNull JLabel dataSourceWarningText = new JLabel();
setupWarningIndicator(autoConfigureDataSourcePanel, this.autoConfigureDataSource, dataSourceWarningLabel,
dataSourceWarningText, FeatureRequiredPlugins.DATABASE);

@NotNull JPanel autoConfigurePhpInterpreterPanel = new JPanel(new BorderLayout());
@NotNull JLabel phpWarningLabel = new JLabel(AllIcons.General.Warning);
@NotNull JLabel phpWarningText = new JLabel();
setupWarningIndicator(autoConfigurePhpInterpreterPanel, this.autoConfigurePhpInterpreter, phpWarningLabel,
phpWarningText, FeatureRequiredPlugins.PHP_INTERPRETER);

@NotNull JPanel autoConfigureNodeJsInterpreterPanel = new JPanel(new BorderLayout());
@NotNull JLabel nodeWarningLabel = new JLabel(AllIcons.General.Warning);
@NotNull JLabel nodeWarningText = new JLabel();
setupWarningIndicator(autoConfigureNodeJsInterpreterPanel, this.autoConfigureNodeJsInterpreter, nodeWarningLabel,
nodeWarningText, FeatureRequiredPlugins.NODE_INTERPRETER);

final JPanel panel = new JPanel();
panel.setBorder(IdeBorderFactory.createTitledBorder(DdevIntegrationBundle.message("settings.automaticConfiguration"), true));
panel.setLayout(new GridBagLayout());
final GridBagConstraints gc = new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, JBUI.emptyInsets(), 0, 0);
panel.add(this.autoConfigureDataSource, gc);
panel.add(this.autoConfigurePhpInterpreter, gc);
panel.add(this.autoConfigureNodeJsInterpreter, gc);
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

// Add feature panels with some vertical spacing
panel.add(autoConfigureDataSourcePanel);
panel.add(Box.createVerticalStrut(5));
panel.add(autoConfigurePhpInterpreterPanel);
panel.add(Box.createVerticalStrut(5));
panel.add(autoConfigureNodeJsInterpreterPanel);

this.ddevBinary.addBrowseFolderListener(
project,
Expand All @@ -65,6 +93,51 @@ public DdevSettingsComponent(Project project) {
.getPanel();
}

/**
* Sets up a warning indicator for a feature that requires plugins.
*
* @param panel The panel to add the components to
* @param checkbox The checkbox for the feature
* @param warningLabel The warning icon to show if plugins are missing
* @param warningText The label to display the warning text
* @param requiredPlugins The list of required plugin IDs
*/
private void setupWarningIndicator(@NotNull JPanel panel, @NotNull JBCheckBox checkbox, @NotNull JLabel warningLabel,
@NotNull JLabel warningText, @NotNull List<String> requiredPlugins) {
// Set up panel with vertical layout
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

// Add checkbox to panel
JPanel checkboxPanel = new JPanel(new BorderLayout());
checkboxPanel.add(checkbox, BorderLayout.WEST);
panel.add(checkboxPanel);

// Check if any required plugins are missing
List<String> missingPlugins = PluginChecker.getMissingPlugins(requiredPlugins);

if (missingPlugins.isEmpty()) {
// No missing plugins, hide warning
warningLabel.setVisible(false);
warningText.setVisible(false);
} else {
// Create warning message
String warningMessage = DdevIntegrationBundle.message("settings.warning.missingPlugins", String.join(", ", missingPlugins));

// Set up warning text
warningText.setText(" " + warningMessage);
warningText.setForeground(UIUtil.getErrorForeground());

// Create warning panel with left alignment and indentation
JPanel warningPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
warningPanel.setBorder(JBUI.Borders.emptyLeft(24));
warningPanel.add(warningLabel);
warningPanel.add(warningText);

// Add warning panel below the checkbox
panel.add(warningPanel);
}
}

public @NotNull JPanel getPanel() {
return this.jPanel;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package de.php_perfect.intellij.ddev.util;

import org.jetbrains.annotations.NotNull;

import java.util.List;

/**
* Utility class to hold the required plugins for each feature.
*/
public final class FeatureRequiredPlugins {
private FeatureRequiredPlugins() {
// Utility class, no instances
}

/**
* Required plugins for PHP interpreter auto-registration.
*/
public static final @NotNull List<String> PHP_INTERPRETER = List.of(
"com.jetbrains.php",
"org.jetbrains.plugins.phpstorm-remote-interpreter",
"org.jetbrains.plugins.phpstorm-docker"
);

/**
* Required plugins for Node.js interpreter auto-registration.
*/
public static final @NotNull List<String> NODE_INTERPRETER = List.of(
"NodeJS",
"org.jetbrains.plugins.node-remote-interpreter"
);

/**
* Required plugins for database auto-registration.
*/
public static final @NotNull List<String> DATABASE = List.of(
"com.intellij.database"
);
}
67 changes: 67 additions & 0 deletions src/main/java/de/php_perfect/intellij/ddev/util/PluginChecker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package de.php_perfect.intellij.ddev.util;

import com.intellij.ide.plugins.PluginManager;
import com.intellij.openapi.extensions.PluginId;
import com.intellij.openapi.project.Project;
import de.php_perfect.intellij.ddev.notification.DdevNotifier;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.List;

/**
* Utility class to check for required plugins.
*/
public final class PluginChecker {
private PluginChecker() {
// Utility class, no instances
}

/**
* Checks if any required plugin is missing.
* If any plugins are missing, it will notify the user and return true.
*
* @param project The current project
* @param requiredPlugins List of plugin IDs to check
* @param featureName The name of the feature that requires these plugins
* @return true if any plugin is missing, false if all are available
*/
public static boolean isMissingRequiredPlugins(@NotNull Project project, @NotNull List<String> requiredPlugins, @NotNull String featureName) {
List<String> missingPluginNames = getMissingPlugins(requiredPlugins);

if (!missingPluginNames.isEmpty()) {
String missingPluginsDisplay = String.join(", ", missingPluginNames);
if (missingPluginNames.size() == 1) {
DdevNotifier.getInstance(project).notifyMissingPlugin(missingPluginsDisplay, featureName);
} else {
DdevNotifier.getInstance(project).notifyMissingPlugins(missingPluginsDisplay, featureName);
}
return true;
}

return false;
}

/**
* Checks if any required plugin is missing without showing notifications.
* This is useful for UI components that want to check dependencies without showing notifications.
*
* @param requiredPlugins List of plugin IDs to check
* @return List of missing plugin display names, empty if all are available
*/
public static @NotNull List<String> getMissingPlugins(@NotNull List<String> requiredPlugins) {
final var pluginManager = PluginManager.getInstance();
final List<String> missingPluginNames = new ArrayList<>();

for (final String id : requiredPlugins) {
final PluginId pluginId = PluginId.findId(id);

if (pluginId == null || pluginManager.findEnabledPlugin(pluginId) == null) {
String displayName = PluginDisplayNameMapper.getDisplayName(id);
missingPluginNames.add(displayName);
}
}

return missingPluginNames;
}
}
Loading
Loading