Skip to content

Commit

Permalink
refactor: wizard API & tests
Browse files Browse the repository at this point in the history
  • Loading branch information
vaind committed Jun 20, 2022
1 parent 943e018 commit c9aa74f
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 69 deletions.
91 changes: 22 additions & 69 deletions src/Sentry.Unity.Editor/ConfigurationWindow/Wizard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Threading;
using System.Threading.Tasks;
using Sentry.Extensibility;
using Sentry.Unity.Editor.WizardApi;
using UnityEditor;
using UnityEngine;

Expand Down Expand Up @@ -40,7 +41,7 @@ public static void Start(IDiagnosticLogger logger)

private void StartLoder()
{
_task = new WizardLoader(this, _logger);
_task = new WizardLoader(_logger);
Task.Run(async () => Response = await _task.Load()).ContinueWith(t =>
{
if (t.Exception is not null)
Expand All @@ -63,11 +64,11 @@ private void OnGUI()

EditorGUILayout.Space();

if (Response.Projects.Count == 0)
if (Response.projects.Count == 0)
{
wizardConfiguration = new WizardConfiguration
{
Token = Response.ApiKeys!.Token
Token = Response.apiKeys!.token
};

EditorGUILayout.LabelField("There don't seem to be any projects in your sentry.io account.");
Expand All @@ -80,11 +81,11 @@ private void OnGUI()
var firstEntry = new string(' ', 60);

// sort "unity" projects first
Response.Projects.Sort((a, b) =>
Response.projects.Sort((a, b) =>
{
if (a.IsUnity == b.IsUnity)
{
return (a.Name ?? "").CompareTo(b.Name ?? "");
return (a.name ?? "").CompareTo(b.name ?? "");
}
else if (a.IsUnity)
{
Expand All @@ -96,7 +97,7 @@ private void OnGUI()
}
});

var orgsAndProjects = Response.Projects.GroupBy(k => k.Organization!.Name, v => v).ToArray();
var orgsAndProjects = Response.projects.GroupBy(k => k.organization!.name, v => v).ToArray();

// if only one org
if (orgsAndProjects.Length == 1)
Expand All @@ -111,7 +112,7 @@ private void OnGUI()

if (_orgSelected > 0)
{
_projectSelected = EditorGUILayout.Popup("Project", _projectSelected, orgsAndProjects[_orgSelected - 1].Select(v => $"{v.Name} - ({v.Slug})").ToArray()
_projectSelected = EditorGUILayout.Popup("Project", _projectSelected, orgsAndProjects[_orgSelected - 1].Select(v => $"{v.name} - ({v.slug})").ToArray()
.Prepend(firstEntry).ToArray());
}

Expand All @@ -120,10 +121,10 @@ private void OnGUI()
var proj = orgsAndProjects[_orgSelected - 1].ToArray()[_projectSelected - 1];
wizardConfiguration = new WizardConfiguration
{
Token = Response.ApiKeys!.Token,
Dsn = proj.Keys.First().Dsn!.Public,
OrgSlug = proj.Organization!.Slug,
ProjectSlug = proj.Slug,
Token = Response.apiKeys!.token,
Dsn = proj.keys.First().dsn!.@public,
OrgSlug = proj.organization!.slug,
ProjectSlug = proj.slug,
};
}
}
Expand Down Expand Up @@ -218,48 +219,6 @@ internal static void OpenUrl(string url)

Application.OpenURL(parsedUri.ToString());
}

internal class WizardStep1Response
{
public string? Hash { get; set; }
}
internal class WizardStep2Response
{
public ApiKeys? ApiKeys { get; set; }
public List<Project> Projects { get; set; } = new List<Project>(0);
}

internal class ApiKeys
{
public string? Token { get; set; }
}

internal class Project
{
public Organization? Organization { get; set; }
public string? Slug { get; set; }
public string? Name { get; set; }
public string? Platform { get; set; }
public IEnumerable<Key> Keys { get; set; } = Enumerable.Empty<Key>();

public bool IsUnity => string.Equals(Platform, "unity", StringComparison.InvariantCultureIgnoreCase);
}

internal class Key
{
public Dsn? Dsn { get; set; }
}

internal class Dsn
{
public string? Public { get; set; }
}

internal class Organization
{
public string? Name { get; set; }
public string? Slug { get; set; }
}
}

internal class WizardConfiguration
Expand All @@ -279,7 +238,6 @@ internal WizardCancelled(string message, Exception innerException) : base(messag

internal class WizardLoader
{
private Wizard _wizard;
private IDiagnosticLogger _logger;
internal float _progress = 0.0f;
internal string _progressText = "";
Expand All @@ -289,15 +247,8 @@ internal class WizardLoader
internal Action? _uiAction = null;
private const int StepCount = 5;

private readonly JsonSerializerOptions _serializeOptions = new()
public WizardLoader(IDiagnosticLogger logger)
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true
};

public WizardLoader(Wizard wizard, IDiagnosticLogger logger)
{
_wizard = wizard;
_logger = logger;
}

Expand All @@ -316,23 +267,23 @@ private void Progress(string status, float current)
_progress = current;
}

internal async Task<Wizard.WizardStep2Response?> Load()
internal async Task<WizardStep2Response?> Load()
{
Wizard.WizardStep2Response? response = null;
WizardStep2Response? response = null;
try
{
Progress("Started", 1);

Progress("Connecting to sentry.io settings wizard...", 2);
var http = new HttpClient();
var resp = await http.GetAsync("https://sentry.io/api/0/wizard/").ConfigureAwait(false);
var wizardHashResponse = await DeserializeJson<Wizard.WizardStep1Response>(resp);
var wizardHashResponse = await DeserializeJson<WizardStep1Response>(resp);

Progress("Opening sentry.io in the default browser...", 3);
await RunOnUiThread(() => Wizard.OpenUrl($"https://sentry.io/account/settings/wizard/{wizardHashResponse.Hash}/"));
await RunOnUiThread(() => Wizard.OpenUrl($"https://sentry.io/account/settings/wizard/{wizardHashResponse.hash}/"));

// Poll https://sentry.io/api/0/wizard/hash/
var pollingUrl = $"https://sentry.io/api/0/wizard/{wizardHashResponse.Hash}/";
var pollingUrl = $"https://sentry.io/api/0/wizard/{wizardHashResponse.hash}/";

Progress("Waiting for the the response from the browser session...", 4);

Expand All @@ -343,7 +294,7 @@ private void Progress(string status, float current)
resp = await http.GetAsync(pollingUrl).ConfigureAwait(false);
if (resp.StatusCode != HttpStatusCode.BadRequest) // not ready yet
{
response = await DeserializeJson<Wizard.WizardStep2Response>(resp).ConfigureAwait(false);
response = await DeserializeJson<WizardStep2Response>(resp).ConfigureAwait(false);
break;
}
}
Expand Down Expand Up @@ -378,9 +329,11 @@ private void Progress(string status, float current)
private async Task<T> DeserializeJson<T>(HttpResponseMessage response)
{
var content = await response.EnsureSuccessStatusCode().Content.ReadAsByteArrayAsync().ConfigureAwait(false);
return JsonSerializer.Deserialize<T>(content, _serializeOptions)!;
return DeserializeJson<T>(System.Text.Encoding.UTF8.GetString(content));
}

internal T DeserializeJson<T>(string json) => JsonUtility.FromJson<T>(json);

private Task RunOnUiThread(Action callback)
{
var tcs = new TaskCompletionSource<bool>();
Expand Down
68 changes: 68 additions & 0 deletions src/Sentry.Unity.Editor/ConfigurationWindow/WizardApi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Sentry.Extensibility;
using UnityEditor;
using UnityEngine;

namespace Sentry.Unity.Editor.WizardApi
{
[Serializable]
internal class WizardStep1Response
{
public string? hash;
}

[Serializable]
internal class WizardStep2Response
{
public ApiKeys? apiKeys;
public List<Project> projects = new List<Project>(0);
}

[Serializable]
internal class ApiKeys
{
public string? token;
}

[Serializable]
internal class Project
{
public Organization? organization;
public string? slug;
public string? name;
public string? platform;
public List<Key>? keys;

public bool IsUnity => string.Equals(platform, "unity", StringComparison.InvariantCultureIgnoreCase);
}

[Serializable]
internal class Key
{
public Dsn? dsn;
}

[Serializable]
internal class Dsn
{
public string? @public;
}

[Serializable]
internal class Organization
{
public string? name;
public string? slug;
}

interface WizardApi {

}
}
58 changes: 58 additions & 0 deletions test/Sentry.Unity.Editor.Tests/WizardTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Sentry.Unity.Editor.ConfigurationWindow;
using Sentry.Unity.Editor.WizardApi;
using UnityEngine.TestTools;
using Sentry.Unity.Tests.SharedClasses;

namespace Sentry.Unity.Editor.Tests
{
public sealed class WizardJson
{
[Test]
public void Step1Response()
{
var sut = new WizardLoader(new TestLogger());

var parsed = sut.DeserializeJson<WizardStep1Response>("{\"hash\":\"foo\"}");

Assert.AreEqual("foo", parsed.hash);
}

[Test]
public void Step2Response()
{
var sut = new WizardLoader(new TestLogger());

var json = "{\"apiKeys\":{\"id\":\"key-1\",\"scopes\":[\"org:read\",\"project:read\",\"project:releases\",\"project:write\"],\"application\":null,\"expiresAt\":null,\"dateCreated\":\"2022-03-02T10:37:56.385524Z\",\"state\":null,\"token\":\"api-key-token\",\"refreshToken\":null},\"projects\":[{\"id\":\"project-1\",\"slug\":\"project-slug\",\"name\":\"personal\",\"isPublic\":false,\"isBookmarked\":false,\"color\":\"#3fb7bf\",\"dateCreated\":\"2022-01-15T20:05:53.883628Z\",\"firstEvent\":\"2022-01-15T20:15:10.171648Z\",\"firstTransactionEvent\":false,\"hasSessions\":true,\"features\":[\"alert-filters\",\"issue-alerts-targeting\",\"minidump\",\"performance-suspect-spans-ingestion\",\"race-free-group-creation\",\"similarity-indexing\",\"similarity-view\",\"releases\"],\"status\":\"active\",\"platform\":\"flutter\",\"isInternal\":false,\"isMember\":false,\"hasAccess\":true,\"avatar\":{\"avatarType\":\"letter_avatar\",\"avatarUuid\":null},\"organization\":{\"id\":\"org-1\",\"slug\":\"org-slug\",\"status\":{\"id\":\"active\",\"name\":\"active\"},\"name\":\"organization-1\",\"dateCreated\":\"2022-01-15T20:03:49.620687Z\",\"isEarlyAdopter\":false,\"require2FA\":false,\"requireEmailVerification\":false,\"avatar\":{\"avatarType\":\"letter_avatar\",\"avatarUuid\":null},\"features\":[\"onboarding\",\"ondemand-budgets\",\"slack-overage-notifications\",\"dashboards-template\",\"discover-frontend-use-events-endpoint\",\"integrations-stacktrace-link\",\"crash-rate-alerts\",\"org-subdomains\",\"performance-dry-run-mep\",\"mobile-app\",\"custom-event-title\",\"advanced-search\",\"widget-library\",\"auto-start-free-trial\",\"release-health-return-metrics\",\"minute-resolution-sessions\",\"capture-lead\",\"invite-members-rate-limits\",\"alert-crash-free-metrics\",\"alert-wizard-v3\",\"images-loaded-v2\",\"duplicate-alert-rule\",\"performance-autogroup-sibling-spans\",\"performance-ops-breakdown\",\"new-widget-builder-experience-design\",\"performance-suspect-spans-view\",\"unified-span-view\",\"performance-frontend-use-events-endpoint\",\"widget-viewer-modal\",\"event-attachments\",\"symbol-sources\",\"performance-span-histogram-view\",\"intl-sales-tax\",\"metrics-extraction\",\"performance-view\",\"new-weekly-report\",\"performance-span-tree-autoscroll\",\"metric-alert-snql\",\"shared-issues\",\"dashboard-grid-layout\",\"open-membership\"]},\"keys\":[{\"id\":\"key-1\",\"name\":\"Default\",\"label\":\"Default\",\"public\":\"public-key\",\"secret\":\"secret-key\",\"projectId\":12345,\"isActive\":true,\"rateLimit\":null,\"dsn\":{\"secret\":\"dsn-secret\",\"public\":\"dsn-public\",\"csp\":\"\",\"security\":\"\",\"minidump\":\"\",\"unreal\":\"\",\"cdn\":\"\"},\"browserSdkVersion\":\"6.x\",\"browserSdk\":{\"choices\":[[\"latest\",\"latest\"],[\"7.x\",\"7.x\"],[\"6.x\",\"6.x\"],[\"5.x\",\"5.x\"],[\"4.x\",\"4.x\"]]},\"dateCreated\":\"2022-01-15T20:05:53.895882Z\"}]},{\"id\":\"project-2\",\"slug\":\"trending-movies\",\"name\":\"trending-movies\",\"isPublic\":false,\"isBookmarked\":false,\"color\":\"#bfb93f\",\"dateCreated\":\"2022-06-16T16:34:36.833418Z\",\"firstEvent\":null,\"firstTransactionEvent\":false,\"hasSessions\":false,\"features\":[\"alert-filters\",\"custom-inbound-filters\",\"data-forwarding\",\"discard-groups\",\"issue-alerts-targeting\",\"minidump\",\"performance-suspect-spans-ingestion\",\"race-free-group-creation\",\"rate-limits\",\"servicehooks\",\"similarity-indexing\",\"similarity-indexing-v2\",\"similarity-view\",\"similarity-view-v2\"],\"status\":\"active\",\"platform\":\"apple-ios\",\"isInternal\":false,\"isMember\":false,\"hasAccess\":true,\"avatar\":{\"avatarType\":\"letter_avatar\",\"avatarUuid\":null},\"organization\":{\"id\":\"organization-2\",\"slug\":\"sentry-sdks\",\"status\":{\"id\":\"active\",\"name\":\"active\"},\"name\":\"Sentry SDKs\",\"dateCreated\":\"2020-09-14T17:28:14.933511Z\",\"isEarlyAdopter\":true,\"require2FA\":false,\"requireEmailVerification\":false,\"avatar\":{\"avatarType\":\"upload\",\"avatarUuid\":\"\"},\"features\":[\"mobile-screenshots\",\"integrations-ticket-rules\",\"ondemand-budgets\",\"dashboards-template\",\"metric-alert-chartcuterie\",\"reprocessing-v2\",\"filters-and-sampling\",\"grouping-stacktrace-ui\",\"discover-frontend-use-events-endpoint\",\"grouping-title-ui\",\"app-store-connect-multiple\",\"crash-rate-alerts\",\"org-subdomains\",\"performance-dry-run-mep\",\"mobile-app\",\"grouping-tree-ui\",\"custom-event-title\",\"widget-library\",\"advanced-search\",\"auto-start-free-trial\",\"global-views\",\"integrations-chat-unfurl\",\"release-health-return-metrics\",\"integrations-stacktrace-link\",\"dashboards-basic\",\"minute-resolution-sessions\",\"discover-basic\",\"capture-lead\",\"alert-crash-free-metrics\",\"data-forwarding\",\"custom-symbol-sources\",\"sso-saml2\",\"integrations-alert-rule\",\"alert-wizard-v3\",\"invite-members\",\"team-insights\",\"integrations-issue-sync\",\"images-loaded-v2\",\"duplicate-alert-rule\",\"performance-autogroup-sibling-spans\",\"monitors\",\"new-widget-builder-experience-design\",\"dashboards-edit\",\"integrations-issue-basic\",\"performance-ops-breakdown\",\"performance-suspect-spans-view\",\"unified-span-view\",\"related-events\",\"integrations-event-hooks\",\"performance-frontend-use-events-endpoint\",\"sso-basic\",\"widget-viewer-modal\",\"event-attachments\",\"symbol-sources\",\"performance-span-histogram-view\",\"new-widget-builder-experience\",\"profiling\",\"dashboards-releases\",\"metrics-extraction\",\"integrations-incident-management\",\"new-weekly-report\",\"performance-view\",\"performance-span-tree-autoscroll\",\"open-membership\",\"metric-alert-snql\",\"shared-issues\",\"integrations-codeowners\",\"change-alerts\",\"dashboard-grid-layout\",\"baa\",\"discover-query\",\"alert-filters\",\"incidents\",\"relay\"]},\"keys\":[{\"id\":\"key-2\",\"name\":\"Default\",\"label\":\"Default\",\"public\":\"public\",\"secret\":\"secret\",\"projectId\":6789,\"isActive\":true,\"rateLimit\":null,\"dsn\":{\"secret\":\"dsn-secret\",\"public\":\"dsn-public\",\"csp\":\"\",\"security\":\"\",\"minidump\":\"\",\"unreal\":\"\",\"cdn\":\"\"},\"browserSdkVersion\":\"7.x\",\"browserSdk\":{\"choices\":[[\"latest\",\"latest\"],[\"7.x\",\"7.x\"],[\"6.x\",\"6.x\"],[\"5.x\",\"5.x\"],[\"4.x\",\"4.x\"]]},\"dateCreated\":\"2022-06-16T16:34:36.845197Z\"}]}]}";
var parsed = sut.DeserializeJson<WizardStep2Response>(json);

Assert.NotNull(parsed.apiKeys);
Assert.AreEqual("api-key-token", parsed.apiKeys!.token);

Assert.NotNull(parsed.projects);
Assert.AreEqual(2, parsed.projects.Count);
var project = parsed.projects[0];
Assert.AreEqual("project-slug", project.slug);
Assert.AreEqual("personal", project.name);
Assert.AreEqual("flutter", project.platform);

Assert.IsFalse(project.IsUnity);
project.platform = "unity";
Assert.IsTrue(project.IsUnity);

Assert.NotNull(project.organization);
var org = project.organization!;
Assert.AreEqual("organization-1", org.name);
Assert.AreEqual("org-slug", org.slug);

Assert.NotNull(project.keys);
Assert.AreEqual(1, project.keys!.Count);
var key = project.keys[0];
Assert.NotNull(key.dsn);
Assert.AreEqual("dsn-public", key.dsn!.@public);
}
}
}

0 comments on commit c9aa74f

Please sign in to comment.