mirror of
https://github.com/carlrobertoh/ProxyAI.git
synced 2026-05-04 14:40:53 +00:00
anymous telemetry based on redhat (#212)
* initial telemetry * fixed segment bugs * Move telemetry impl to submodule, add more actions * Replace privacy policy link, minor refactoring --------- Co-authored-by: Carl-Robert Linnupuu <carlrobertoh@gmail.com>
This commit is contained in:
parent
05c7560ec9
commit
8f9980fbf1
56 changed files with 5348 additions and 32 deletions
|
|
@ -0,0 +1,18 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core;
|
||||
|
||||
import ee.carlrobert.codegpt.telemetry.core.service.TelemetryEvent;
|
||||
|
||||
public interface IMessageBroker {
|
||||
void send(TelemetryEvent event);
|
||||
void dispose();
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core;
|
||||
|
||||
import ee.carlrobert.codegpt.telemetry.core.service.TelemetryEvent;
|
||||
|
||||
public interface ITelemetryService {
|
||||
void send(TelemetryEvent event);
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.configuration;
|
||||
|
||||
import ee.carlrobert.codegpt.telemetry.core.util.Lazy;
|
||||
import java.util.Properties;
|
||||
|
||||
public abstract class AbstractConfiguration implements IConfiguration {
|
||||
|
||||
protected final Lazy<Properties> properties = new Lazy<>(this::loadProperties);
|
||||
|
||||
@Override
|
||||
public String get(String key) {
|
||||
return properties.get().getProperty(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(String key, String value) {
|
||||
properties.get().put(key, value);
|
||||
}
|
||||
|
||||
protected abstract Properties loadProperties();
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.configuration;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class ClasspathConfiguration extends FileConfiguration {
|
||||
|
||||
private final ClassLoader classloader;
|
||||
|
||||
public ClasspathConfiguration(Path file) {
|
||||
this(file, ClasspathConfiguration.class.getClassLoader());
|
||||
}
|
||||
|
||||
public ClasspathConfiguration(Path file, ClassLoader classLoader) {
|
||||
super(file);
|
||||
this.classloader = classLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InputStream createInputStream(Path path) throws FileNotFoundException {
|
||||
if (path == null) {
|
||||
return null;
|
||||
}
|
||||
return classloader.getResourceAsStream(path.toString());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.configuration;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public abstract class CompositeConfiguration implements IConfiguration {
|
||||
|
||||
@Override
|
||||
public String get(final String key) {
|
||||
List<IConfiguration> configurations = getConfigurations();
|
||||
if (configurations == null
|
||||
|| configurations.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return configurations.stream()
|
||||
.map(configuration -> configuration.get(key))
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
protected abstract List<IConfiguration> getConfigurations();
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.configuration;
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Properties;
|
||||
|
||||
public class FileConfiguration extends AbstractConfiguration {
|
||||
|
||||
private static final Logger LOGGER = Logger.getInstance(FileConfiguration.class);
|
||||
|
||||
protected final Path path;
|
||||
|
||||
public FileConfiguration(Path path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Properties loadProperties() {
|
||||
Properties properties = new Properties();
|
||||
try (InputStream in = createInputStream(path)) {
|
||||
if (in != null) {
|
||||
properties.load(in);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Could not load properties file " + (path == null? "" : path.toAbsolutePath()));
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
protected InputStream createInputStream(Path path) throws IOException {
|
||||
if (path == null) {
|
||||
return null;
|
||||
}
|
||||
File file = path.toFile();
|
||||
if (!file.exists()) {
|
||||
return null;
|
||||
}
|
||||
return new FileInputStream(file);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.configuration;
|
||||
|
||||
public interface IConfiguration {
|
||||
String get(String key);
|
||||
void put(String key, String value);
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.configuration;
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDate;
|
||||
|
||||
public class SaveableFileConfiguration extends FileConfiguration {
|
||||
|
||||
private static final Logger LOGGER = Logger.getInstance(SaveableFileConfiguration.class);
|
||||
|
||||
public SaveableFileConfiguration(Path file) {
|
||||
super(file);
|
||||
}
|
||||
|
||||
public void save() throws IOException {
|
||||
if (path == null) {
|
||||
return;
|
||||
}
|
||||
File file = path.toFile();
|
||||
file.createNewFile(); // ensure exists
|
||||
try (Writer writer = new FileWriter(file)) {
|
||||
properties.get().store(writer, "updated " + LocalDate.now());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.configuration;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
public class SystemProperties extends AbstractConfiguration {
|
||||
|
||||
@Override
|
||||
protected Properties loadProperties() {
|
||||
return System.getProperties();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.configuration;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.util.messages.Topic;
|
||||
import ee.carlrobert.codegpt.telemetry.core.util.Directories;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class TelemetryConfiguration extends CompositeConfiguration {
|
||||
|
||||
public static final String KEY_MODE = "ee.carlrobert.telemetry.mode";
|
||||
|
||||
private static final SaveableFileConfiguration FILE = new SaveableFileConfiguration(
|
||||
Directories.PATH.resolve("ee.carlrobert.intellij.telemetry"));
|
||||
|
||||
private static TelemetryConfiguration INSTANCE = new TelemetryConfiguration();
|
||||
|
||||
public static TelemetryConfiguration getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
// for testing purposes
|
||||
protected TelemetryConfiguration() {
|
||||
}
|
||||
|
||||
public void setMode(Mode mode) {
|
||||
put(KEY_MODE, mode.toString());
|
||||
}
|
||||
|
||||
public Mode getMode() {
|
||||
return Mode.safeValueOf(get(KEY_MODE));
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return getMode().isEnabled();
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
setMode(Mode.valueOf(enabled));
|
||||
}
|
||||
|
||||
public boolean isDebug() {
|
||||
return getMode() == Mode.DEBUG;
|
||||
}
|
||||
|
||||
public boolean isConfigured() {
|
||||
return getMode().isConfigured();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(String key, String value) {
|
||||
getSaveableFile().put(key, value);
|
||||
getNotifier().configurationChanged(key, value);
|
||||
}
|
||||
|
||||
protected ConfigurationChangedListener getNotifier() {
|
||||
return ApplicationManager.getApplication().getMessageBus()
|
||||
.syncPublisher(ConfigurationChangedListener.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<IConfiguration> getConfigurations() {
|
||||
return Arrays.asList(
|
||||
new SystemProperties(),
|
||||
getSaveableFile());
|
||||
}
|
||||
|
||||
public void save() throws IOException {
|
||||
getSaveableFile().save();
|
||||
}
|
||||
|
||||
protected SaveableFileConfiguration getSaveableFile() {
|
||||
return FILE;
|
||||
}
|
||||
|
||||
public enum Mode {
|
||||
NORMAL {
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfigured() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
, DEBUG {
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfigured() {
|
||||
return true;
|
||||
}
|
||||
}, DISABLED {
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfigured() {
|
||||
return true;
|
||||
}
|
||||
}, UNKNOWN {
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfigured() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
public abstract boolean isEnabled();
|
||||
|
||||
public abstract boolean isConfigured();
|
||||
|
||||
public static Mode safeValueOf(String value) {
|
||||
try {
|
||||
if (value == null) {
|
||||
return UNKNOWN;
|
||||
}
|
||||
return Mode.valueOf(value.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
public static Mode valueOf(boolean enabled) {
|
||||
if (enabled) {
|
||||
return Mode.NORMAL;
|
||||
} else {
|
||||
return Mode.DISABLED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ConfigurationChangedListener {
|
||||
Topic<ConfigurationChangedListener> CONFIGURATION_CHANGED =
|
||||
Topic.create("Telemetry Configuration Changed", ConfigurationChangedListener.class);
|
||||
|
||||
void configurationChanged(String property, String value);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.service;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class Application {
|
||||
|
||||
private final String name;
|
||||
private final String version;
|
||||
private final Map<String, String> properties = new HashMap<>();
|
||||
|
||||
Application(String name, String version) {
|
||||
this.name = name;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public Application property(String key, String value) {
|
||||
this.properties.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Collection<AbstractMap.SimpleEntry<String, Object>> getProperties() {
|
||||
return properties.entrySet().stream()
|
||||
.map(entry -> new AbstractMap.SimpleEntry<String, Object>(entry.getKey(), entry.getValue()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof Application)) return false;
|
||||
Application that = (Application) o;
|
||||
return Objects.equals(name, that.name)
|
||||
&& Objects.equals(version, that.version)
|
||||
&& Objects.equals(properties, that.properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, version, properties);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.service;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import ee.carlrobert.codegpt.telemetry.core.util.Lazy;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* A class that provides the country for a given Timezone.
|
||||
* The mapping that this is based on relies on data provided the "countries-and-timezones" project
|
||||
* at https://github.com/manuelmhtr/countries-and-timezones
|
||||
*/
|
||||
public class Country {
|
||||
|
||||
private static final Logger LOGGER = Logger.getInstance(Country.class);
|
||||
|
||||
private static final String TIMEZONES = "/timezones.json";
|
||||
private static final String KEY_COUNTRY = "c";
|
||||
private static final String KEY_ALTERNATIVE = "a";
|
||||
|
||||
private static final Country INSTANCE = new Country();
|
||||
|
||||
public static Country getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private final Lazy<Map<String, Map<String, String>>> timezones = new Lazy<>(() -> deserialize(TIMEZONES));
|
||||
|
||||
protected Country() {
|
||||
// for testing purposes
|
||||
}
|
||||
|
||||
public String get(TimeZone timeZone) {
|
||||
if (timeZone == null) {
|
||||
return null;
|
||||
}
|
||||
return get(timeZone.getID());
|
||||
}
|
||||
|
||||
public String get(String timezoneId) {
|
||||
Map<String, String> timezone = timezones.get().get(timezoneId);
|
||||
if (timezone == null) {
|
||||
return null;
|
||||
}
|
||||
String abbreviation = timezone.get(KEY_COUNTRY);
|
||||
if (abbreviation != null) {
|
||||
return abbreviation;
|
||||
}
|
||||
String alternative = timezone.get(KEY_ALTERNATIVE);
|
||||
if (alternative == null) {
|
||||
return null;
|
||||
}
|
||||
return get(alternative);
|
||||
}
|
||||
|
||||
private <V> Map<String, V> deserialize(String file) {
|
||||
try {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
InputStream input = getClass().getResourceAsStream(file);
|
||||
TypeReference<Map<String, V>> typeRef = new TypeReference<Map<String, V>>() {};
|
||||
return mapper.readValue(input, typeRef);
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Could not load file " + file, e);
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.service;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.TimeZone;
|
||||
|
||||
public class Environment {
|
||||
|
||||
public static final String UNKNOWN_COUNTRY = "ZZ";
|
||||
|
||||
private final Plugin plugin;
|
||||
private final IDE ide;
|
||||
private final Platform platform;
|
||||
private final String timezone;
|
||||
private final String locale;
|
||||
private final String country;
|
||||
|
||||
private Environment(Plugin plugin, IDE ide, Platform platform, String timezone, String locale, String country) {
|
||||
this.plugin = plugin;
|
||||
this.ide = ide;
|
||||
this.platform = platform;
|
||||
this.timezone = timezone;
|
||||
this.locale = locale;
|
||||
this.country = country;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private IDE ide;
|
||||
private Plugin plugin;
|
||||
private Platform platform;
|
||||
private String timezone;
|
||||
private String locale;
|
||||
private String country;
|
||||
|
||||
public Builder ide(IDE ide) {
|
||||
this.ide = ide;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void ensureIDE() {
|
||||
if (ide == null) {
|
||||
ide(new IDE.Factory().create());
|
||||
}
|
||||
}
|
||||
|
||||
public Buildable plugin(ClassLoader classLoader) {
|
||||
return plugin(new Plugin.Factory().create(classLoader));
|
||||
}
|
||||
|
||||
public Buildable plugin(Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
return new Buildable();
|
||||
}
|
||||
|
||||
public Builder platform(Platform platform) {
|
||||
this.platform = platform;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void ensurePlatform() {
|
||||
if (platform == null) {
|
||||
platform(new Platform());
|
||||
}
|
||||
}
|
||||
|
||||
public Builder timezone(String timezone) {
|
||||
this.timezone = timezone;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void ensureTimezone() {
|
||||
if (timezone == null) {
|
||||
timezone(TimeZone.getDefault().getID());
|
||||
}
|
||||
}
|
||||
|
||||
public Builder locale(String locale) {
|
||||
this.locale = locale;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void ensureLocale() {
|
||||
if (locale == null) {
|
||||
locale(Locale.getDefault().toString().replace('_', '-'));
|
||||
}
|
||||
}
|
||||
|
||||
public Builder country(String country) {
|
||||
this.country = country;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void ensureCountry() {
|
||||
if (this.country == null) {
|
||||
/*
|
||||
* We're not allowed to query 3rd party services to determine the country.
|
||||
* Segment won't report countries for incoming requests.
|
||||
* We thus currently dont have any better solution than use the country in the Locale.
|
||||
*/
|
||||
ensureTimezone();
|
||||
String country = Country.getInstance().get(timezone);
|
||||
if (country == null) {
|
||||
country = UNKNOWN_COUNTRY;
|
||||
}
|
||||
country(country);
|
||||
}
|
||||
}
|
||||
|
||||
class Buildable {
|
||||
public Environment build() {
|
||||
ensureIDE();
|
||||
ensurePlatform();
|
||||
ensureCountry();
|
||||
ensureLocale();
|
||||
ensureTimezone();
|
||||
return new Environment(plugin, ide, platform, timezone, locale, country);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the plugin from which Telemetry events are sent.
|
||||
*/
|
||||
public Application getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the application from which Telemetry events are sent .
|
||||
*/
|
||||
public IDE getIde() {
|
||||
return ide;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the platform (or OS) from from which Telemetry events are sent.
|
||||
*/
|
||||
public Platform getPlatform() {
|
||||
return platform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user timezone, eg. 'Europe/Paris'
|
||||
*/
|
||||
public String getTimezone() {
|
||||
return timezone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user locale, eg. 'en-US'
|
||||
*/
|
||||
public String getLocale() {
|
||||
return locale;
|
||||
}
|
||||
|
||||
public String getCountry() {
|
||||
return country;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof Environment)) return false;
|
||||
Environment that = (Environment) o;
|
||||
return Objects.equals(plugin, that.plugin)
|
||||
&& Objects.equals(ide, that.ide)
|
||||
&& Objects.equals(platform, that.platform)
|
||||
&& Objects.equals(timezone, that.timezone)
|
||||
&& Objects.equals(locale, that.locale)
|
||||
&& Objects.equals(country, that.country);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(plugin, ide, platform, timezone, locale, country);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2022 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.service;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationInfo;
|
||||
import com.intellij.openapi.application.ApplicationNamesInfo;
|
||||
|
||||
public class IDE extends Application {
|
||||
|
||||
public static final String PROP_JAVA_VERSION = "java_version";
|
||||
|
||||
public static final class Factory {
|
||||
public IDE create() {
|
||||
return new IDE(
|
||||
ApplicationNamesInfo.getInstance().getFullProductNameWithEdition(),
|
||||
ApplicationInfo.getInstance().getFullVersion());
|
||||
}
|
||||
}
|
||||
|
||||
IDE(String applicationName, String applicationVersion) {
|
||||
super(applicationName, applicationVersion);
|
||||
}
|
||||
|
||||
public IDE setJavaVersion() {
|
||||
return setJavaVersion(System.getProperty("java.version"));
|
||||
}
|
||||
|
||||
public IDE setJavaVersion(String version) {
|
||||
property(PROP_JAVA_VERSION, version);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IDE property(String key, String value) {
|
||||
super.property(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.service;
|
||||
|
||||
import com.intellij.openapi.util.SystemInfo;
|
||||
import java.util.Objects;
|
||||
|
||||
public class Platform {
|
||||
|
||||
Platform() {
|
||||
/*
|
||||
* distribution not determined yet.
|
||||
* A possible impl exists in jbosstools-base/usage:
|
||||
* https://github.com/jbosstools/jbosstools-base/blob/master/usage/plugins/org.jboss.tools.usage/src/org/jboss/tools/usage/internal/environment/eclipse/LinuxSystem.java
|
||||
*/
|
||||
this(SystemInfo.OS_NAME, null, SystemInfo.OS_VERSION);
|
||||
}
|
||||
|
||||
Platform(String name, String distribution, String version) {
|
||||
this.name = name;
|
||||
this.distribution = distribution;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
private final String name;
|
||||
private final String distribution;
|
||||
private final String version;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getDistribution() {
|
||||
return distribution;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof Platform)) return false;
|
||||
Platform platform = (Platform) o;
|
||||
return Objects.equals(name, platform.name)
|
||||
&& Objects.equals(distribution, platform.distribution)
|
||||
&& Objects.equals(version, platform.version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, distribution, version);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2022 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.service;
|
||||
|
||||
import com.intellij.ide.plugins.IdeaPluginDescriptor;
|
||||
import com.intellij.ide.plugins.PluginManagerCore;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Plugin extends Application {
|
||||
|
||||
public static final class Factory {
|
||||
public Plugin create(ClassLoader classLoader) {
|
||||
IdeaPluginDescriptor descriptor = getPluginDescriptor(classLoader);
|
||||
if (descriptor == null) {
|
||||
return null;
|
||||
}
|
||||
return create(descriptor.getName(), descriptor.getVersion());
|
||||
}
|
||||
|
||||
public Plugin create(String name, String version) {
|
||||
return new Plugin(name, version);
|
||||
}
|
||||
|
||||
private IdeaPluginDescriptor getPluginDescriptor(ClassLoader classLoader) {
|
||||
return Arrays.stream(PluginManagerCore.getPlugins())
|
||||
.filter(descriptor -> classLoader.equals(descriptor.getPluginClassLoader()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
Plugin(String name, String version) {
|
||||
super(name, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin property(String key, String value) {
|
||||
super.property(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class TelemetryEvent {
|
||||
|
||||
public enum Type {
|
||||
USER, ACTION, STARTUP, SHUTDOWN
|
||||
}
|
||||
|
||||
private final Type type;
|
||||
private final String name;
|
||||
private final Map<String, String> properties;
|
||||
|
||||
public TelemetryEvent(Type type, String name) {
|
||||
this(type, name, new HashMap<>());
|
||||
}
|
||||
|
||||
public TelemetryEvent(Type type, String name, Map<String, String> properties) {
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Map<String, String> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,283 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.service;
|
||||
|
||||
import static ee.carlrobert.codegpt.telemetry.core.service.TelemetryEvent.Type.ACTION;
|
||||
import static ee.carlrobert.codegpt.telemetry.core.service.TelemetryEvent.Type.SHUTDOWN;
|
||||
import static ee.carlrobert.codegpt.telemetry.core.service.TelemetryEvent.Type.STARTUP;
|
||||
import static ee.carlrobert.codegpt.telemetry.core.util.TimeUtils.toLocalTime;
|
||||
|
||||
import com.intellij.ide.AppLifecycleListener;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.util.messages.MessageBusConnection;
|
||||
import ee.carlrobert.codegpt.telemetry.core.ITelemetryService;
|
||||
import ee.carlrobert.codegpt.telemetry.core.service.TelemetryEvent.Type;
|
||||
import ee.carlrobert.codegpt.telemetry.core.util.AnonymizeUtils;
|
||||
import ee.carlrobert.codegpt.telemetry.core.util.TimeUtils;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class TelemetryMessageBuilder {
|
||||
|
||||
private static final Logger LOGGER = Logger.getInstance(TelemetryMessageBuilder.class);
|
||||
|
||||
private final ServiceFacade service;
|
||||
|
||||
public TelemetryMessageBuilder(ClassLoader classLoader) {
|
||||
this(new ServiceFacade(classLoader));
|
||||
}
|
||||
|
||||
TelemetryMessageBuilder(ServiceFacade serviceFacade) {
|
||||
this.service = serviceFacade;
|
||||
}
|
||||
|
||||
public ActionMessage action(String name) {
|
||||
return new ActionMessage(name, service);
|
||||
}
|
||||
|
||||
static class StartupMessage extends Message<StartupMessage> {
|
||||
|
||||
private StartupMessage(ServiceFacade service) {
|
||||
super(STARTUP, "startup", service);
|
||||
}
|
||||
}
|
||||
|
||||
static class ShutdownMessage extends Message<ShutdownMessage> {
|
||||
|
||||
private static final String PROP_SESSION_DURATION = "session_duration";
|
||||
|
||||
private ShutdownMessage(ServiceFacade service) {
|
||||
this(toLocalTime(ApplicationManager.getApplication().getStartTime()), service);
|
||||
}
|
||||
|
||||
private ShutdownMessage(LocalDateTime startup, ServiceFacade service) {
|
||||
this(startup, LocalDateTime.now(), service);
|
||||
}
|
||||
|
||||
ShutdownMessage(LocalDateTime startup, LocalDateTime shutdown, ServiceFacade service) {
|
||||
super(SHUTDOWN, "shutdown", service);
|
||||
sessionDuration(startup, shutdown);
|
||||
}
|
||||
|
||||
private ShutdownMessage sessionDuration(LocalDateTime startup, LocalDateTime shutdown) {
|
||||
return sessionDuration(Duration.between(startup, shutdown));
|
||||
}
|
||||
|
||||
private ShutdownMessage sessionDuration(Duration duration) {
|
||||
return property(PROP_SESSION_DURATION, TimeUtils.toString(duration));
|
||||
}
|
||||
|
||||
String getSessionDuration() {
|
||||
return getProperty(PROP_SESSION_DURATION);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ActionMessage extends Message<ActionMessage> {
|
||||
|
||||
static final String PROP_DURATION = "duration";
|
||||
static final String PROP_ERROR = "error";
|
||||
static final String PROP_RESULT = "result";
|
||||
|
||||
public static final String RESULT_SUCCESS = "success";
|
||||
|
||||
private LocalDateTime started;
|
||||
|
||||
private ActionMessage(String name, ServiceFacade service) {
|
||||
super(ACTION, name, service);
|
||||
started();
|
||||
}
|
||||
|
||||
public ActionMessage started() {
|
||||
return started(LocalDateTime.now());
|
||||
}
|
||||
|
||||
public ActionMessage started(LocalDateTime started) {
|
||||
this.started = started;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ActionMessage finished() {
|
||||
finished(LocalDateTime.now());
|
||||
return this;
|
||||
}
|
||||
|
||||
public ActionMessage finished(LocalDateTime finished) {
|
||||
duration(Duration.between(started, finished));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ActionMessage duration(Duration duration) {
|
||||
return property(PROP_DURATION, TimeUtils.toString(duration));
|
||||
}
|
||||
|
||||
String getDuration() {
|
||||
return getProperty(PROP_DURATION);
|
||||
}
|
||||
|
||||
public ActionMessage success() {
|
||||
return result(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
public ActionMessage result(String result) {
|
||||
property(PROP_RESULT, result);
|
||||
return clearError();
|
||||
}
|
||||
|
||||
protected ActionMessage clearResult() {
|
||||
properties().remove(PROP_RESULT);
|
||||
return this;
|
||||
}
|
||||
|
||||
String getResult() {
|
||||
return getProperty(PROP_RESULT);
|
||||
}
|
||||
|
||||
public ActionMessage error(Exception exception) {
|
||||
if (exception == null) {
|
||||
return this;
|
||||
}
|
||||
return error(exception.getMessage());
|
||||
}
|
||||
|
||||
public ActionMessage error(String message) {
|
||||
property(PROP_ERROR, AnonymizeUtils.anonymize(message));
|
||||
return clearResult();
|
||||
}
|
||||
|
||||
protected ActionMessage clearError() {
|
||||
properties().remove(PROP_ERROR);
|
||||
return this;
|
||||
}
|
||||
|
||||
String getError() {
|
||||
return getProperty(PROP_ERROR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TelemetryEvent send() {
|
||||
ensureFinished();
|
||||
ensureResultOrError();
|
||||
return super.send();
|
||||
}
|
||||
|
||||
private void ensureFinished() {
|
||||
if (!hasProperty(PROP_DURATION)) {
|
||||
finished();
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureResultOrError() {
|
||||
if (!hasProperty(PROP_ERROR)
|
||||
&& !hasProperty(PROP_RESULT)) {
|
||||
success();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private abstract static class Message<T extends Message<?>> {
|
||||
|
||||
private final Type type;
|
||||
private final Map<String, String> properties = new HashMap<>();
|
||||
private final String name;
|
||||
private final ServiceFacade service;
|
||||
|
||||
private Message(Type type, String name, ServiceFacade service) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public T property(String key, String value) {
|
||||
if (key == null
|
||||
|| value == null) {
|
||||
LOGGER.warn("Ignored property with key: " + key + " value: " + value);
|
||||
} else {
|
||||
properties.put(key, value);
|
||||
}
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
String getProperty(String key) {
|
||||
return properties.get(key);
|
||||
}
|
||||
|
||||
Map<String, String> properties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
protected boolean hasProperty(String key) {
|
||||
return properties.containsKey(key);
|
||||
}
|
||||
|
||||
public TelemetryEvent send() {
|
||||
TelemetryEvent event = new TelemetryEvent(type, name, new HashMap<>(properties));
|
||||
service.send(event);
|
||||
return event;
|
||||
}
|
||||
}
|
||||
|
||||
static class ServiceFacade {
|
||||
private final ClassLoader classLoader;
|
||||
private ITelemetryService service = null;
|
||||
|
||||
protected ServiceFacade(final ClassLoader classLoader) {
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
public void send(final TelemetryEvent event) {
|
||||
if (service == null) {
|
||||
this.service = createService(classLoader);
|
||||
sendStartup();
|
||||
onShutdown();
|
||||
}
|
||||
service.send(event);
|
||||
}
|
||||
|
||||
protected ITelemetryService createService(ClassLoader classLoader) {
|
||||
TelemetryServiceFactory factory = ApplicationManager.getApplication().getService(TelemetryServiceFactory.class);
|
||||
return factory.create(classLoader);
|
||||
}
|
||||
|
||||
private void sendStartup() {
|
||||
new StartupMessage(this).send();
|
||||
}
|
||||
|
||||
private void onShutdown() {
|
||||
MessageBusConnection connection = createMessageBusConnection();
|
||||
connection.subscribe(AppLifecycleListener.TOPIC, new AppLifecycleListener() {
|
||||
@Override
|
||||
public void appWillBeClosed(boolean isRestart) {
|
||||
sendShutdown();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void sendShutdown() {
|
||||
new ShutdownMessage(ServiceFacade.this).send();
|
||||
}
|
||||
|
||||
protected MessageBusConnection createMessageBusConnection() {
|
||||
return ApplicationManager.getApplication().getMessageBus().connect();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.service;
|
||||
|
||||
import static ee.carlrobert.codegpt.telemetry.core.configuration.TelemetryConfiguration.KEY_MODE;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.util.messages.MessageBusConnection;
|
||||
import ee.carlrobert.codegpt.telemetry.core.IMessageBroker;
|
||||
import ee.carlrobert.codegpt.telemetry.core.ITelemetryService;
|
||||
import ee.carlrobert.codegpt.telemetry.core.configuration.TelemetryConfiguration;
|
||||
import ee.carlrobert.codegpt.telemetry.core.configuration.TelemetryConfiguration.ConfigurationChangedListener;
|
||||
import ee.carlrobert.codegpt.telemetry.core.configuration.TelemetryConfiguration.Mode;
|
||||
import ee.carlrobert.codegpt.telemetry.core.service.TelemetryEvent.Type;
|
||||
import ee.carlrobert.codegpt.telemetry.core.util.CircularBuffer;
|
||||
import ee.carlrobert.codegpt.telemetry.ui.TelemetryNotifications;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class TelemetryService implements ITelemetryService {
|
||||
|
||||
private static final Logger LOGGER = Logger.getInstance(TelemetryService.class);
|
||||
|
||||
private static final int BUFFER_SIZE = 35;
|
||||
|
||||
private final TelemetryNotifications notifications;
|
||||
private final TelemetryConfiguration configuration;
|
||||
protected final IMessageBroker broker;
|
||||
private final AtomicBoolean userQueried = new AtomicBoolean(false);
|
||||
private final CircularBuffer<TelemetryEvent> onHold = new CircularBuffer<>(BUFFER_SIZE);
|
||||
|
||||
public TelemetryService(final TelemetryConfiguration configuration, final IMessageBroker broker) {
|
||||
this(configuration, broker, ApplicationManager.getApplication().getMessageBus().connect(), new TelemetryNotifications());
|
||||
}
|
||||
|
||||
TelemetryService(final TelemetryConfiguration configuration,
|
||||
final IMessageBroker broker,
|
||||
final MessageBusConnection connection,
|
||||
final TelemetryNotifications notifications) {
|
||||
this.configuration = configuration;
|
||||
this.broker = broker;
|
||||
this.notifications = notifications;
|
||||
onConfigurationChanged(connection);
|
||||
}
|
||||
|
||||
private void onConfigurationChanged(MessageBusConnection connection) {
|
||||
connection.subscribe(ConfigurationChangedListener.CONFIGURATION_CHANGED, (String key, String value) -> {
|
||||
if (KEY_MODE.equals(key)
|
||||
&& Mode.safeValueOf(value).isEnabled()) {
|
||||
flushOnHold();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(TelemetryEvent event) {
|
||||
sendUserInfo();
|
||||
doSend(event);
|
||||
queryUserConsent();
|
||||
}
|
||||
|
||||
private void sendUserInfo() {
|
||||
doSend(new TelemetryEvent(
|
||||
Type.USER,
|
||||
"Anonymous ID: " + UserId.INSTANCE.get()));
|
||||
}
|
||||
|
||||
private void queryUserConsent() {
|
||||
if (!isConfigured()
|
||||
&& userQueried.compareAndSet(false, true)) {
|
||||
notifications.queryUserConsent();
|
||||
}
|
||||
}
|
||||
|
||||
private void doSend(TelemetryEvent event) {
|
||||
if (isEnabled()) {
|
||||
flushOnHold();
|
||||
broker.send(event);
|
||||
} else if (!isConfigured()) {
|
||||
onHold.offer(event);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isEnabled() {
|
||||
return configuration != null
|
||||
&& configuration.isEnabled();
|
||||
}
|
||||
|
||||
private boolean isConfigured() {
|
||||
return configuration != null
|
||||
&& configuration.isConfigured();
|
||||
}
|
||||
|
||||
private void flushOnHold() {
|
||||
onHold.pollAll().forEach(this::send);
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
flushOnHold();
|
||||
onHold.clear();
|
||||
broker.dispose();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.service;
|
||||
|
||||
import com.intellij.openapi.project.DumbAware;
|
||||
import ee.carlrobert.codegpt.telemetry.core.IMessageBroker;
|
||||
import ee.carlrobert.codegpt.telemetry.core.configuration.TelemetryConfiguration;
|
||||
import ee.carlrobert.codegpt.telemetry.core.service.segment.SegmentBroker;
|
||||
import ee.carlrobert.codegpt.telemetry.core.service.segment.SegmentConfiguration;
|
||||
|
||||
public class TelemetryServiceFactory implements DumbAware {
|
||||
|
||||
private final IDE ide = new IDE.Factory()
|
||||
.create()
|
||||
.setJavaVersion();
|
||||
|
||||
public TelemetryService create(ClassLoader classLoader) {
|
||||
Environment environment = new Environment.Builder()
|
||||
.ide(ide)
|
||||
.plugin(classLoader)
|
||||
.build();
|
||||
TelemetryConfiguration configuration = TelemetryConfiguration.getInstance();
|
||||
IMessageBroker broker = createSegmentBroker(configuration.isDebug(), classLoader, environment);
|
||||
return new TelemetryService(configuration, broker);
|
||||
}
|
||||
|
||||
private IMessageBroker createSegmentBroker(boolean isDebug, ClassLoader classLoader, Environment environment) {
|
||||
SegmentConfiguration brokerConfiguration = new SegmentConfiguration(classLoader);
|
||||
return new SegmentBroker(
|
||||
isDebug,
|
||||
UserId.INSTANCE.get(),
|
||||
environment,
|
||||
brokerConfiguration);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.service;
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import ee.carlrobert.codegpt.telemetry.core.util.Directories;
|
||||
import ee.carlrobert.codegpt.telemetry.core.util.FileUtils;
|
||||
import ee.carlrobert.codegpt.telemetry.core.util.Lazy;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class UserId {
|
||||
|
||||
private static final Logger LOGGER = Logger.getInstance(UserId.class);
|
||||
|
||||
public static final UserId INSTANCE = new UserId();
|
||||
private static final Path UUID_FILE = Directories.PATH.resolve("anonymousId");
|
||||
private static final Pattern UUID_REGEX =
|
||||
Pattern.compile("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$");
|
||||
private final Lazy<String> uuid = new Lazy<>(() -> loadOrCreate(UUID_FILE));
|
||||
|
||||
/** for testing purposes */
|
||||
protected UserId() {}
|
||||
|
||||
public String get() {
|
||||
return uuid.get();
|
||||
}
|
||||
|
||||
private String loadOrCreate(Path file) {
|
||||
String uuid = null;
|
||||
if (exists(file)) {
|
||||
uuid = load(file);
|
||||
if (isValid(uuid)) {
|
||||
return uuid;
|
||||
}
|
||||
}
|
||||
uuid = create();
|
||||
write(uuid, file);
|
||||
return uuid;
|
||||
}
|
||||
|
||||
/** for testing purposes */
|
||||
protected boolean exists(Path file) {
|
||||
return Files.exists(file);
|
||||
}
|
||||
|
||||
/** for testing purposes */
|
||||
protected String load(Path uuidFile) {
|
||||
String uuid = null;
|
||||
try(Stream<String> lines = Files.lines(uuidFile)) {
|
||||
uuid = lines
|
||||
.findAny()
|
||||
.map(String::trim)
|
||||
.orElse(null);
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Could not read anonymous UUID file at " + uuidFile.toAbsolutePath(), e);
|
||||
}
|
||||
return uuid;
|
||||
}
|
||||
|
||||
private boolean isValid(String uuid) {
|
||||
if (uuid == null) {
|
||||
return false;
|
||||
}
|
||||
return UUID_REGEX.matcher(uuid).matches();
|
||||
}
|
||||
|
||||
private String create() {
|
||||
return UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
/** for testing purposes */
|
||||
protected void write(String uuid, Path uuidFile) {
|
||||
try {
|
||||
FileUtils.createFileAndParent(uuidFile);
|
||||
FileUtils.write(uuid, uuidFile);
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Could not write anonymous UUID to file at " + UUID_FILE.toAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.service.segment;
|
||||
|
||||
public interface ISegmentConfiguration {
|
||||
String getNormalWriteKey();
|
||||
String getDebugWriteKey();
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.service.segment;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Traits that the segment broker sends with am identify event.
|
||||
*/
|
||||
public class IdentifyTraits {
|
||||
private final String locale;
|
||||
private final String timezone;
|
||||
private final String osName;
|
||||
private final String osVersion;
|
||||
private final String osDistribution;
|
||||
|
||||
public IdentifyTraits(String locale, String timezone, String osName, String osVersion, String osDistribution) {
|
||||
this.locale = locale;
|
||||
this.timezone = timezone;
|
||||
this.osName = osName;
|
||||
this.osVersion = osVersion;
|
||||
this.osDistribution = osDistribution;
|
||||
}
|
||||
|
||||
public String getLocale() {
|
||||
return locale;
|
||||
}
|
||||
|
||||
public String getTimezone() {
|
||||
return timezone;
|
||||
}
|
||||
|
||||
public String getOsName() {
|
||||
return osName;
|
||||
}
|
||||
|
||||
public String getOsVersion() {
|
||||
return osVersion;
|
||||
}
|
||||
|
||||
public String getOsDistribution() {
|
||||
return osDistribution;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof IdentifyTraits)) return false;
|
||||
IdentifyTraits identifyTraits = (IdentifyTraits) o;
|
||||
return Objects.equals(locale, identifyTraits.locale)
|
||||
&& Objects.equals(timezone, identifyTraits.timezone)
|
||||
&& Objects.equals(osName, identifyTraits.osName)
|
||||
&& Objects.equals(osVersion, identifyTraits.osVersion)
|
||||
&& Objects.equals(osDistribution, identifyTraits.osDistribution);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(locale, timezone, osName, osVersion, osDistribution);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2022 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.service.segment;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import ee.carlrobert.codegpt.telemetry.core.util.Directories;
|
||||
import ee.carlrobert.codegpt.telemetry.core.util.FileUtils;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Persistency for {@link IdentifyTraits}.
|
||||
*/
|
||||
public class IdentifyTraitsPersistence {
|
||||
|
||||
public static final IdentifyTraitsPersistence INSTANCE = new IdentifyTraitsPersistence();
|
||||
private static final Logger LOGGER = Logger.getInstance(IdentifyTraitsPersistence.class);
|
||||
|
||||
private static final Path FILE = Directories.PATH.resolve("segment-identify-traits.json");
|
||||
|
||||
private IdentifyTraits identifyTraits = null;
|
||||
|
||||
protected IdentifyTraitsPersistence() {}
|
||||
|
||||
public synchronized IdentifyTraits get() {
|
||||
if (identifyTraits == null) {
|
||||
this.identifyTraits = deserialize(load(FILE));
|
||||
}
|
||||
return identifyTraits;
|
||||
}
|
||||
|
||||
public synchronized void set(IdentifyTraits identifyTraits) {
|
||||
if (Objects.equals(identifyTraits, this.identifyTraits)) {
|
||||
return;
|
||||
}
|
||||
this.identifyTraits = identifyTraits;
|
||||
String string = null;
|
||||
if (identifyTraits != null) {
|
||||
string = serialize(identifyTraits);
|
||||
}
|
||||
save(string, FILE);
|
||||
}
|
||||
|
||||
private String serialize(IdentifyTraits identifyTraits) {
|
||||
if (identifyTraits == null) {
|
||||
return null;
|
||||
}
|
||||
return new Gson().toJson(identifyTraits);
|
||||
}
|
||||
|
||||
private IdentifyTraits deserialize(String identity) {
|
||||
if (identity == null) {
|
||||
return null;
|
||||
}
|
||||
return new Gson().fromJson(identity, IdentifyTraits.class);
|
||||
}
|
||||
|
||||
private String load(Path file) {
|
||||
String event = null;
|
||||
try(Stream<String> lines = getLines(file)) {
|
||||
event = lines
|
||||
.findAny()
|
||||
.map(String::trim)
|
||||
.orElse(null);
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Could not read identity file at " + file.toAbsolutePath(), e);
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
/* for testing purposes */
|
||||
protected Stream<String> getLines(Path file) throws IOException {
|
||||
return Files.lines(file);
|
||||
}
|
||||
|
||||
private void save(String event, Path file) {
|
||||
try {
|
||||
createFileAndParent(file);
|
||||
writeFile(event, file);
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Could not write identity to file at " + FILE.toAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/* for testing purposes */
|
||||
protected void createFileAndParent(Path file) throws IOException {
|
||||
FileUtils.createFileAndParent(file);
|
||||
}
|
||||
|
||||
/* for testing purposes */
|
||||
protected void writeFile(String event, Path file) throws IOException {
|
||||
FileUtils.write(event, file);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,252 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.service.segment;
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.segment.analytics.Analytics;
|
||||
import com.segment.analytics.messages.IdentifyMessage;
|
||||
import com.segment.analytics.messages.MessageBuilder;
|
||||
import com.segment.analytics.messages.PageMessage;
|
||||
import com.segment.analytics.messages.TrackMessage;
|
||||
import ee.carlrobert.codegpt.telemetry.core.IMessageBroker;
|
||||
import ee.carlrobert.codegpt.telemetry.core.service.Application;
|
||||
import ee.carlrobert.codegpt.telemetry.core.service.Environment;
|
||||
import ee.carlrobert.codegpt.telemetry.core.service.TelemetryEvent;
|
||||
import ee.carlrobert.codegpt.telemetry.core.util.Lazy;
|
||||
import ee.carlrobert.codegpt.telemetry.core.util.MapBuilder;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class SegmentBroker implements IMessageBroker {
|
||||
|
||||
private static final Logger LOGGER = Logger.getInstance(SegmentBroker.class);
|
||||
|
||||
public static final String PROP_NAME = "name";
|
||||
public static final String PROP_VERSION = "version";
|
||||
public static final String PROP_APP = "app";
|
||||
public static final String PROP_IP = "ip";
|
||||
public static final String PROP_COUNTRY = "country";
|
||||
public static final String PROP_LOCALE = "locale";
|
||||
public static final String PROP_LOCATION = "location";
|
||||
public static final String PROP_OS = "os";
|
||||
public static final String PROP_OS_NAME = "os_name";
|
||||
public static final String PROP_OS_DISTRIBUTION = "os_distribution";
|
||||
public static final String PROP_OS_VERSION = "os_version";
|
||||
public static final String PROP_TIMEZONE = "timezone";
|
||||
|
||||
public static final String VALUE_NULL_IP = "0.0.0.0"; // fixed, faked ip addr
|
||||
|
||||
public static final String PROP_EXTENSION_NAME = "extension_name";
|
||||
public static final String PROP_EXTENSION_VERSION = "extension_version";
|
||||
public static final String PROP_APP_NAME = "app_name";
|
||||
public static final String PROP_APP_VERSION = "app_version";
|
||||
|
||||
enum SegmentType {
|
||||
IDENTIFY {
|
||||
public MessageBuilder toMessage(TelemetryEvent event, Map<String, Object> context, SegmentBroker broker) {
|
||||
return broker.toMessage(IdentifyMessage.builder(), event, context);
|
||||
}
|
||||
},
|
||||
TRACK {
|
||||
public MessageBuilder toMessage(TelemetryEvent event, Map<String, Object> context, SegmentBroker broker) {
|
||||
return broker.toMessage(TrackMessage.builder(event.getName()), event, context);
|
||||
}
|
||||
},
|
||||
PAGE {
|
||||
public MessageBuilder toMessage(TelemetryEvent event, Map<String, Object> context, SegmentBroker broker) {
|
||||
return broker.toMessage(PageMessage.builder(event.getName()), event, context);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
public abstract MessageBuilder toMessage(TelemetryEvent event, Map<String, Object> context, SegmentBroker broker);
|
||||
|
||||
public static SegmentType valueOf(TelemetryEvent.Type eventType) {
|
||||
switch (eventType) {
|
||||
case USER:
|
||||
return IDENTIFY;
|
||||
case ACTION:
|
||||
case STARTUP:
|
||||
case SHUTDOWN:
|
||||
default:
|
||||
return TRACK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final String userId;
|
||||
private final IdentifyTraitsPersistence identifyTraitsPersistence;
|
||||
private final Environment environment;
|
||||
private Lazy<Analytics> analytics;
|
||||
|
||||
public SegmentBroker(boolean isDebug, String userId, Environment environment, ISegmentConfiguration configuration) {
|
||||
this(isDebug, userId, IdentifyTraitsPersistence.INSTANCE, environment, configuration, new AnalyticsFactory());
|
||||
}
|
||||
|
||||
public SegmentBroker(boolean isDebug, String userId, IdentifyTraitsPersistence identifyTraitsPersistence, Environment environment, ISegmentConfiguration configuration, Function<String, Analytics> analyticsFactory) {
|
||||
this.userId = userId;
|
||||
this.identifyTraitsPersistence = identifyTraitsPersistence;
|
||||
this.environment = environment;
|
||||
this.analytics = new Lazy<>(() -> analyticsFactory.apply(getWriteKey(isDebug, configuration)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(TelemetryEvent event) {
|
||||
try {
|
||||
if (analytics.get() == null) {
|
||||
LOGGER.warn("Could not send " + event.getType() + " event '" + event.getName() + "': no analytics instance present.");
|
||||
return;
|
||||
}
|
||||
Map<String, Object> context = createContext(environment);
|
||||
SegmentType segmentType = SegmentType.valueOf(event.getType());
|
||||
MessageBuilder builder = segmentType.toMessage(event, context, this);
|
||||
if (builder == null) {
|
||||
LOGGER.debug("No message to be sent.");
|
||||
} else {
|
||||
LOGGER.debug("Sending message " + builder.type() + " to segment.");
|
||||
analytics.get().enqueue(builder);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOGGER.warn("Could not send " + event.getName() + " event: unknown type '" + event.getType() + "'.");
|
||||
}
|
||||
}
|
||||
|
||||
private MessageBuilder toMessage(IdentifyMessage.Builder builder, TelemetryEvent event, Map<String, Object> context) {
|
||||
IdentifyTraits identifyTraits = new IdentifyTraits(
|
||||
environment.getLocale(),
|
||||
environment.getTimezone(),
|
||||
environment.getPlatform().getName(),
|
||||
environment.getPlatform().getVersion(),
|
||||
environment.getPlatform().getDistribution());
|
||||
if (!haveChanged(identifyTraits, identifyTraitsPersistence)) {
|
||||
LOGGER.debug("Skipping identify message: already sent." + identifyTraits);
|
||||
return null;
|
||||
}
|
||||
return builder
|
||||
.userId(userId)
|
||||
.traits(addIdentifyTraits(identifyTraits, event.getProperties()))
|
||||
.context(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the given identify traits to persistence if persistence exists.
|
||||
*
|
||||
* @param identifyTraits the traits to save
|
||||
* @return true if saving occurred or no persistence was present.
|
||||
*/
|
||||
private synchronized boolean haveChanged(IdentifyTraits identifyTraits, IdentifyTraitsPersistence persistence) {
|
||||
if (identifyTraitsPersistence != null) {
|
||||
if (identifyTraits.equals(persistence.get())) {
|
||||
return false;
|
||||
} else {
|
||||
persistence.set(identifyTraits);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private Map<String, ?> addIdentifyTraits(final IdentifyTraits identifyTraits, final Map<String, String> properties) {
|
||||
putIfNotNull(PROP_LOCALE, identifyTraits.getLocale(), properties);
|
||||
putIfNotNull(PROP_TIMEZONE, identifyTraits.getTimezone(), properties);
|
||||
putIfNotNull(PROP_OS_NAME, identifyTraits.getOsName(), properties);
|
||||
putIfNotNull(PROP_OS_DISTRIBUTION, identifyTraits.getOsDistribution(), properties);
|
||||
putIfNotNull(PROP_OS_VERSION, identifyTraits.getOsVersion(), properties);
|
||||
return properties;
|
||||
}
|
||||
|
||||
private MessageBuilder toMessage(TrackMessage.Builder builder, TelemetryEvent event, Map<String, Object> context) {
|
||||
return builder
|
||||
.userId(userId)
|
||||
.properties(addTrackProperties(event.getProperties()))
|
||||
.context(context);
|
||||
}
|
||||
|
||||
private Map<String, ?> addTrackProperties(final Map<String, String> properties) {
|
||||
Application application = environment.getIde();
|
||||
putIfNotNull(PROP_APP_NAME, application.getName(), properties);
|
||||
putIfNotNull(PROP_APP_VERSION, application.getVersion(), properties);
|
||||
application.getProperties().forEach(
|
||||
appProperty -> putIfNotNull(appProperty.getKey(), String.valueOf(appProperty.getValue()), properties));
|
||||
putIfNotNull(PROP_EXTENSION_NAME, environment.getPlugin().getName(), properties);
|
||||
putIfNotNull(PROP_EXTENSION_VERSION, environment.getPlugin().getVersion(), properties);
|
||||
return properties;
|
||||
}
|
||||
|
||||
private MessageBuilder toMessage(PageMessage.Builder builder, TelemetryEvent event, Map<String, Object> context) {
|
||||
return builder
|
||||
.userId(userId)
|
||||
.properties(event.getProperties())
|
||||
.context(context);
|
||||
}
|
||||
|
||||
private void putIfNotNull(String key, String value, Map<String, String> properties) {
|
||||
if (key == null
|
||||
|| value == null
|
||||
|| properties == null) {
|
||||
return;
|
||||
}
|
||||
properties.put(key, value);
|
||||
}
|
||||
|
||||
private Map<String, Object> createContext(Environment environment) {
|
||||
return new MapBuilder()
|
||||
.mapPair(PROP_APP)
|
||||
.pair(PROP_NAME, environment.getIde().getName())
|
||||
.pair(PROP_VERSION, environment.getIde().getVersion())
|
||||
.pairs(environment.getIde().getProperties())
|
||||
.build()
|
||||
.pair(PROP_IP, VALUE_NULL_IP)
|
||||
.pair(PROP_LOCALE, environment.getLocale())
|
||||
.mapPair(PROP_LOCATION)
|
||||
.pair(PROP_COUNTRY, environment.getCountry())
|
||||
.build()
|
||||
.mapPair(PROP_OS)
|
||||
.pair(PROP_NAME, environment.getPlatform().getName())
|
||||
.pair(PROP_VERSION, environment.getPlatform().getVersion())
|
||||
.build()
|
||||
.pair(PROP_TIMEZONE, environment.getTimezone())
|
||||
.build();
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
analytics.get().flush();
|
||||
analytics.get().shutdown();
|
||||
}
|
||||
|
||||
|
||||
private String getWriteKey(boolean isDebug, ISegmentConfiguration configuration) {
|
||||
if (isDebug) {
|
||||
return configuration.getDebugWriteKey();
|
||||
} else {
|
||||
return configuration.getNormalWriteKey();
|
||||
}
|
||||
}
|
||||
|
||||
private static class AnalyticsFactory implements Function<String, Analytics> {
|
||||
|
||||
private static final int FLUSH_INTERVAL = 10000;
|
||||
private static final int FLUSH_QUEUE_SIZE = 10;
|
||||
|
||||
@Override
|
||||
public Analytics apply(String writeKey) {
|
||||
if (writeKey == null) {
|
||||
LOGGER.warn("Could not create Segment Analytics instance, missing writeKey.");
|
||||
return null;
|
||||
}
|
||||
LOGGER.debug("Creating Segment Analytics instance using " + writeKey + " writeKey.");
|
||||
return Analytics.builder(writeKey)
|
||||
.flushQueueSize(FLUSH_QUEUE_SIZE)
|
||||
.flushInterval(FLUSH_INTERVAL, TimeUnit.MILLISECONDS)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.service.segment;
|
||||
|
||||
import ee.carlrobert.codegpt.telemetry.core.configuration.ClasspathConfiguration;
|
||||
import ee.carlrobert.codegpt.telemetry.core.configuration.CompositeConfiguration;
|
||||
import ee.carlrobert.codegpt.telemetry.core.configuration.IConfiguration;
|
||||
import ee.carlrobert.codegpt.telemetry.core.configuration.SystemProperties;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class SegmentConfiguration extends CompositeConfiguration implements ISegmentConfiguration {
|
||||
|
||||
public static final String KEY_SEGMENT_WRITE = "writeKey";
|
||||
public static final String KEY_SEGMENT_DEBUG_WRITE = "debugWriteKey";
|
||||
|
||||
private static final String SEGMENT_PROPERTIES = "segment.properties";
|
||||
private static final String SEGMENT_DEFAULTS_PROPERTIES = "segment-defaults.properties";
|
||||
|
||||
private final ClasspathConfiguration consumerClasspathConfiguration;
|
||||
|
||||
public SegmentConfiguration(ClassLoader classLoader) {
|
||||
this(new ClasspathConfiguration(Paths.get(SEGMENT_PROPERTIES), classLoader));
|
||||
}
|
||||
|
||||
protected SegmentConfiguration(ClasspathConfiguration consumerClasspathConfiguration) {
|
||||
this.consumerClasspathConfiguration = consumerClasspathConfiguration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(String key, String value) {
|
||||
consumerClasspathConfiguration.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IConfiguration> getConfigurations() {
|
||||
return Arrays.asList(
|
||||
new SystemProperties(),
|
||||
// segment.properties in consuming plugin
|
||||
consumerClasspathConfiguration,
|
||||
// segment-defaults.properties in this plugin
|
||||
new ClasspathConfiguration(Paths.get(SEGMENT_DEFAULTS_PROPERTIES), getClass().getClassLoader()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNormalWriteKey() {
|
||||
return get(KEY_SEGMENT_WRITE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDebugWriteKey() {
|
||||
return get(KEY_SEGMENT_DEBUG_WRITE);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.util;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class AnonymizeUtils {
|
||||
|
||||
public static final String USER_NAME = System.getProperty("user.name");
|
||||
public static final String ANONYMOUS_USER_NAME = "<USER>";
|
||||
public static final String HOME_DIR = System.getProperty("user.home");
|
||||
public static final String ANONYMOUS_HOMEDIR = "<HOMEDIR>";
|
||||
public static final String TMP_DIR = System.getProperty("java.io.tmpdir");
|
||||
public static final String ANONYMOUS_TMPDIR = "<TMPDIR>";
|
||||
private static final Pattern EMAIL_PATTERN = Pattern.compile(
|
||||
"[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
public static final String ANONYMOUS_EMAIL = "<EMAIL>";
|
||||
private static final Pattern IP_PATTERN = Pattern.compile(
|
||||
"(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])");
|
||||
public static final String ANONYMOUS_IP = "<IP>";
|
||||
public static final String ANONYMOUS_RESOURCENAME = "<RESOURCENAME>";
|
||||
public static final String ANONYMOUS_NAMESPACE = "<NAMESPACE>";
|
||||
|
||||
private AnonymizeUtils() {
|
||||
}
|
||||
|
||||
public static String anonymize(String string) {
|
||||
return anonymizeEmail(
|
||||
anonymizeUserName(
|
||||
anonymizeIP(
|
||||
anonymizeHomeDir(
|
||||
anonymizeTmpDir(string)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static String anonymizeResource(String name, String namespace, String string) {
|
||||
if (string == null
|
||||
|| string.isEmpty()) {
|
||||
return string;
|
||||
}
|
||||
if (name != null) {
|
||||
string = string.replace(name, ANONYMOUS_RESOURCENAME);
|
||||
}
|
||||
if (namespace != null) {
|
||||
string = string.replace(namespace, ANONYMOUS_NAMESPACE);
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
public static String anonymizeUserName(String string) {
|
||||
if (string == null
|
||||
|| string.isEmpty()) {
|
||||
return string;
|
||||
}
|
||||
return string.replace(USER_NAME, ANONYMOUS_USER_NAME);
|
||||
}
|
||||
|
||||
public static String anonymizeEmail(String string) {
|
||||
if (string == null
|
||||
|| string.isEmpty()) {
|
||||
return string;
|
||||
}
|
||||
return EMAIL_PATTERN.matcher(string).replaceAll(ANONYMOUS_EMAIL);
|
||||
}
|
||||
|
||||
public static String anonymizeHomeDir(String string) {
|
||||
if (string == null
|
||||
|| string.isEmpty()) {
|
||||
return string;
|
||||
}
|
||||
return string.replace(HOME_DIR, ANONYMOUS_HOMEDIR);
|
||||
}
|
||||
|
||||
public static String anonymizeTmpDir(String string) {
|
||||
if (string == null
|
||||
|| string.isEmpty()) {
|
||||
return string;
|
||||
}
|
||||
return string.replace(TMP_DIR, ANONYMOUS_TMPDIR);
|
||||
}
|
||||
|
||||
public static String anonymizeIP(String string) {
|
||||
if (string == null
|
||||
|| string.isEmpty()) {
|
||||
return string;
|
||||
}
|
||||
return IP_PATTERN.matcher(string).replaceAll(ANONYMOUS_IP);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
/**
|
||||
* MIT License
|
||||
* <p>
|
||||
* Copyright (c) 2017 Eugen Paraschiv
|
||||
* <p>
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
* <p>
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
* <p>
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
* <p>
|
||||
* Based on work by Eugen Paraschiv for Baeldung.com at
|
||||
* https://github.com/eugenp/tutorials/blob/master/data-structures/src/main/java/com/baeldung/circularbuffer/CircularBuffer.java
|
||||
*/
|
||||
package ee.carlrobert.codegpt.telemetry.core.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class CircularBuffer<E> {
|
||||
|
||||
private static final int DEFAULT_CAPACITY = 8;
|
||||
|
||||
private final int capacity;
|
||||
private final E[] data;
|
||||
private volatile int writeSequence = -1;
|
||||
private volatile int readSequence = 0;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public CircularBuffer(int capacity) {
|
||||
this.capacity = (capacity < 1) ? DEFAULT_CAPACITY : capacity;
|
||||
this.data = (E[]) new Object[capacity];
|
||||
}
|
||||
|
||||
public boolean offer(E element) {
|
||||
if (!isFull()) {
|
||||
int nextWriteSeq = writeSequence + 1;
|
||||
data[nextWriteSeq % capacity] = element;
|
||||
|
||||
writeSequence++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public E poll() {
|
||||
if (!isEmpty()) {
|
||||
E nextValue = data[readSequence % capacity];
|
||||
readSequence++;
|
||||
return nextValue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<E> pollAll() {
|
||||
List<E> values = new ArrayList<>();
|
||||
for(E value = poll(); value != null; value = poll()) {
|
||||
values.add(value);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
public int capacity() {
|
||||
return capacity;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return (writeSequence - readSequence) + 1;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return writeSequence < readSequence;
|
||||
}
|
||||
|
||||
public boolean isFull() {
|
||||
return size() >= capacity;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
readSequence = 0;
|
||||
writeSequence = -1;
|
||||
Arrays.fill(data, null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2022 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.util;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class Directories {
|
||||
|
||||
public static final Path PATH = Paths.get(
|
||||
System.getProperty("user.home"),
|
||||
".codegpt");
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2022 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class FileUtils {
|
||||
|
||||
/**
|
||||
* Creates the file for the given path and the folder that contains it.
|
||||
* Does nothing if it any of those already exist.
|
||||
*
|
||||
* @param file the file to create
|
||||
*
|
||||
* @throws IOException if the file operation fails
|
||||
*/
|
||||
public static void createFileAndParent(Path file) throws IOException {
|
||||
if (!Files.exists(file.getParent())) {
|
||||
Files.createDirectories(file.getParent());
|
||||
}
|
||||
if (!Files.exists(file)) {
|
||||
Files.createFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
public static void write(String content, Path file) throws IOException {
|
||||
try (Writer writer = Files.newBufferedWriter(file)) {
|
||||
writer.append(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.util;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class Lazy<T> implements Supplier<T> {
|
||||
|
||||
private final Supplier<T> factory;
|
||||
private volatile T value;
|
||||
|
||||
public Lazy(Supplier<T> factory) {
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get() {
|
||||
if (value == null) {
|
||||
this.value = factory.get();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2022 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.util;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class MapBuilder {
|
||||
|
||||
private final Map<String, Object> map = new HashMap<>();
|
||||
|
||||
public MapBuilder pair(String key, String value) {
|
||||
map.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public MapBuilder pair(String key, Object value) {
|
||||
map.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public MapBuilder pairs(Collection<AbstractMap.SimpleEntry<String, Object>> entries) {
|
||||
if (entries == null) {
|
||||
return this;
|
||||
}
|
||||
entries.stream().forEach(entry -> map.put(entry.getKey(), entry.getValue()));
|
||||
return this;
|
||||
}
|
||||
|
||||
public MapValueBuilder mapPair(String key) {
|
||||
return new MapValueBuilder(key);
|
||||
}
|
||||
|
||||
public Map<String, Object> build() {
|
||||
return map;
|
||||
}
|
||||
|
||||
public class MapValueBuilder {
|
||||
private final Map<String, Object> map = new HashMap<>();
|
||||
private final String key;
|
||||
|
||||
private MapValueBuilder(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public MapValueBuilder pair(String key, Object value) {
|
||||
map.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public MapValueBuilder pairs(Collection<AbstractMap.SimpleEntry<String, Object>> entries) {
|
||||
if (entries == null) {
|
||||
return this;
|
||||
}
|
||||
entries.stream().forEach(entry -> map.put(entry.getKey(), entry.getValue()));
|
||||
return this;
|
||||
}
|
||||
|
||||
public MapBuilder build() {
|
||||
MapBuilder.this.pair(key, map);
|
||||
return MapBuilder.this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.core.util;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class TimeUtils {
|
||||
|
||||
private static final Pattern HH_MM_SS_DURATION = Pattern.compile("([\\d]+):([\\d]{2}):([\\d]{2})");
|
||||
|
||||
private TimeUtils() {}
|
||||
|
||||
/**
|
||||
* Returns a {@link LocalTime} for a given milliseconds.
|
||||
*
|
||||
* @param millis
|
||||
* @return
|
||||
*/
|
||||
public static LocalDateTime toLocalTime(long millis) {
|
||||
Instant instant = new Date(millis).toInstant();
|
||||
ZonedDateTime time = instant.atZone(ZoneId.systemDefault());
|
||||
return time.toLocalDateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a human readable string representation in the format "HH:MM:SS" for a given duration.
|
||||
*
|
||||
* @param duration to be transformed to its human readable form
|
||||
*
|
||||
* @return a string "HH:MM:SS" representing the duration
|
||||
*/
|
||||
public static String toString(Duration duration) {
|
||||
return String.format("%02d:%02d:%02d",
|
||||
duration.toHours(),
|
||||
duration.toMinutes() % 60,
|
||||
duration.getSeconds() % 60);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the duration for a given string "HH:MM:SS".
|
||||
* It's the reverse operation for #toString(Duration).
|
||||
*
|
||||
* @param hoursMinutesSeconds
|
||||
* @return the duration
|
||||
*
|
||||
* @see #toString(Duration)
|
||||
*/
|
||||
public static Duration toDuration(String hoursMinutesSeconds) {
|
||||
Matcher matcher = HH_MM_SS_DURATION.matcher(hoursMinutesSeconds);
|
||||
if (!matcher.matches()) {
|
||||
return null;
|
||||
}
|
||||
long hours = Integer.parseInt(matcher.group(1));
|
||||
Duration duration = Duration.ofHours(hours);
|
||||
long minutes = Integer.parseInt(matcher.group(2));
|
||||
duration = duration.plusMinutes(minutes);
|
||||
long seconds = Integer.parseInt(matcher.group(3));
|
||||
duration = duration.plusSeconds(seconds);
|
||||
return duration;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.ui;
|
||||
|
||||
import com.intellij.icons.AllIcons;
|
||||
import com.intellij.notification.*;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.DumbAwareAction;
|
||||
import ee.carlrobert.codegpt.telemetry.core.configuration.TelemetryConfiguration;
|
||||
import ee.carlrobert.codegpt.telemetry.ui.utils.NotificationGroupFactory;
|
||||
import java.io.IOException;
|
||||
|
||||
public class TelemetryNotifications {
|
||||
|
||||
private static final Logger LOGGER = Logger.getInstance(TelemetryNotifications.class);
|
||||
|
||||
private static final NotificationGroup QUERY_USER_CONSENT = NotificationGroupFactory.create(
|
||||
"Enable Telemetry",
|
||||
NotificationDisplayType.STICKY_BALLOON,
|
||||
true);
|
||||
|
||||
private final NotificationGroup group;
|
||||
|
||||
public TelemetryNotifications() {
|
||||
this(QUERY_USER_CONSENT);
|
||||
}
|
||||
|
||||
TelemetryNotifications(NotificationGroup group) {
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
public void queryUserConsent() {
|
||||
Notification notification = group.createNotification(
|
||||
"Help CodeGPT improve its extensions by allowing them to collect anonymous usage data. " +
|
||||
"Read our <a href=\"https://codegpt.carlrobert.ee/privacy\">privacy statement</a> " +
|
||||
"and learn how to <a href=\"\">opt out</a>.",
|
||||
NotificationType.INFORMATION);
|
||||
notification.setTitle("Enable Telemetry");
|
||||
notification.setListener(new NotificationListener.UrlOpeningListener(false));
|
||||
DumbAwareAction accept = DumbAwareAction.create("Accept",
|
||||
e -> enableTelemetry(true, notification));
|
||||
DumbAwareAction deny = DumbAwareAction.create("Deny",
|
||||
e -> enableTelemetry(false, notification));
|
||||
DumbAwareAction later = DumbAwareAction.create("Later",
|
||||
e -> notification.expire());
|
||||
notification
|
||||
.addAction(accept)
|
||||
.addAction(deny)
|
||||
.addAction(later)
|
||||
.setIcon(AllIcons.General.TodoQuestion)
|
||||
.notify(null);
|
||||
}
|
||||
|
||||
private static void enableTelemetry(boolean enabled, Notification notification) {
|
||||
TelemetryConfiguration configuration = TelemetryConfiguration.getInstance();
|
||||
configuration.setEnabled(enabled);
|
||||
try {
|
||||
configuration.save();
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Could not save telemetry configuration.", e);
|
||||
} finally {
|
||||
notification.expire();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.ui.preferences;
|
||||
|
||||
import com.intellij.ui.components.JBCheckBox;
|
||||
import com.intellij.util.ui.FormBuilder;
|
||||
import com.intellij.util.ui.UI;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
/**
|
||||
* Provides a {@link JPanel} with widgets for the Telemetry settings.
|
||||
*/
|
||||
public class TelemetryComponent {
|
||||
|
||||
private static final String DESCRIPTION =
|
||||
"Help CodeGPT improve its products by sending anonymous data about features and plugins used, "
|
||||
+ "hardware and software configuration.<br/>"
|
||||
+ "<br/>"
|
||||
+ "Please note that this will not include personal data or any sensitive Information.<br/>"
|
||||
+ "The data sent complies with the <a href=\"https://codegpt.com/legal/privacy\">Privacy Policy</a>.";
|
||||
|
||||
private final JPanel panel;
|
||||
private final JBCheckBox enabled = new JBCheckBox("Send usage statistics");
|
||||
|
||||
public TelemetryComponent() {
|
||||
this.panel = FormBuilder.createFormBuilder()
|
||||
.addComponent(createCommentedPanel(enabled, DESCRIPTION), 1)
|
||||
.addComponentFillVertically(new JPanel(), 0)
|
||||
.getPanel();
|
||||
}
|
||||
|
||||
private JPanel createCommentedPanel(JComponent component, String comment) {
|
||||
// @See com.intellij.internal.ui.ComponentPanelTestAction for more details on how to create comment panels
|
||||
return UI.PanelFactory.panel(component)
|
||||
.withComment(comment)
|
||||
.createPanel();
|
||||
}
|
||||
|
||||
public JPanel getPanel() {
|
||||
return panel;
|
||||
}
|
||||
|
||||
public JComponent getPreferredFocusedComponent() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled.isSelected();
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled.setSelected(enabled);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.ui.preferences;
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.options.SearchableConfigurable;
|
||||
import ee.carlrobert.codegpt.telemetry.core.configuration.TelemetryConfiguration;
|
||||
import java.io.IOException;
|
||||
import javax.swing.JComponent;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Controller for telemetry settings.
|
||||
*/
|
||||
public class TelemetryConfigurable implements SearchableConfigurable {
|
||||
|
||||
/* plugin.xml > applicationConfigurable > id */
|
||||
public static final String ID = "tools.preferences.carlrobert.telemetry";
|
||||
|
||||
private static final Logger LOGGER = Logger.getInstance(TelemetryConfigurable.class);
|
||||
|
||||
private TelemetryComponent component;
|
||||
private final TelemetryConfiguration configuration = TelemetryConfiguration.getInstance();
|
||||
|
||||
@Nls(capitalization = Nls.Capitalization.Title)
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "CodeGPT Telemetry";
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getPreferredFocusedComponent() {
|
||||
return component.getPreferredFocusedComponent();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public JComponent createComponent() {
|
||||
this.component = new TelemetryComponent();
|
||||
return component.getPanel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModified() {
|
||||
boolean modified = false;
|
||||
modified |= (component.isEnabled() != configuration.isEnabled());
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
configuration.setEnabled(component.isEnabled());
|
||||
try {
|
||||
configuration.save();
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Could not save telemetry configuration.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
component.setEnabled(configuration.isEnabled());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disposeUIResources() {
|
||||
component = null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc.
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.ui.utils;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationInfo;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.ui.ColorUtil;
|
||||
import java.awt.Color;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import javax.swing.JLabel;
|
||||
|
||||
public class JBLabelUtils {
|
||||
|
||||
private static final Logger LOGGER = Logger.getInstance(JBLabelUtils.class);
|
||||
|
||||
private JBLabelUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns the given text to html that a JBLabel can display (with links).
|
||||
*
|
||||
* @param text the text to convert to Html
|
||||
* @param maxLineLength the maximum number of characters in a line
|
||||
* @param component the component to retrieve the font metrics from
|
||||
*
|
||||
* @returns the text
|
||||
*/
|
||||
public static String toStyledHtml(String text, int maxLineLength, JLabel component) {
|
||||
String css = "<head><style type=\"text/css\">\n" +
|
||||
"a, a:link {color:#" + toHex(getColor(
|
||||
"com.intellij.util.ui.JBUI$CurrentTheme$Link", "linkColor",
|
||||
"com.intellij.util.ui.JBUI$CurrentTheme$Link$Foreground", "ENABLED")) + ";}\n" +
|
||||
"a:visited {color:#" + toHex(getColor(
|
||||
"com.intellij.util.ui.JBUI$CurrentTheme$Link", "linkVisitedColor",
|
||||
"com.intellij.util.ui.JBUI$CurrentTheme$Link$Foreground", "VISITED")) + ";}\n" +
|
||||
"a:hover {color:#" + toHex(getColor(
|
||||
"com.intellij.util.ui.JBUI$CurrentTheme$Link", "linkHoverColor",
|
||||
"com.intellij.util.ui.JBUI$CurrentTheme$Link$Foreground", "HOVERED")) + ";}\n" +
|
||||
"a:active {color:#" + toHex(getColor(
|
||||
"com.intellij.util.ui.JBUI$CurrentTheme$Link", "linkPressedColor",
|
||||
"com.intellij.util.ui.JBUI$CurrentTheme$Link$Foreground", "PRESSED")) + ";}\n" +
|
||||
"</style>\n</head>";
|
||||
int width = component.getFontMetrics(component.getFont()).stringWidth(text.substring(0, maxLineLength));
|
||||
return "<html>" + css + "<body><div width=" + width + ">" + text + "</div></body></html>";
|
||||
}
|
||||
|
||||
private static Color getColor(String methodClass, String method, String fieldClass, String field) {
|
||||
try {
|
||||
// < IC-2022.1
|
||||
Class clazz = Class.forName(methodClass);
|
||||
Method colorMethod = clazz.getMethod(method);
|
||||
return (Color) colorMethod.invoke(null);
|
||||
} catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException | ClassCastException e) {
|
||||
// >= IC-2022.1
|
||||
try {
|
||||
Class clazz = Class.forName(fieldClass);
|
||||
Field colorField = clazz.getDeclaredField(field);
|
||||
return (Color) colorField.get(null);
|
||||
} catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | ClassCastException ex) {
|
||||
LOGGER.warn("Could not retrieve link colors from "
|
||||
+ methodClass + "/" + method + " or " + fieldClass + "/" + field
|
||||
+ "on IC-" + ApplicationInfo.getInstance().getBuild().asStringWithoutProductCode(), ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String toHex(Color color) {
|
||||
if (color == null) {
|
||||
return "";
|
||||
}
|
||||
return ColorUtil.toHex(color);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Red Hat, Inc.
|
||||
* Distributed under license by Red Hat, Inc. All rights reserved.
|
||||
* This program is made available under the terms of the
|
||||
* Eclipse Public License v2.0 which accompanies this distribution,
|
||||
* and is available at http://www.eclipse.org/legal/epl-v20.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
******************************************************************************/
|
||||
package ee.carlrobert.codegpt.telemetry.ui.utils;
|
||||
|
||||
import com.intellij.notification.NotificationDisplayType;
|
||||
import com.intellij.notification.NotificationGroup;
|
||||
import com.intellij.openapi.application.ApplicationInfo;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* A factory to create {@link NotificationGroup} on >= IC-2019.3 and >= IC-2021.3 where the API was broken.
|
||||
*
|
||||
* <ul>
|
||||
* <li><b>IC-2019.3</b>
|
||||
* <pre>{@code
|
||||
* new NotificationGroup(String displayId, NotificationDisplayType defaultDisplayType, boolean logByDefault)
|
||||
* }</pre>
|
||||
* </li>
|
||||
* <li><b></b>IC-2021.3</b>
|
||||
* <pre>{@code
|
||||
* NotificationGroupManager.getInstance().getNotificationGroup(displayId)
|
||||
* }</pre>
|
||||
* </li>
|
||||
* </ul>
|
||||
*/
|
||||
public class NotificationGroupFactory {
|
||||
|
||||
public static NotificationGroup create(String displayId, NotificationDisplayType type, boolean logByDefault) {
|
||||
try {
|
||||
// < IC-2021.3
|
||||
// new NotificationGroup(String displayId, NotificationDisplayType defaultDisplayType, boolean logByDefault)
|
||||
Constructor<NotificationGroup> constructor = NotificationGroup.class.getConstructor(String.class, NotificationDisplayType.class, boolean.class);
|
||||
return constructor.newInstance(displayId, type, logByDefault);
|
||||
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
|
||||
// >= IC-2021.3
|
||||
// constructor removed >= 2021.3
|
||||
try {
|
||||
// NotificationGroupManager.getInstance().getNotificationGroup(displayId)
|
||||
Class<?> managerClass = Class.forName("com.intellij.notification.NotificationGroupManager");
|
||||
Method getInstance = managerClass.getMethod("getInstance");
|
||||
Object manager = getInstance.invoke(null);
|
||||
if (manager == null) {
|
||||
return null;
|
||||
}
|
||||
Method getNotificationGroup = managerClass.getMethod("getNotificationGroup", String.class);
|
||||
Object group = getNotificationGroup.invoke(manager, displayId);
|
||||
if (!(group instanceof NotificationGroup)) {
|
||||
return null;
|
||||
}
|
||||
return (NotificationGroup) group;
|
||||
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
|
||||
Logger.getInstance(NotificationGroupFactory.class).warn("Could not create NotificationGroup for IC-"
|
||||
+ ApplicationInfo.getInstance().getBuild().asStringWithoutProductCode());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue