diff --git a/Editor/Api/AnalyticsApi.cs b/Editor/Api/AnalyticsApi.cs new file mode 100644 index 0000000..318cc0c --- /dev/null +++ b/Editor/Api/AnalyticsApi.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; +using UnityEngine; + +namespace Edgegap.Editor.Api +{ + public class AnalyticsApi + { + private readonly HttpClient _httpClient = new HttpClient(); + private string _url = "https://us.i.posthog.com/i/v0/e/"; + private string _Key = "phc_sjDOXB5OakYZu0h70u4GLcFR7hZ55XfnnDef5xaeDws"; + private string _Event = "Plugin Button Click"; + private string _DistinctId; + + private class AnalyticsPayload + { + [JsonProperty("api_key")] + public string ApiKey { get; set; } + + [JsonProperty("event")] + public string Event { get; set; } + + [JsonProperty("distinct_id")] + public string DistinctId { get; set; } + + [JsonProperty("properties")] + public Dictionary Properties { get; set; } + + public AnalyticsPayload( + string apiKey, + string ev, + string disctinctId, + Dictionary properties + ) + { + this.ApiKey = apiKey; + this.Event = ev; + this.DistinctId = disctinctId; + this.Properties = properties; + } + + public override string ToString() => JsonConvert.SerializeObject(this); + } + + public AnalyticsApi(string distinctId) + { + this._httpClient.BaseAddress = new Uri(_url); + this._httpClient.DefaultRequestHeaders.Accept.Add( + new MediaTypeWithQualityHeaderValue("application/json") + ); + this._DistinctId = distinctId; + } + + public async Task PostAsync(Dictionary properties) + { + AnalyticsPayload payload = new AnalyticsPayload(_Key, _Event, _DistinctId, properties); + StringContent stringContent = new StringContent( + payload.ToString(), + Encoding.UTF8, + "application/json" + ); + + try + { + return await _httpClient.PostAsync(_httpClient.BaseAddress, stringContent); + } + catch (Exception e) + { + Debug.LogError($"Error: {e}"); + throw; + } + } + } +} diff --git a/Editor/Api/AnalyticsApi.cs.meta b/Editor/Api/AnalyticsApi.cs.meta new file mode 100644 index 0000000..c45facf --- /dev/null +++ b/Editor/Api/AnalyticsApi.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f145b77191ba04842a4e0f94261d1c35 \ No newline at end of file diff --git a/Editor/EdgegapWindowV2.cs b/Editor/EdgegapWindowV2.cs index d5b9a2e..3d8bada 100644 --- a/Editor/EdgegapWindowV2.cs +++ b/Editor/EdgegapWindowV2.cs @@ -900,8 +900,16 @@ private async Task checkForUpdates() /// /// "Sign out" btn click /// - private void OnSignOutBtnClickAsync() + private async void OnSignOutBtnClickAsync() { + AnalyticsApi analyticsApi = getAnalyticsApi(); + Dictionary properties = new Dictionary() + { + { "button_name", "1. Connect Edgegap Account > Sign out" }, + { "utm_source", "plugin_unity" }, + }; + await analyticsApi.PostAsync(properties); + EditorPrefs.DeleteKey(EdgegapWindowMetadata.API_TOKEN_KEY_STR); EditorPrefs.DeleteKey(EdgegapWindowMetadata.SELECTED_NETCODE_KEY_STR); _apiTokenInput.SetValueWithoutNotify(""); @@ -967,8 +975,16 @@ private async void OnInstallLinuxBtnClick() /// /// Open Unity build settings btn click /// - private void OnOpenBuildParamsBtnClick() + private async void OnOpenBuildParamsBtnClick() { + AnalyticsApi analyticsApi = getAnalyticsApi(); + Dictionary properties = new Dictionary() + { + { "button_name", "2. Build Game Server > Edit Settings" }, + { "utm_source", "plugin_unity" }, + }; + await analyticsApi.PostAsync(properties); + #if UNITY_2021_3_OR_NEWER EditorWindow.GetWindow( System.Type.GetType("UnityEditor.BuildPlayerWindow,UnityEditor") @@ -992,6 +1008,22 @@ private void OnFolderNameInputFocusOut(FocusOutEvent evt) /// private void OnBuildServerBtnClick() { + _ = BuildServer(); + } + + private async Task BuildServer(string buttonName = "2. Build Game Server > Build") + { + AnalyticsApi analyticsApi = getAnalyticsApi(); + Dictionary properties = new Dictionary() + { + { "button_name", buttonName }, + { "utm_source", "plugin_unity" }, + }; + if (buttonName.Contains("Rebuild from Source")) + { + properties.Add("rebuild_step", "Build"); + } + try { _serverBuildBtn.SetEnabled(false); @@ -1022,10 +1054,17 @@ private void OnBuildServerBtnClick() if (buildResult.summary.result != BuildResult.Succeeded) { Debug.LogWarning(buildResult.summary.result.ToString()); + + properties.Add("succeeded", false.ToString()); + properties.Add("error_message", buildResult.summary.result.ToString()); + await analyticsApi.PostAsync(properties); } else { OnBuildContainerizeUploadSuccess(_serverBuildResultLabel, "Build succeeded."); + + properties.Add("succeeded", true.ToString()); + await analyticsApi.PostAsync(properties); } _containerizeFoldout.SetValueWithoutNotify(true); @@ -1033,6 +1072,11 @@ private void OnBuildServerBtnClick() } catch (Exception e) { + properties.Add("succeeded", false.ToString()); + properties.Add("error_message", e.Message); + properties.Add("stack_trace", e.StackTrace); + await analyticsApi.PostAsync(properties); + Debug.LogError($"OnBuildServerBtnClick Error: {e}"); ShowErrorDialog(e.Message, _serverBuildResultLabel, "Build failed (see logs)."); } @@ -1053,11 +1097,18 @@ private void OnDockerInfoClick() => /// private void OnValidateDockerBtnClick() { - _ = ValidateDockerRequirement(); + _ = ValidateDockerRequirement(true); } - private async Task ValidateDockerRequirement() + private async Task ValidateDockerRequirement(bool runAnalytics) { + AnalyticsApi analyticsApi = getAnalyticsApi(); + Dictionary properties = new Dictionary() + { + { "button_name", "3. Containerize Server > Validate Docker" }, + { "utm_source", "plugin_unity" }, + }; + _validateDockerRequirementsBtn.SetEnabled(false); hideResultLabels(); string error; @@ -1070,6 +1121,14 @@ private async Task ValidateDockerRequirement() catch (Exception e) { error = e.Message; + + if (runAnalytics) + { + properties.Add("succeeded", false.ToString()); + properties.Add("error_message", e.Message); + properties.Add("stack_trace", e.StackTrace); + await analyticsApi.PostAsync(properties); + } } _validateDockerRequirementsBtn.SetEnabled(true); @@ -1097,6 +1156,7 @@ private async Task ValidateDockerRequirement() _dockerRequirementsResultLabel, "There was a problem." ); + return error; } else @@ -1105,6 +1165,12 @@ private async Task ValidateDockerRequirement() _dockerRequirementsResultLabel, "Docker is running." ); + + if (runAnalytics) + { + properties.Add("succeeded", true.ToString()); + await analyticsApi.PostAsync(properties); + } } return null; } @@ -1225,10 +1291,27 @@ private void OnContainerizeBtnClick() _ = ContainerizeServerAsync(); } - private async Task ContainerizeServerAsync() + private async Task ContainerizeServerAsync( + string buttonName = "3. Containerize Server > Containerize" + ) { - if (!string.IsNullOrEmpty(await ValidateDockerRequirement())) + AnalyticsApi analyticsApi = getAnalyticsApi(); + + if (!string.IsNullOrEmpty(await ValidateDockerRequirement(false))) { + Dictionary properties = new Dictionary() + { + { "button_name", buttonName }, + { "utm_source", "plugin_unity" }, + { "succeeded", false.ToString() }, + { "error_message", "Docker validation failed"} + }; + if (buttonName.Contains("Rebuild from Source")) + { + properties.Add("rebuild_step", "Containerize"); + } + await analyticsApi.PostAsync(properties); + return; } try @@ -1300,9 +1383,47 @@ await EdgegapBuildUtils.RunCommand_DockerBuild( { _localTestImageShowDropdownBtn.SetEnabled(true); } + + Dictionary properties = new Dictionary() + { + { "button_name", buttonName }, + { "utm_source", "plugin_unity" }, + { "docker_params", _optionalDockerParamsInput.value }, + { "image_name", imageName }, + { "image_tag", tag }, + { "succeeded", true.ToString() } + }; + if (buttonName.Contains("Rebuild from Source")) + { + properties.Add("rebuild_step", "Containerize"); + } + await analyticsApi.PostAsync(properties); } catch (Exception e) { + string imageName = Tokenize(_containerizeImageNameInput.value); + string tag = + _containerizeImageTagInput.value == _containerizeImageTagInputDefault + ? nowUTC + : _containerizeImageTagInput.value; + + Dictionary properties = new Dictionary() + { + { "button_name", buttonName }, + { "utm_source", "plugin_unity" }, + { "docker_params", _optionalDockerParamsInput.value }, + { "image_name", imageName }, + { "image_tag", tag }, + { "succeeded", false.ToString() }, + { "error_message", e.Message }, + { "stack_trace", e.StackTrace } + }; + if (buttonName.Contains("Rebuild from Source")) + { + properties.Add("rebuild_step", "Containerize"); + } + await analyticsApi.PostAsync(properties); + Debug.LogError($"Containerization Error: {e}"); ShowErrorDialog( e.Message, @@ -1392,6 +1513,15 @@ private bool CheckFilledLocalTestInputs() private async void OnLocalTestDeployClick() { + AnalyticsApi analyticsApi = getAnalyticsApi(); + Dictionary properties = new Dictionary() + { + { "button_name", "4. Test Locally > Deploy" }, + { "utm_source", "plugin_unity" }, + { "server_image", _localTestImageInput.value }, + { "docker_params", _localTestDockerRunInput.value }, + }; + try { hideResultLabels(); @@ -1429,6 +1559,9 @@ private async void OnLocalTestDeployClick() true ); _createAppFoldout.SetValueWithoutNotify(true); + + properties.Add("succeeded", true.ToString()); + await analyticsApi.PostAsync(properties); } catch (Exception e) { @@ -1444,6 +1577,11 @@ private async void OnLocalTestDeployClick() labelMsg = "There was an issue while deploying. See more in Docker Desktop or Docker CLI."; Debug.LogError($"OnLocalTestDeploy Error: {e}"); + + properties.Add("succeeded", false.ToString()); + properties.Add("error_message", e.Message); + properties.Add("stack_trace", e.StackTrace); + await analyticsApi.PostAsync(properties); } OnLocalDeploymentResult(labelMsg, false); @@ -1452,6 +1590,15 @@ private async void OnLocalTestDeployClick() private async void OnLocalTestTerminateCLick() { + AnalyticsApi analyticsApi = getAnalyticsApi(); + Dictionary properties = new Dictionary() + { + { "button_name", "4. Test Locally > Terminate" }, + { "utm_source", "plugin_unity" }, + { "server_image", _localTestImageInput.value }, + { "docker_params", _localTestDockerRunInput.value }, + }; + try { hideResultLabels(); @@ -1461,8 +1608,10 @@ private async void OnLocalTestTerminateCLick() await EdgegapBuildUtils.RunCommand_DockerStop(); OnLocalDeploymentResult("Container terminated successfully.", true); - _createAppFoldout.SetValueWithoutNotify(true); + + properties.Add("succeeded", true.ToString()); + await analyticsApi.PostAsync(properties); } catch (Exception e) { @@ -1477,6 +1626,11 @@ private async void OnLocalTestTerminateCLick() "There was an issue while terminating. See more in Docker Desktop or Docker CLI.", false ); + + properties.Add("succeeded", false.ToString()); + properties.Add("error_message", e.Message); + properties.Add("stack_trace", e.StackTrace); + await analyticsApi.PostAsync(properties); } } } @@ -1593,8 +1747,24 @@ private void OnUploadImageCreateAppBtnClick() _ = UploadImageCreateAppAsync(); } - private async Task UploadImageCreateAppAsync() + private async Task UploadImageCreateAppAsync( + string buttonName = "5. Upload to Edgegap > Upload and Create App" + ) { + AnalyticsApi analyticsApi = getAnalyticsApi(); + Dictionary properties = new Dictionary() + { + { "button_name", buttonName }, + { "utm_source", "plugin_unity" }, + { "app_name", _createAppNameInput.value }, + { "image_name", _serverImageNameInput.value }, + { "image_tag", _serverImageTagInput.value }, + }; + if (buttonName.Contains("Rebuild from Source")) + { + properties.Add("rebuild_step", "Upload and Create App"); + } + try { _uploadImageCreateAppBtn.SetEnabled(false); @@ -1634,6 +1804,9 @@ private async Task UploadImageCreateAppAsync() throw new Exception("Unable to push docker image to registry (see logs)."); } + properties.Add("succeeded", true.ToString()); + await analyticsApi.PostAsync(properties); + ShowWorkInProgress("Create Application", "Updating server info on Edgegap"); if (IsLogLevelDebug) @@ -1693,6 +1866,11 @@ private async Task UploadImageCreateAppAsync() } catch (Exception e) { + properties.Add("succeeded", false.ToString()); + properties.Add("error_message", e.Message); + properties.Add("stack_trace", e.StackTrace); + await analyticsApi.PostAsync(properties); + if ( e.Message.Contains("Docker authorization failed") || e.Message.Contains("Unable to push docker image") @@ -1740,7 +1918,7 @@ private async void OnRebuildFromSrcBtnClickAsync() hideResultLabels(); //Build - OnBuildServerBtnClick(); + await BuildServer("5. Upload to Edgegap > Rebuild from Source"); if (_serverBuildResultLabel.text.Contains(EdgegapWindowMetadata.FAIL_COLOR_HEX)) { @@ -1754,7 +1932,7 @@ private async void OnRebuildFromSrcBtnClickAsync() _containerizeImageTagInput.SetValueWithoutNotify(_containerizeImageTagInputDefault); } - await ContainerizeServerAsync(); + await ContainerizeServerAsync("5. Upload to Edgegap > Rebuild from Source"); if ( _containerizeServerResultLabel.text.Contains(EdgegapWindowMetadata.FAIL_COLOR_HEX) @@ -1768,7 +1946,7 @@ private async void OnRebuildFromSrcBtnClickAsync() } //Upload - await UploadImageCreateAppAsync(); + await UploadImageCreateAppAsync("5. Upload to Edgegap > Rebuild from Source"); } #endregion @@ -1970,8 +2148,22 @@ private async Task CreateDeploymentStartServer() // Request to deploy (it won't be active, yet) => EdgegapHttpResult createDeploymentResponse = await deployApi.CreateDeploymentAsync(createDeploymentReq); + + AnalyticsApi analyticsApi = getAnalyticsApi(); + Dictionary properties = new Dictionary() + { + { "button_name", "6. Deploy > Start" }, + { "utm_source", "plugin_unity" }, + { "app_name", _deployAppNameInput.value }, + { "app_version", _deployAppVersionInput.value }, + }; + if (!createDeploymentResponse.IsResultCode202) { + properties.Add("succeeded", false.ToString()); + properties.Add("error_message", createDeploymentResponse.Error.ErrorMessage); + await analyticsApi.PostAsync(properties); + OnCreateDeploymentStartServerFail(createDeploymentResponse.Error.ErrorMessage); return; } @@ -2002,9 +2194,16 @@ await deployApi.AwaitReadyStatusAsync( EdgegapWindowMetadata.StatusColors.Success ); _deployResultLabel.style.display = DisplayStyle.Flex; + + properties.Add("succeeded", true.ToString()); + await analyticsApi.PostAsync(properties); } else { + properties.Add("succeeded", false.ToString()); + properties.Add("error_message", getDeploymentStatusResponse.Error.ErrorMessage); + await analyticsApi.PostAsync(properties); + OnCreateDeploymentStartServerFail(getDeploymentStatusResponse.Error.ErrorMessage); } } @@ -2032,6 +2231,15 @@ private void OnCreateDeploymentStartServerFail(string message = null) /// private async void OnStopLastDeployClick() { + AnalyticsApi analyticsApi = getAnalyticsApi(); + Dictionary properties = new Dictionary() + { + { "button_name", "6. Deploy > Stop" }, + { "utm_source", "plugin_unity" }, + { "app_name", _deployAppNameInput.value }, + { "app_version", _deployAppVersionInput.value }, + }; + try { hideResultLabels(); @@ -2069,6 +2277,10 @@ private async void OnStopLastDeployClick() if (!stopDeploymentResponse.IsResultCode200) { + properties.Add("succeeded", false.ToString()); + properties.Add("error_message", stopDeploymentResponse.Error.ErrorMessage); + await analyticsApi.PostAsync(properties); + OnGetStopLastDeploymentResult(stopDeploymentResponse.Error.ErrorMessage, false); return; } @@ -2085,10 +2297,17 @@ private async void OnStopLastDeployClick() //Process response if (!stopDeploymentResponse.IsResultCode410) { + properties.Add("succeeded", false.ToString()); + properties.Add("error_message", stopDeploymentResponse.Error.ErrorMessage); + await analyticsApi.PostAsync(properties); + OnGetStopLastDeploymentResult(stopDeploymentResponse.Error.ErrorMessage, false); } else { + properties.Add("succeeded", true.ToString()); + await analyticsApi.PostAsync(properties); + OnGetStopLastDeploymentResult("Deployment stopped successfully", true); } @@ -2096,6 +2315,11 @@ private async void OnStopLastDeployClick() } catch (Exception e) { + properties.Add("succeeded", false.ToString()); + properties.Add("error_message", e.Message); + properties.Add("stack_trace", e.StackTrace); + await analyticsApi.PostAsync(properties); + OnGetStopLastDeploymentResult(e.Message, false); OpenEdgegapURL(EdgegapWindowMetadata.EDGEGAP_DEPLOY_APP_URL); } @@ -2559,6 +2783,13 @@ public static string Base64Decode(string base64EncodedText) return Encoding.UTF8.GetString(base64Bytes); } #endregion + + #region Utility / Analytics + private AnalyticsApi getAnalyticsApi() => new AnalyticsApi(getDistinctId()); + + //TODO - replace with hash + private string getDistinctId() => "marjorie.dudemaine@edgegap.com"; + #endregion } }