feat(usage): open the TokenTracker dashboard from the usage monitor
The modal deliberately shows only a slice of the analytics; this hands off to TokenTracker's own local dashboard for the rest. The worker starts `tokentracker serve` on demand and returns the URL, the UI opens the browser. Three things the spawn has to get right: port 7680 is not free on Windows (Delivery Optimization holds [::]:7680) and serve does not fall back, so we scan 7680-7689 with a dual-stack bind probe; --no-open because the CLI would open the browser before the server answers; and the child is a cmd.exe shim, so shutdown kills the process tree. --no-sync keeps our no-cloud-sync rule.
This commit is contained in:
@@ -194,6 +194,10 @@ public abstract class StubWorkerClient : IWorkerClient
|
||||
public virtual Task<TokenTrackerStatusDto?> RefreshTokenTrackerAsync() => GetTokenTrackerStatusAsync();
|
||||
|
||||
public virtual Task<TokenTrackerStatusDto?> InstallTokenTrackerAsync() => GetTokenTrackerStatusAsync();
|
||||
|
||||
public virtual Task<TokenTrackerDashboardDto?> OpenTokenTrackerDashboardAsync() =>
|
||||
Task.FromResult<TokenTrackerDashboardDto?>(new TokenTrackerDashboardDto(true, "http://127.0.0.1:7681/", null));
|
||||
|
||||
public void RaiseUsageUpdated(UsageSnapshotDto snapshot) => UsageUpdatedEvent?.Invoke(snapshot);
|
||||
|
||||
protected void RaisePropertyChanged(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
||||
|
||||
@@ -180,6 +180,9 @@ sealed class FakeWorkerClient : IWorkerClient
|
||||
public Task<TokenTrackerStatusDto?> RefreshTokenTrackerAsync() => GetTokenTrackerStatusAsync();
|
||||
|
||||
public Task<TokenTrackerStatusDto?> InstallTokenTrackerAsync() => GetTokenTrackerStatusAsync();
|
||||
|
||||
public Task<TokenTrackerDashboardDto?> OpenTokenTrackerDashboardAsync() =>
|
||||
Task.FromResult<TokenTrackerDashboardDto?>(new TokenTrackerDashboardDto(false, null, "not configured"));
|
||||
}
|
||||
|
||||
// ── Helper to build VM with pre-seeded Items ──────────────────────────────────
|
||||
|
||||
@@ -38,6 +38,15 @@ public sealed class FakeTokenTrackerClient : ITokenTrackerClient
|
||||
return ExportResult;
|
||||
}
|
||||
|
||||
public TokenTrackerDashboard DashboardResult { get; set; } = new(true, "http://127.0.0.1:7680/", null);
|
||||
public int DashboardCalls;
|
||||
|
||||
public Task<TokenTrackerDashboard> StartDashboardAsync(CancellationToken ct = default)
|
||||
{
|
||||
Interlocked.Increment(ref DashboardCalls);
|
||||
return Task.FromResult(DashboardResult);
|
||||
}
|
||||
|
||||
public Task<TokenTrackerRunResult> InstallAsync(IProgress<string>? output = null, CancellationToken ct = default)
|
||||
{
|
||||
Interlocked.Increment(ref InstallCalls);
|
||||
|
||||
@@ -39,4 +39,12 @@ public sealed class TokenTrackerArgsTests
|
||||
{
|
||||
Assert.Equal(new[] { "i", "-g", "tokentracker-cli" }, TokenTrackerArgs.Install());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Serve_PinsThePortAndOptsOutOfBrowserAndCloudSync()
|
||||
{
|
||||
Assert.Equal(
|
||||
new[] { "serve", "--port", "7681", "--no-open", "--no-sync" },
|
||||
TokenTrackerArgs.Serve(7681));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,32 @@ public sealed class TokenTrackerServiceTests
|
||||
return (new TokenTrackerService(client, new TokenTrackerState(), () => clock), client);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OpenDashboardAsync_ReturnsTheUrlWhenTheCliIsInstalled()
|
||||
{
|
||||
var (service, client) = Build();
|
||||
|
||||
var dashboard = await service.OpenDashboardAsync();
|
||||
|
||||
Assert.True(dashboard.Ok);
|
||||
Assert.Equal("http://127.0.0.1:7680/", dashboard.Url);
|
||||
Assert.Equal(1, client.DashboardCalls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OpenDashboardAsync_MissingCli_ReportsTheProbeErrorWithoutSpawning()
|
||||
{
|
||||
var (service, client) = Build();
|
||||
client.Probe = new TokenTrackerProbe(false, null, true, "v22.1.0", "'tokentracker' not found on PATH.");
|
||||
|
||||
var dashboard = await service.OpenDashboardAsync();
|
||||
|
||||
Assert.False(dashboard.Ok);
|
||||
Assert.Null(dashboard.Url);
|
||||
Assert.Equal("'tokentracker' not found on PATH.", dashboard.Error);
|
||||
Assert.Equal(0, client.DashboardCalls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RefreshAsync_StoresParsedExport()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user