Skip to content

Commit 9219207

Browse files
Merge pull request #6084 from microsoft/feature-validation
refactor validation framework to improve user experience and fix several related bugs.
2 parents a05ca00 + 746d4fe commit 9219207

File tree

10 files changed

+156
-88
lines changed

10 files changed

+156
-88
lines changed

PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-lib/src/main/java/com/microsoft/azure/toolkit/intellij/common/AzureComboBox.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public AzureComboBox(boolean refresh) {
7070
super();
7171
this.init();
7272
this.refresher = new TailingDebouncer(this::doRefreshItems, DEBOUNCE_DELAY);
73-
this.valueDebouncer = new TailingDebouncer(() -> this.fireValueChangedEvent(this.getValue()), DEBOUNCE_DELAY);
73+
this.valueDebouncer = new TailingDebouncer(this::fireValueChangedEvent, DEBOUNCE_DELAY);
7474
if (refresh) {
7575
this.refreshItems();
7676
}

PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-lib/src/main/java/com/microsoft/azure/toolkit/intellij/common/AzureDialog.java

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,22 @@
44
*/
55
package com.microsoft.azure.toolkit.intellij.common;
66

7+
import com.intellij.openapi.Disposable;
78
import com.intellij.openapi.project.Project;
89
import com.intellij.openapi.ui.DialogWrapper;
910
import com.intellij.openapi.ui.ValidationInfo;
11+
import com.intellij.openapi.util.Disposer;
1012
import com.microsoft.azure.toolkit.lib.common.form.AzureForm;
1113
import com.microsoft.azure.toolkit.lib.common.form.AzureFormInput;
1214
import com.microsoft.azure.toolkit.lib.common.form.AzureValidationInfo;
1315
import com.microsoft.azure.toolkit.lib.common.messager.AzureMessager;
1416
import lombok.extern.java.Log;
1517

16-
import javax.swing.*;
1718
import java.util.List;
1819
import java.util.Objects;
19-
import java.util.Optional;
2020
import java.util.stream.Collectors;
2121

2222
import static com.microsoft.azure.toolkit.lib.common.form.AzureValidationInfo.Type.SUCCESS;
23-
import static com.microsoft.azure.toolkit.lib.common.form.AzureValidationInfo.Type.WARNING;
2423

2524
@Log
2625
public abstract class AzureDialog<T> extends DialogWrapper {
@@ -33,7 +32,17 @@ public AzureDialog(Project project) {
3332
}
3433

3534
public AzureDialog() {
36-
super(null);
35+
this(null);
36+
}
37+
38+
@Override
39+
protected void init() {
40+
super.init();
41+
for (final AzureFormInput<?> input : this.getForm().getInputs()) {
42+
if (input instanceof Disposable) {
43+
Disposer.register(this.getDisposable(), (Disposable) input);
44+
}
45+
}
3746
}
3847

3948
@Override
@@ -57,23 +66,13 @@ public void close() {
5766
@Override
5867
protected List<ValidationInfo> doValidateAll() {
5968
final List<AzureValidationInfo> infos = this.getForm().getAllValidationInfos(true);
60-
this.setOKActionEnabled(infos.stream().allMatch(AzureValidationInfo::isValid));
69+
// this.setOKActionEnabled(infos.stream().allMatch(AzureValidationInfo::isValid));
6170
return infos.stream()
6271
.filter(i -> i.getType() != SUCCESS)
63-
.map(AzureDialog::toIntellijValidationInfo)
72+
.map(AzureFormInputComponent::toIntellijValidationInfo)
6473
.collect(Collectors.toList());
6574
}
6675

67-
private static ValidationInfo toIntellijValidationInfo(final AzureValidationInfo info) {
68-
final AzureFormInput<?> input = info.getInput();
69-
final JComponent component = input instanceof AzureFormInputComponent ? ((AzureFormInputComponent<?>) input).getInputComponent() : null;
70-
final ValidationInfo v = new ValidationInfo(Optional.ofNullable(info.getMessage()).orElse("Unknown error"), component);
71-
if (info.getType() == WARNING) {
72-
v.asWarning();
73-
}
74-
return v;
75-
}
76-
7776
public abstract AzureForm<T> getForm();
7877

7978
protected abstract String getDialogTitle();

PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-lib/src/main/java/com/microsoft/azure/toolkit/intellij/common/AzureFormInputComponent.java

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,49 @@
55

66
package com.microsoft.azure.toolkit.intellij.common;
77

8+
import com.intellij.openapi.Disposable;
9+
import com.intellij.openapi.ui.ComponentValidator;
10+
import com.intellij.openapi.ui.ValidationInfo;
811
import com.microsoft.azure.toolkit.lib.common.form.AzureFormInput;
912
import com.microsoft.azure.toolkit.lib.common.form.AzureValidationInfo;
13+
import com.microsoft.azure.toolkit.lib.common.task.AzureTaskManager;
1014

1115
import javax.accessibility.AccessibleRelation;
12-
import javax.annotation.Nonnull;
16+
import javax.annotation.Nullable;
1317
import javax.swing.*;
18+
import java.util.Objects;
1419
import java.util.Optional;
1520

16-
public interface AzureFormInputComponent<T> extends AzureFormInput<T> {
21+
import static com.microsoft.azure.toolkit.lib.common.form.AzureValidationInfo.Type.PENDING;
22+
import static com.microsoft.azure.toolkit.lib.common.form.AzureValidationInfo.Type.SUCCESS;
23+
import static com.microsoft.azure.toolkit.lib.common.form.AzureValidationInfo.Type.WARNING;
24+
25+
public interface AzureFormInputComponent<T> extends AzureFormInput<T>, Disposable {
1726
default JComponent getInputComponent() {
1827
return (JComponent) this;
1928
}
2029

21-
/**
22-
* NOTE: don't override
23-
*/
24-
@Nonnull
2530
@Override
26-
default AzureValidationInfo validateInternal(T value) {
27-
if (!this.getInputComponent().isEnabled() || !this.getInputComponent().isVisible()) {
28-
return AzureValidationInfo.success(this);
31+
default boolean needValidation() {
32+
final JComponent comp = this.getInputComponent();
33+
return AzureFormInput.super.needValidation() && comp.isEnabled() && comp.isVisible();
34+
}
35+
36+
@Override
37+
default void setValidationInfo(@Nullable AzureValidationInfo vi) {
38+
AzureFormInput.super.setValidationInfo(vi);
39+
final ValidationInfo info = Objects.nonNull(vi) && (vi.getType() == PENDING || vi.getType() == SUCCESS) ? null : toIntellijValidationInfo(vi);
40+
final String state = Objects.isNull(info) ? null : info.warning ? "warning" : "error";
41+
final JComponent input = this.getInputComponent();
42+
// see com.intellij.openapi.ui.ComponentValidator.updateInfo
43+
input.putClientProperty("JComponent.outline", state);
44+
input.revalidate();
45+
input.repaint();
46+
// see com.intellij.openapi.ui.DialogWrapper.setErrorInfoAll
47+
final ComponentValidator v = ComponentValidator.getInstance(input).orElseGet(() -> (new ComponentValidator(this)).installOn(input));
48+
if (v != null) {
49+
AzureTaskManager.getInstance().runLater(() -> v.updateInfo(info));
2950
}
30-
return AzureFormInput.super.validateInternal(value);
3151
}
3252

3353
@Override
@@ -37,4 +57,23 @@ default String getLabel() {
3757
.map(t -> t.endsWith(":") ? t.substring(0, t.length() - 1) : t)
3858
.orElse(this.getClass().getSimpleName());
3959
}
60+
61+
@Nullable
62+
static ValidationInfo toIntellijValidationInfo(@Nullable final AzureValidationInfo info) {
63+
if (Objects.isNull(info)) {
64+
return null;
65+
}
66+
final AzureFormInput<?> input = info.getInput();
67+
final JComponent component = input instanceof AzureFormInputComponent ? ((AzureFormInputComponent<?>) input).getInputComponent() : null;
68+
final ValidationInfo v = new ValidationInfo(Optional.ofNullable(info.getMessage()).orElse("Unknown error"), component);
69+
if (info.getType() == WARNING) {
70+
v.asWarning();
71+
}
72+
return v;
73+
}
74+
75+
@Override
76+
default void dispose() {
77+
this.clearAll();
78+
}
4079
}
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
package com.microsoft.azure.toolkit.intellij.common;
77

8-
import com.intellij.ui.components.JBTextField;
98
import com.microsoft.azure.toolkit.lib.common.form.AzureValidationInfo;
109
import lombok.Getter;
1110
import lombok.Setter;
@@ -14,7 +13,7 @@
1413
import javax.annotation.Nonnull;
1514
import javax.annotation.Nullable;
1615

17-
public class IntegerTextField extends JBTextField implements AzureFormInputComponent<Integer> {
16+
public class AzureIntegerInput extends BaseAzureTextInput<Integer> {
1817

1918
@Setter
2019
@Getter
@@ -27,7 +26,10 @@ public class IntegerTextField extends JBTextField implements AzureFormInputCompo
2726
@Override
2827
public Integer getValue() {
2928
final String text = getText();
30-
return (StringUtils.isNotEmpty(text) && StringUtils.isNumeric(text)) ? Integer.valueOf(getText()) : null;
29+
if (StringUtils.isBlank(text) || !StringUtils.isNumeric(text)) {
30+
throw new NumberFormatException(String.format("\"%s\" is not an integer", text));
31+
}
32+
return Integer.valueOf(getText());
3133
}
3234

3335
@Override
@@ -36,9 +38,8 @@ public void setValue(final Integer val) {
3638
}
3739

3840
@Nonnull
39-
@Override
4041
public AzureValidationInfo doValidate(Integer value) {
41-
if ((minValue != null && value < minValue) || (maxValue != null && value > maxValue)) {
42+
if (value < minValue || value > maxValue) {
4243
return AzureValidationInfo.error(String.format("Value should be in range [%d, %d]", minValue, maxValue), this);
4344
} else {
4445
return AzureValidationInfo.success(this);

PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-lib/src/main/java/com/microsoft/azure/toolkit/intellij/common/AzureTextInput.java

Lines changed: 2 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,43 +5,16 @@
55

66
package com.microsoft.azure.toolkit.intellij.common;
77

8-
import com.google.common.collect.ImmutableMap;
9-
import com.intellij.icons.AllIcons;
10-
import com.intellij.ui.AnimatedIcon;
11-
import com.intellij.ui.components.fields.ExtendableTextField;
12-
import com.microsoft.azure.toolkit.lib.common.form.AzureValidationInfo;
13-
import com.microsoft.azure.toolkit.lib.common.utils.Debouncer;
14-
import com.microsoft.azure.toolkit.lib.common.utils.TailingDebouncer;
15-
168
import javax.annotation.Nullable;
179
import javax.swing.*;
18-
import java.util.Map;
19-
import java.util.Optional;
20-
import java.util.function.Function;
21-
22-
public class AzureTextInput extends ExtendableTextField
23-
implements AzureFormInputComponent<String>, TextDocumentListenerAdapter {
24-
protected static final int DEBOUNCE_DELAY = 500;
25-
private final Debouncer debouncer;
26-
private static final Extension VALIDATING = Extension.create(AnimatedIcon.Default.INSTANCE, "Validating...", null);
27-
private static final Extension SUCCESS = Extension.create(AllIcons.General.InspectionsOK, "Validation passed.", null);
28-
private static final Map<AzureValidationInfo.Type, Function<AzureValidationInfo, Extension>> extensions = ImmutableMap.of(
29-
AzureValidationInfo.Type.PENDING, (i) -> VALIDATING,
30-
AzureValidationInfo.Type.SUCCESS, (i) -> SUCCESS,
31-
AzureValidationInfo.Type.ERROR, (i) -> Extension.create(AllIcons.General.BalloonError, i.getMessage(), null),
32-
AzureValidationInfo.Type.WARNING, (i) -> Extension.create(AllIcons.General.BalloonWarning, i.getMessage(), null)
33-
);
3410

11+
public class AzureTextInput extends BaseAzureTextInput<String> {
3512
public AzureTextInput() {
3613
this(null);
3714
}
3815

3916
public AzureTextInput(@Nullable JTextField comp) {
40-
super();
41-
this.debouncer = new TailingDebouncer(() -> this.fireValueChangedEvent(this.getValue()), DEBOUNCE_DELAY);
42-
Optional.ofNullable(comp).or(() -> Optional.of(this.getInputComponent()))
43-
.ifPresent(t -> t.getDocument().addDocumentListener(this));
44-
this.trackValidation();
17+
super(comp);
4518
}
4619

4720
@Override
@@ -53,20 +26,4 @@ public String getValue() {
5326
public void setValue(final String val) {
5427
this.setText(val);
5528
}
56-
57-
public void setValidationInfo(AzureValidationInfo info) {
58-
AzureFormInputComponent.super.setValidationInfo(info);
59-
final Extension ex = extensions.getOrDefault(info.getType(), (i) -> SUCCESS).apply(info);
60-
this.setExtensions(ex);
61-
}
62-
63-
public void onDocumentChanged() {
64-
this.setValidationInfo(AzureValidationInfo.pending(this));
65-
this.debouncer.debounce();
66-
}
67-
68-
@Override
69-
public JTextField getInputComponent() {
70-
return this;
71-
}
7229
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*/
5+
6+
package com.microsoft.azure.toolkit.intellij.common;
7+
8+
import com.google.common.collect.ImmutableMap;
9+
import com.intellij.icons.AllIcons;
10+
import com.intellij.ui.AnimatedIcon;
11+
import com.intellij.ui.components.fields.ExtendableTextField;
12+
import com.microsoft.azure.toolkit.lib.common.form.AzureValidationInfo;
13+
import com.microsoft.azure.toolkit.lib.common.utils.Debouncer;
14+
import com.microsoft.azure.toolkit.lib.common.utils.TailingDebouncer;
15+
16+
import javax.annotation.Nonnull;
17+
import javax.annotation.Nullable;
18+
import javax.swing.*;
19+
import java.util.Map;
20+
import java.util.Objects;
21+
import java.util.function.Function;
22+
23+
public class BaseAzureTextInput<T> extends ExtendableTextField
24+
implements AzureFormInputComponent<T>, TextDocumentListenerAdapter {
25+
protected static final int DEBOUNCE_DELAY = 500;
26+
private final Debouncer debouncer;
27+
private static final Extension VALIDATING = Extension.create(AnimatedIcon.Default.INSTANCE, "Validating...", null);
28+
private static final Extension SUCCESS = Extension.create(AllIcons.General.InspectionsOK, "Validation passed.", null);
29+
private static final Map<AzureValidationInfo.Type, Function<AzureValidationInfo, Extension>> extensions = ImmutableMap.of(
30+
AzureValidationInfo.Type.PENDING, (i) -> VALIDATING,
31+
AzureValidationInfo.Type.SUCCESS, (i) -> SUCCESS,
32+
AzureValidationInfo.Type.ERROR, (i) -> Extension.create(AllIcons.General.BalloonError, i.getMessage(), null),
33+
AzureValidationInfo.Type.WARNING, (i) -> Extension.create(AllIcons.General.BalloonWarning, i.getMessage(), null)
34+
);
35+
36+
public BaseAzureTextInput() {
37+
super();
38+
this.debouncer = new TailingDebouncer(this::fireValueChangedEvent, DEBOUNCE_DELAY);
39+
this.getInputComponent().getDocument().addDocumentListener(this);
40+
this.trackValidation();
41+
}
42+
43+
public BaseAzureTextInput(@Nonnull JTextField comp) {
44+
super();
45+
this.debouncer = new TailingDebouncer(this::fireValueChangedEvent, DEBOUNCE_DELAY);
46+
comp.getDocument().addDocumentListener(this);
47+
this.trackValidation();
48+
}
49+
50+
public void setValidationInfo(@Nullable AzureValidationInfo info) {
51+
AzureFormInputComponent.super.setValidationInfo(info);
52+
final Extension ex = Objects.isNull(info) ? null : extensions.get(info.getType()).apply(info);
53+
this.setExtensions(ex);
54+
}
55+
56+
public void onDocumentChanged() {
57+
if (this.needValidation()) {
58+
this.setExtensions(VALIDATING);
59+
}
60+
this.debouncer.debounce();
61+
}
62+
63+
@Override
64+
public JTextField getInputComponent() {
65+
return this;
66+
}
67+
68+
@Override
69+
public String toString() {
70+
return String.format("[%s]%s", this.getClass().getSimpleName(), this.getLabel());
71+
}
72+
}

PluginsAndFeatures/azure-toolkit-for-intellij/src/main/java/com/microsoft/azure/toolkit/intellij/appservice/AppServiceMonitorPanel.form

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@
195195
<properties/>
196196
<border type="none"/>
197197
<children>
198-
<component id="45563" class="com.microsoft.azure.toolkit.intellij.common.IntegerTextField" binding="txtQuota" custom-create="true">
198+
<component id="45563" class="com.microsoft.azure.toolkit.intellij.common.AzureIntegerInput" binding="txtQuota" custom-create="true">
199199
<constraints>
200200
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
201201
</constraints>
@@ -211,7 +211,7 @@
211211
<properties/>
212212
<border type="none"/>
213213
<children>
214-
<component id="d3331" class="com.microsoft.azure.toolkit.intellij.common.IntegerTextField" binding="txtRetention" custom-create="true">
214+
<component id="d3331" class="com.microsoft.azure.toolkit.intellij.common.AzureIntegerInput" binding="txtRetention" custom-create="true">
215215
<constraints>
216216
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
217217
</constraints>

PluginsAndFeatures/azure-toolkit-for-intellij/src/main/java/com/microsoft/azure/toolkit/intellij/appservice/AppServiceMonitorPanel.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import com.intellij.ui.TitledSeparator;
1010
import com.microsoft.azure.toolkit.intellij.appservice.insights.ApplicationInsightsComboBox;
1111
import com.microsoft.azure.toolkit.intellij.common.AzureFormPanel;
12-
import com.microsoft.azure.toolkit.intellij.common.IntegerTextField;
12+
import com.microsoft.azure.toolkit.intellij.common.AzureIntegerInput;
1313
import com.microsoft.azure.toolkit.lib.appservice.ApplicationInsightsConfig;
1414
import com.microsoft.azure.toolkit.lib.appservice.MonitorConfig;
1515
import com.microsoft.azure.toolkit.lib.appservice.model.DiagnosticConfig;
@@ -43,8 +43,8 @@ public class AppServiceMonitorPanel extends JPanel implements AzureFormPanel<Mon
4343
private JPanel pnlApplicationLog;
4444
private TitledSeparator titleApplicationInsights;
4545
private TitledSeparator titleAppServiceLog;
46-
private IntegerTextField txtQuota;
47-
private IntegerTextField txtRetention;
46+
private AzureIntegerInput txtQuota;
47+
private AzureIntegerInput txtRetention;
4848
private LogLevelComboBox cbLogLevel;
4949
private JLabel lblApplicationLog;
5050

@@ -146,11 +146,11 @@ private void createUIComponents() {
146146
cbLogLevel = new LogLevelComboBox();
147147
applicationInsightsComboBox = new ApplicationInsightsComboBox();
148148

149-
txtQuota = new IntegerTextField();
149+
txtQuota = new AzureIntegerInput();
150150
txtQuota.setMinValue(25);
151151
txtQuota.setMaxValue(100);
152152

153-
txtRetention = new IntegerTextField();
153+
txtRetention = new AzureIntegerInput();
154154
txtRetention.setMinValue(0);
155155
txtRetention.setMaxValue(99999);
156156
txtRetention.setRequired(false);

0 commit comments

Comments
 (0)