Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 230 additions & 0 deletions src/App/Lab/CommitHashView.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
@inject ILocalStorageService LocalStorage
@inject WorkerController Worker
@inject HttpClient Client

@if (alternativeCommit is { } alt)
{
<CommitHashView Enabled="Enabled" Commit="alt.Result" SaveKey="@("Alternative" + SaveKey)" />
@(" / ")
}

<a href="@Commit.Url" target="_blank"
title="@GetTitle()">@Commit.ShortHash</a>
@if (TopLevelDateAndVersion && commitInfo != null)
{
@(" ")

<span title="@commitInfo.Message">(@commitInfo.Date.ToString(dateFormat))</span>
@if (commitInfo.Version != null)
{
@(", ")

<a title="Click to see release notes"
href="@Commit.ReleasesUrl"
target="_blank">@commitInfo.Version</a>
}
}

@code {
private const string dateFormat = "MMM d, yyyy, HH:mm";
private readonly AsyncLock loading = new();
private CommitInfo? commitInfo;
private AlternativeCommitInfo? alternativeCommit;

private readonly struct AlternativeCommitInfo
{
public required CommitLink Result { get; init; }
public required CommitLink For { get; init; }
}

[Parameter, EditorRequired]
public bool Enabled { get; init; }

[Parameter, EditorRequired]
public CommitLink Commit { get; init; }

[Parameter, EditorRequired]
public string SaveKey { get; init; }

[Parameter]
public string? AlternativeCommitHash { get; init; }

[Parameter]
public string? AlternativeRepoUrl { get; init; }

[Parameter]
public bool TopLevelDateAndVersion { get; init; }

public string? GetTitle()
{
if (commitInfo is null || TopLevelDateAndVersion)
{
return commitInfo?.Message;
}

string versionSuffix = commitInfo.Version != null ? $", {commitInfo.Version}" : string.Empty;

return $"{commitInfo.Message} ({commitInfo.Date.ToString(dateFormat)}{versionSuffix})";
}

private sealed class CommitInfo
{
public required string RepoUrl { get; init; }
public required string Hash { get; init; }
public required DateTimeOffset Date { get; init; }
public required string Message { get; init; }
public required string? Version { get; init; }
}

protected override async Task OnParametersSetAsync()
{
if (!Enabled)
{
return;
}

using var _ = await loading.LockAsync();

var tasks = new List<Task>();

// Load commit info from storage (cached) or the web unless already loaded.
if (!Matches(Commit, commitInfo))
{
if (await tryLoadCachedAsync() is { } cached &&
Matches(Commit, cached))
{
commitInfo = cached;
}
else
{
tasks.Add(LoadCommitInfoAsync());
}
}

// Load alternative commit info.
tasks.Add(LoadAlternativeInfoAsync());

await Task.WhenAll(tasks);

async Task<CommitInfo?> tryLoadCachedAsync()
{
try
{
return await LocalStorage.GetItemAsync<CommitInfo>(SaveKey);
}
catch (JsonException)
{
return null;
}
}
}

private async Task LoadCommitInfoAsync()
{
commitInfo = await GetCommitInfoAsync();
StateHasChanged();
if (commitInfo != null)
{
await LocalStorage.SetItemAsync(SaveKey, commitInfo);
}
}

private async Task<CommitInfo?> GetCommitInfoAsync()
{
if (Commit.OwnerAndName is not { } ownerAndName)
{
return null;
}

GitHubCommitResponse? response;
try
{
response = await Client.GetFromJsonAsync(
$"https://api.github.com/repos/{ownerAndName}/commits/{Commit.Hash}",
SettingsJsonContext.Default.GitHubCommitResponse);
if (response is null)
{
return null;
}
}
catch (HttpRequestException)
{
return null;
}

// Find version tag corresponding to the commit.
// Inspired by https://stackoverflow.com/a/69590375.
string? version = null;
try
{
var request = new HttpRequestMessage(
HttpMethod.Get,
$"https://github.com/{ownerAndName}/branch_commits/{Commit.Hash}".WithCorsProxy());
request.Headers.Accept.Add(new("application/json"));
var tagsResponse = await Client.SendAsync(request);
var tagsObj = await tagsResponse.Content.ReadFromJsonAsync(SettingsJsonContext.Default.GitHubBranchCommitsResponse);
version = tagsObj?.Tags is [{ Length: > 0 } tag] ? tag : null;
}
catch (HttpRequestException) { }
catch (JsonException) { }

return new CommitInfo
{
RepoUrl = Commit.RepoUrl,
Hash = Commit.Hash,
Date = response.Commit.Author.Date,
Message = response.Commit.Message.GetFirstLine(),
Version = version,
};
}

private async Task LoadAlternativeInfoAsync()
{
var saveKey = SaveKey + "_AlternativeCommitLink";
if (alternativeCommit is null)
{
alternativeCommit = await LocalStorage.GetItemAsync<AlternativeCommitInfo?>(saveKey);
}

if ((AlternativeRepoUrl is null || AlternativeRepoUrl == Commit.RepoUrl) &&
(AlternativeCommitHash is null || AlternativeCommitHash == Commit.Hash))
{
alternativeCommit = null;
}
else if (alternativeCommit is { } alt &&
alt.Result.RepoUrl == AlternativeRepoUrl &&
alt.Result.Hash == AlternativeCommitHash &&
Matches(alt.For, commitInfo))
{
// Already loaded.
return;
}
else if ((AlternativeCommitHash ?? await Worker.TryGetSubRepoCommitHashAsync(Commit.Hash, AlternativeRepoUrl!)) is not { } altCommitHash)
{
alternativeCommit = null;
}
else
{
alternativeCommit = new()
{
Result = new CommitLink
{
RepoUrl = AlternativeRepoUrl ?? Commit.RepoUrl,
Hash = altCommitHash,
},
For = Commit,
};
}

StateHasChanged();

await LocalStorage.SetItemAsync(saveKey, alternativeCommit);
}

private static bool Matches(CommitLink commit, CommitInfo? info)
{
return info != null &&
info.Hash == commit.Hash &&
info.RepoUrl == commit.RepoUrl;
}
}
2 changes: 1 addition & 1 deletion src/App/Lab/InputOutputCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public async Task StoreAsync(SavedState state, CompiledAssembly output)
return null;
}

if (await response.Content.ReadFromJsonAsync<CompiledAssembly>(WorkerJsonContext.Default.Options) is not { } output)
if (await response.Content.ReadFromJsonAsync(WorkerJsonContext.Default.CompiledAssembly) is not { } output)
{
logger.LogError("No output.");
return null;
Expand Down
Loading