Skip to content

Commit ee23492

Browse files
authored
Merge pull request #3882 from microsoft/feature-eclipse
Merge Eclipse Customer Survey to Develop
2 parents c9d3b1b + 48d8b7c commit ee23492

File tree

6 files changed

+359
-5
lines changed

6 files changed

+359
-5
lines changed

BuildDevint

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,13 @@ set -x
5252

5353
# Build Utils
5454
echo "Building Utils ..."
55-
mvn install -f $SCRIPTPATH/Utils/pom.xml -Dmaven.repo.local=$SCRIPTPATH/.repository -Dcheckstyle.skip=$SKIP_CHECKSTYLE $MAVEN_QUIET
56-
mvn install -f $SCRIPTPATH/PluginsAndFeatures/AddLibrary/AzureLibraries/pom.xml -Dmaven.repo.local=$SCRIPTPATH/.repository $MAVEN_QUIET
55+
mvn install -B -f $SCRIPTPATH/Utils/pom.xml -Dmaven.repo.local=$SCRIPTPATH/.repository -Dcheckstyle.skip=$SKIP_CHECKSTYLE $MAVEN_QUIET
56+
mvn install -B -f $SCRIPTPATH/PluginsAndFeatures/AddLibrary/AzureLibraries/pom.xml -Dmaven.repo.local=$SCRIPTPATH/.repository $MAVEN_QUIET
5757

5858
# Build Eclipse plugin
5959
if $BUILD_ECLIPSE; then
6060
echo "Building Eclipse plugin ..."
61-
mvn clean install -f $SCRIPTPATH/PluginsAndFeatures/azure-toolkit-for-eclipse/pom.xml $MAVEN_QUIET
61+
mvn clean install -B -f $SCRIPTPATH/PluginsAndFeatures/azure-toolkit-for-eclipse/pom.xml $MAVEN_QUIET
6262
cp ./PluginsAndFeatures/azure-toolkit-for-eclipse/WindowsAzurePlugin4EJ/target/WindowsAzurePlugin4EJ*.zip ./$ARTIFACTS_DIR/WindowsAzurePlugin4EJ.zip
6363
fi
6464

PluginsAndFeatures/azure-toolkit-for-eclipse/com.microsoft.azuretools.core/META-INF/MANIFEST.MF

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ Require-Bundle: org.eclipse.ui,
2626
org.eclipse.m2e.core,
2727
org.eclipse.m2e.maven.runtime,
2828
org.eclipse.m2e.launching,
29-
org.eclipse.equinox.registry
29+
org.eclipse.equinox.registry,
30+
org.eclipse.mylyn.commons.core,
31+
org.eclipse.mylyn.commons.ui
3032
Bundle-ActivationPolicy: lazy
3133
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
3234
Bundle-Vendor: Microsoft Corp.
82 Bytes
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation
3+
*
4+
* All rights reserved.
5+
*
6+
* MIT License
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
9+
* (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge,
10+
* publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
11+
* subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
14+
*
15+
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
17+
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
18+
* THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19+
*/
20+
package com.microsoft.azuretools.core.survey;
21+
22+
import static com.microsoft.azuretools.Constants.FILE_NAME_SURVEY_CONFIG;
23+
import static com.microsoft.azuretools.telemetry.TelemetryConstants.SURVEY;
24+
import static com.microsoft.azuretools.telemetry.TelemetryConstants.SYSTEM;
25+
26+
import java.io.File;
27+
import java.io.FileOutputStream;
28+
import java.io.FileReader;
29+
import java.io.IOException;
30+
import java.nio.charset.Charset;
31+
import java.util.HashMap;
32+
import java.util.Map;
33+
import java.util.concurrent.TimeUnit;
34+
35+
import org.apache.commons.io.IOUtils;
36+
import org.eclipse.core.runtime.Platform;
37+
import org.eclipse.swt.program.Program;
38+
import org.eclipse.swt.widgets.Display;
39+
import org.joda.time.LocalDateTime;
40+
import org.osgi.framework.Version;
41+
42+
import com.fasterxml.jackson.annotation.JsonProperty;
43+
import com.fasterxml.jackson.databind.ObjectMapper;
44+
import com.fasterxml.jackson.databind.ObjectWriter;
45+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
46+
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
47+
import com.fasterxml.jackson.datatype.joda.deser.LocalDateTimeDeserializer;
48+
import com.fasterxml.jackson.datatype.joda.ser.LocalDateTimeSerializer;
49+
import com.microsoft.azuretools.authmanage.CommonSettings;
50+
import com.microsoft.azuretools.core.Activator;
51+
import com.microsoft.azuretools.telemetry.AppInsightsClient;
52+
import com.microsoft.azuretools.telemetrywrapper.EventType;
53+
import com.microsoft.azuretools.telemetrywrapper.EventUtil;
54+
import com.microsoft.azuretools.telemetrywrapper.Operation;
55+
import com.microsoft.azuretools.telemetrywrapper.TelemetryManager;
56+
57+
import rx.Observable;
58+
import rx.schedulers.Schedulers;
59+
60+
public class CustomerSurveyHelper {
61+
62+
private static final int POP_UP_DELAY = 30;
63+
private static final int INIT_SURVEY_DELAY_BY_DAY = 10;
64+
private static final int PUT_OFF_DELAY_BY_DAY = 30;
65+
private static final int TAKE_SURVEY_DELAY_BY_DAY = 180;
66+
// Eclipse pop up window can't reset dispost time, so use a longer value here which differs IntelliJ(10s)
67+
private static final int DISPOSE_TIME = 20;
68+
69+
private static final String SURVEY_URL = "https://microsoft.qualtrics.com/jfe/form/SV_5nhMbnPVKPLu2pv?"
70+
+ "toolkit=%s&ide=%s&os=%s&jdk=%s&id=%s";
71+
72+
private static final String TELEMETRY_KEY_RESPONSE = "response";
73+
private static final String TELEMETRY_VALUE_NEVER_SHOW = "neverShowAgain";
74+
private static final String TELEMETRY_VALUE_PUT_OFF = "putOff";
75+
private static final String TELEMETRY_VALUE_ACCEPT = "accept";
76+
77+
private boolean isShown = false;
78+
private SurveyConfig surveyConfig;
79+
private Operation operation;
80+
81+
public CustomerSurveyHelper() {
82+
loadConfiguration();
83+
}
84+
85+
public void showFeedbackNotification() {
86+
if (isAbleToPopUpSurvey()) {
87+
Observable.timer(POP_UP_DELAY, TimeUnit.SECONDS).subscribeOn(Schedulers.io()).take(1).subscribe(next -> {
88+
Display.getDefault().syncExec(() -> {
89+
SurveyNotificationPopup dialog = new SurveyNotificationPopup(Display.getDefault(),
90+
() -> {
91+
takeSurvey();
92+
},
93+
() -> {
94+
putOff();
95+
},
96+
() -> {
97+
neverShowAgain();
98+
});
99+
100+
dialog.setFadingEnabled(false);
101+
dialog.setDelayClose(DISPOSE_TIME * 1000);
102+
dialog.open();
103+
synchronized (CustomerSurveyHelper.class) {
104+
if (operation != null) {
105+
operation.complete();
106+
}
107+
operation = TelemetryManager.createOperation(SYSTEM, SURVEY);
108+
operation.start();
109+
}
110+
});
111+
});
112+
}
113+
}
114+
115+
private void takeSurvey() {
116+
Program.launch(getRequestUrl());
117+
surveyConfig.surveyTimes++;
118+
surveyConfig.lastSurveyDate = LocalDateTime.now();
119+
surveyConfig.nextSurveyDate = LocalDateTime.now().plusDays(TAKE_SURVEY_DELAY_BY_DAY);
120+
saveConfiguration();
121+
sendTelemetry(TELEMETRY_VALUE_ACCEPT);
122+
}
123+
124+
private void neverShowAgain() {
125+
surveyConfig.isAcceptSurvey = false;
126+
saveConfiguration();
127+
sendTelemetry(TELEMETRY_VALUE_NEVER_SHOW);
128+
}
129+
130+
private void putOff() {
131+
surveyConfig.nextSurveyDate = LocalDateTime.now().plusDays(PUT_OFF_DELAY_BY_DAY);
132+
saveConfiguration();
133+
sendTelemetry(TELEMETRY_VALUE_PUT_OFF);
134+
}
135+
136+
private void loadConfiguration() {
137+
try (final FileReader fileReader = new FileReader(getConfigFile())) {
138+
String configString = IOUtils.toString(fileReader);
139+
ObjectMapper mapper = new ObjectMapper();
140+
surveyConfig = mapper.readValue(configString, SurveyConfig.class);
141+
} catch (IOException e) {
142+
surveyConfig = new SurveyConfig();
143+
saveConfiguration();
144+
}
145+
}
146+
147+
private void saveConfiguration() {
148+
try {
149+
File configFile = getConfigFile();
150+
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
151+
IOUtils.write(ow.writeValueAsString(surveyConfig), new FileOutputStream(configFile),
152+
Charset.defaultCharset());
153+
} catch (IOException e) {
154+
// swallow this exception as survey config should not bother user
155+
}
156+
}
157+
158+
private synchronized void sendTelemetry(String response) {
159+
if (operation == null) {
160+
return;
161+
}
162+
Map<String, String> properties = new HashMap<>();
163+
properties.put(TELEMETRY_KEY_RESPONSE, response);
164+
EventUtil.logEvent(EventType.info, operation, properties);
165+
operation.complete();
166+
}
167+
168+
private synchronized boolean isAbleToPopUpSurvey() {
169+
if (isShown) {
170+
return false;
171+
}
172+
isShown = true;
173+
return surveyConfig.isAcceptSurvey && LocalDateTime.now().isAfter(surveyConfig.nextSurveyDate);
174+
}
175+
176+
private static File getConfigFile() {
177+
return new File(CommonSettings.getSettingsBaseDir(), FILE_NAME_SURVEY_CONFIG);
178+
}
179+
180+
private String getRequestUrl() {
181+
final Version version = Platform.getBundle("org.eclipse.platform").getVersion();
182+
String ide = String.format("%s %s", "eclipse", version.toString());
183+
String os = System.getProperty("os.name");
184+
String jdk = String.format("%s %s", System.getProperty("java.vendor"), System.getProperty("java.version"));
185+
String id = AppInsightsClient.getInstallationId();
186+
String toolkitVersion = Activator.getDefault().getBundle().getVersion().toString();
187+
return String.format(SURVEY_URL, toolkitVersion, ide, os, jdk, id).replace(' ', '+');
188+
}
189+
190+
static class SurveyConfig {
191+
@JsonProperty("surveyTimes")
192+
private int surveyTimes = 0;
193+
@JsonProperty("isAcceptSurvey")
194+
private boolean isAcceptSurvey = true;
195+
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
196+
@JsonSerialize(using = LocalDateTimeSerializer.class)
197+
private LocalDateTime lastSurveyDate = null;
198+
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
199+
@JsonSerialize(using = LocalDateTimeSerializer.class)
200+
private LocalDateTime nextSurveyDate = LocalDateTime.now().plusDays(INIT_SURVEY_DELAY_BY_DAY);
201+
}
202+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation
3+
*
4+
* All rights reserved.
5+
*
6+
* MIT License
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
9+
* (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge,
10+
* publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
11+
* subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
14+
*
15+
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
17+
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
18+
* THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19+
*/
20+
package com.microsoft.azuretools.core.survey;
21+
22+
import org.eclipse.mylyn.commons.ui.dialogs.AbstractNotificationPopup;
23+
import org.eclipse.swt.SWT;
24+
import org.eclipse.swt.events.SelectionAdapter;
25+
import org.eclipse.swt.events.SelectionEvent;
26+
import org.eclipse.swt.graphics.Font;
27+
import org.eclipse.swt.graphics.FontData;
28+
import org.eclipse.swt.graphics.Image;
29+
import org.eclipse.swt.layout.GridData;
30+
import org.eclipse.swt.layout.GridLayout;
31+
import org.eclipse.swt.widgets.Button;
32+
import org.eclipse.swt.widgets.Composite;
33+
import org.eclipse.swt.widgets.Display;
34+
import org.eclipse.swt.widgets.Label;
35+
36+
import com.microsoft.azuretools.core.Activator;
37+
38+
public class SurveyNotificationPopup extends AbstractNotificationPopup {
39+
private static final String SURVEY_WINDOW_TITLE = "Azure Toolkit Survey";
40+
private static final String SURVEY_TEXT_HEADER = "Enjoy Azure Toolkits?";
41+
private static final String SURVEY_TEXT_BODY = "Your feedback is important, take a minute to fill out our survey.";
42+
private static final String PUT_OFF_BUTTON_TEXT = "Not now";
43+
private static final String TAKE_SURVEY_BUTTON_TEXT = "Give feedback";
44+
private static final String NEVER_SHOW_AGAIN_BUTTON_TEXT = "Don't show again";
45+
46+
private Runnable takeSurveyAction;
47+
private Runnable putOffAction;
48+
private Runnable neverShowAgainAction;
49+
private boolean isActionTaken = false;
50+
51+
public SurveyNotificationPopup(Display display, Runnable takeSurveyAction, Runnable putOffAction, Runnable neverShowAgainAction) {
52+
super(display);
53+
this.takeSurveyAction = takeSurveyAction;
54+
this.putOffAction = putOffAction;
55+
this.neverShowAgainAction = neverShowAgainAction;
56+
}
57+
58+
@Override
59+
protected void createContentArea(Composite parent) {
60+
final Composite container = new Composite(parent, SWT.NULL);
61+
container.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
62+
container.setLayout(new GridLayout(1, false));
63+
64+
final Label surveyLogo = new Label(container, SWT.NONE);
65+
66+
final Image azureSmallLogo = Activator.getImageDescriptor("icons/azure_small.png").createImage();
67+
surveyLogo.setImage(azureSmallLogo);
68+
surveyLogo.setSize(50, 50);
69+
final GridData logoLayoutData = new GridData(GridData.HORIZONTAL_ALIGN_CENTER);
70+
logoLayoutData.widthHint = 50;
71+
surveyLogo.setLayoutData(logoLayoutData);
72+
73+
final Label surveryHeader = new Label(container, SWT.NULL);
74+
surveryHeader.setText(SURVEY_TEXT_HEADER);
75+
surveryHeader.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_CENTER));
76+
77+
new Label(container, SWT.NONE);
78+
79+
final Label surveyBody = new Label(container, SWT.NONE | SWT.WRAP);
80+
surveyBody.setText(SURVEY_TEXT_BODY);
81+
82+
if (surveyBody.getFont().getFontData().length >= 0) {
83+
FontData fontData = surveyBody.getFont().getFontData()[0];
84+
Font font = new Font(Display.getCurrent(),
85+
new FontData(fontData.getName(), fontData.getHeight(), SWT.BOLD));
86+
surveryHeader.setFont(font);
87+
}
88+
final GridData surveyBodyLayoutData = new GridData(GridData.HORIZONTAL_ALIGN_CENTER);
89+
surveyBodyLayoutData.widthHint = 200;
90+
surveyBody.setLayoutData(surveyBodyLayoutData);
91+
92+
final Button takeSurveyButton = new Button(container, SWT.PUSH);
93+
takeSurveyButton.setText(TAKE_SURVEY_BUTTON_TEXT);
94+
takeSurveyButton.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_CENTER));
95+
takeSurveyButton.getShell().setDefaultButton(takeSurveyButton);
96+
97+
final Button putOffButton = new Button(container, SWT.PUSH);
98+
putOffButton.setText(PUT_OFF_BUTTON_TEXT);
99+
putOffButton.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_CENTER));
100+
101+
final Button neverShowAgainButton = new Button(container, SWT.PUSH);
102+
neverShowAgainButton.setText(NEVER_SHOW_AGAIN_BUTTON_TEXT);
103+
neverShowAgainButton.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_CENTER));
104+
105+
takeSurveyButton.addSelectionListener(new SelectionAdapter() {
106+
@Override
107+
public void widgetSelected(SelectionEvent e) {
108+
isActionTaken = true;
109+
takeSurveyAction.run();
110+
close();
111+
}
112+
});
113+
114+
putOffButton.addSelectionListener(new SelectionAdapter() {
115+
@Override
116+
public void widgetSelected(SelectionEvent e) {
117+
close();
118+
}
119+
});
120+
121+
122+
neverShowAgainButton.addSelectionListener(new SelectionAdapter() {
123+
@Override
124+
public void widgetSelected(SelectionEvent e) {
125+
isActionTaken = true;
126+
neverShowAgainAction.run();
127+
close();
128+
}
129+
});
130+
131+
}
132+
133+
@Override
134+
public boolean close() {
135+
// default to putOff if we don't get option from user
136+
if (!isActionTaken) {
137+
putOffAction.run();
138+
}
139+
return super.close();
140+
}
141+
142+
@Override
143+
protected String getPopupShellTitle() {
144+
return SURVEY_WINDOW_TITLE;
145+
}
146+
}
147+

0 commit comments

Comments
 (0)