diff --git a/Flawless.Client/Service/Remote_Generated.cs b/Flawless.Client/Service/Remote_Generated.cs index 6fc0552..9d73348 100644 --- a/Flawless.Client/Service/Remote_Generated.cs +++ b/Flawless.Client/Service/Remote_Generated.cs @@ -205,6 +205,12 @@ namespace Flawless.Client.Remote [Get("/api/repo/{userName}/{repositoryName}/get_users")] Task GetUsers(string userName, string repositoryName, CancellationToken cancellationToken = default); + /// OK + /// Thrown when the request returns a non-success status code. + [Headers("Accept: text/plain, application/json, text/json")] + [Get("/api/repo/{userName}/{repositoryName}/stats")] + Task Stats(string userName, string repositoryName, CancellationToken cancellationToken = default); + /// A that completes when the request is finished. /// Thrown when the request returns a non-success status code. [Headers("Content-Type: application/json")] @@ -406,6 +412,30 @@ namespace Flawless.Client.Remote } + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.4.0.0 (NJsonSchema v11.3.2.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class CommitByDayDetail + { + + [JsonPropertyName("day")] + public System.DateTimeOffset Day { get; set; } + + [JsonPropertyName("count")] + public int Count { get; set; } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.4.0.0 (NJsonSchema v11.3.2.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class CommitByPersonDetail + { + + [JsonPropertyName("username")] + public string Username { get; set; } + + [JsonPropertyName("count")] + public int Count { get; set; } + + } + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.4.0.0 (NJsonSchema v11.3.2.0 (Newtonsoft.Json v13.0.0.0))")] public partial class CommitManifest { @@ -451,6 +481,18 @@ namespace Flawless.Client.Remote } + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.4.0.0 (NJsonSchema v11.3.2.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class DepotDetail + { + + [JsonPropertyName("depotName")] + public string DepotName { get; set; } + + [JsonPropertyName("depotSize")] + public long DepotSize { get; set; } + + } + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.4.0.0 (NJsonSchema v11.3.2.0 (Newtonsoft.Json v13.0.0.0))")] public partial class DepotLabel { @@ -644,6 +686,21 @@ namespace Flawless.Client.Remote } + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.4.0.0 (NJsonSchema v11.3.2.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class RepoStatisticResponse + { + + [JsonPropertyName("depots")] + public ICollection Depots { get; set; } + + [JsonPropertyName("commitByPerson")] + public ICollection CommitByPerson { get; set; } + + [JsonPropertyName("commitByDay")] + public ICollection CommitByDay { get; set; } + + } + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.4.0.0 (NJsonSchema v11.3.2.0 (Newtonsoft.Json v13.0.0.0))")] public partial class RepoUserRole { diff --git a/Flawless.Client/Service/RepositoryService.cs b/Flawless.Client/Service/RepositoryService.cs index ea8b266..26127d3 100644 --- a/Flawless.Client/Service/RepositoryService.cs +++ b/Flawless.Client/Service/RepositoryService.cs @@ -1157,7 +1157,6 @@ public class RepositoryService : BaseService catch (Exception e) { UIHelper.NotifyError(e); - Console.WriteLine(e); return null; } } @@ -1182,7 +1181,6 @@ public class RepositoryService : BaseService catch (Exception e) { UIHelper.NotifyError(e); - Console.WriteLine(e); return null; } } diff --git a/Flawless.Client/ViewModels/RepositoryViewModel.cs b/Flawless.Client/ViewModels/RepositoryViewModel.cs index 4328e93..c2e8d9f 100644 --- a/Flawless.Client/ViewModels/RepositoryViewModel.cs +++ b/Flawless.Client/ViewModels/RepositoryViewModel.cs @@ -16,6 +16,10 @@ using Flawless.Client.Remote; using Flawless.Client.Service; using Flawless.Client.ViewModels.ModalBox; using Flawless.Client.Views.ModalBox; +using LiveChartsCore; +using LiveChartsCore.Defaults; +using LiveChartsCore.Kernel.Sketches; +using LiveChartsCore.SkiaSharpView; using ReactiveUI; using ReactiveUI.SourceGenerators; using Ursa.Controls; @@ -123,6 +127,13 @@ public class CommitTransitNode public partial class RepositoryViewModel : RoutableViewModelBase { + public class DepotStatsInfo + { + public string Id { get; set; } + + public long Size { get; set; } + } + public RepositoryModel Repository { get; } public RepositoryLocalDatabaseModel LocalDatabase { get; } @@ -133,18 +144,28 @@ public partial class RepositoryViewModel : RoutableViewModelBase public FlatTreeDataGridSource Commits { get; } + public FlatTreeDataGridSource Depots { get; } + public ObservableCollectionExtended LocalChangeSetRaw { get; } = new(); public ObservableCollectionExtended CurrentCommitFileTreeRaw { get; } = new(); public ObservableCollectionExtended CommitsRaw { get; } = new(); + + public ObservableCollection DepotsStats { get; } = new(); public UserModel User { get; } [Reactive] private bool _autoDetectChanges = true; [Reactive] private bool _isOwnerRole, _isDeveloperRole, _isReporterRole, _isGuestRole, _showWebHook; - + + [Reactive] private ISeries[] _byDay = [new ColumnSeries()]; + + public ICartesianAxis[] XAxesByDay { get; set; } = [ + new DateTimeAxis(TimeSpan.FromDays(1), date => date.ToString("MMMM dd")) + ]; + private string _wsRoot; public RepositoryViewModel(RepositoryModel repo, IScreen hostScreen) : base(hostScreen) @@ -234,6 +255,20 @@ public partial class RepositoryViewModel : RoutableViewModelBase } }; + Depots = new FlatTreeDataGridSource(DepotsStats) + { + Columns = + { + new TextColumn( + "Id", + n => n.Id), + + new TextColumn( + "Size", + n => PathUtility.ConvertBytesToBestDisplay(n.Size)), + } + }; + _ = StartupTasksAsync(); } @@ -242,6 +277,7 @@ public partial class RepositoryViewModel : RoutableViewModelBase { await RefreshRepositoryRoleInfoAsyncCommand.Execute(); await RefreshRepositoryIssuesAsyncCommand.Execute(); + await RefreshStatisticDataCommand.Execute(); await RefreshWebhooksCommand.Execute(); await DetectLocalChangesAsyncCommand.Execute(); await RendererFileTreeAsync(); @@ -745,4 +781,37 @@ public partial class RepositoryViewModel : RoutableViewModelBase foreach (var n in LocalChangeSetRaw) n.Included = false; } + + [ReactiveCommand] + private async Task RefreshStatisticData() + { + try + { + var api = Api.C; + if (api.RequireRefreshToken() && !(await api.TryRefreshTokenAsync())) + { + api.ClearGateway(); + return; + } + + var rsp = await api.Gateway.Stats(Repository.OwnerName, Repository.Name); + + DepotsStats.Clear(); + foreach (var dp in rsp.Depots) + DepotsStats.Add(new DepotStatsInfo{ Id = dp.DepotName, Size = dp.DepotSize}); + + ByDay = new[] + { + new ColumnSeries + { + Values = rsp.CommitByDay.Select(k => new DateTimePoint(k.Day.LocalDateTime, k.Count)).ToList(), + } + }; + } + catch (Exception e) + { + UIHelper.NotifyError(e); + return; + } + } } \ No newline at end of file diff --git a/Flawless.Client/ViewModels/SettingViewModel.cs b/Flawless.Client/ViewModels/SettingViewModel.cs index 0faef80..cd83c2c 100644 --- a/Flawless.Client/ViewModels/SettingViewModel.cs +++ b/Flawless.Client/ViewModels/SettingViewModel.cs @@ -13,6 +13,7 @@ using Flawless.Client.Remote; using Flawless.Client.Service; using Flawless.Client.ViewModels.ModalBox; using Flawless.Client.Views.ModalBox; +using LiveChartsCore; using ReactiveUI; using ReactiveUI.SourceGenerators; using Refit; @@ -82,14 +83,6 @@ public partial class SettingViewModel : RoutableViewModelBase { UIHelper.NotifyError(ex); } - - try - { - } - catch (Exception ex) - { - UIHelper.NotifyError(ex); - } } private async Task LoadClientSettingsAsync() diff --git a/Flawless.Client/Views/RepositoryPage/RepoSettingPageView.axaml b/Flawless.Client/Views/RepositoryPage/RepoSettingPageView.axaml index b9f08f1..c3905eb 100644 --- a/Flawless.Client/Views/RepositoryPage/RepoSettingPageView.axaml +++ b/Flawless.Client/Views/RepositoryPage/RepoSettingPageView.axaml @@ -4,6 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:u="https://irihi.tech/ursa" xmlns:vm="using:Flawless.Client.ViewModels" + xmlns:lvc="using:LiveChartsCore.SkiaSharpView.Avalonia" xmlns:views="clr-namespace:Flawless.Client.Views" x:DataType="vm:RepositoryViewModel" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" @@ -95,10 +96,23 @@ - - - - + + + + + + + + + + + + + + + + diff --git a/Flawless.Communication/Response/RepoStatisticResponse.cs b/Flawless.Communication/Response/RepoStatisticResponse.cs new file mode 100644 index 0000000..e7162f5 --- /dev/null +++ b/Flawless.Communication/Response/RepoStatisticResponse.cs @@ -0,0 +1,31 @@ +namespace Flawless.Communication.Response; + +public struct RepoStatisticResponse +{ + public struct DepotDetail + { + public string DepotName { get; set; } + + public long DepotSize { get; set; } + } + + public struct CommitByPersonDetail + { + public string Username { get; set; } + + public int Count { get; set; } + } + + public struct CommitByDayDetail + { + public DateTime Day { get; set; } + + public int Count { get; set; } + } + + public DepotDetail[] Depots { get; set; } + + public CommitByPersonDetail[] CommitByPerson { get; set; } + + public CommitByDayDetail[] CommitByDay { get; set; } +} \ No newline at end of file diff --git a/Flawless.Server/Controllers/RepositoryInnieController.cs b/Flawless.Server/Controllers/RepositoryInnieController.cs index 6e22d0b..3919eea 100644 --- a/Flawless.Server/Controllers/RepositoryInnieController.cs +++ b/Flawless.Server/Controllers/RepositoryInnieController.cs @@ -174,6 +174,45 @@ public class RepositoryInnieController( return rp; } + + [HttpGet("stats")] + public async Task> GetStatisticAsync(string userName, string repositoryName) + { + var user = (await userManager.GetUserAsync(HttpContext.User))!; + var grantIssue = await ValidateRepositoryAsync(userName, repositoryName, user, RepositoryRole.Owner); + if (grantIssue is not Repository rp) return (ActionResult) grantIssue; + + + var response = new RepoStatisticResponse(); + + // 获取Depot大小统计 + response.Depots = rp.Depots + .Select(d => new RepoStatisticResponse.DepotDetail + { + DepotName = d.DepotId.ToString(), + DepotSize = d.Length + }).ToArray(); + + // 获取提交者提交数量统计 + response.CommitByPerson = rp.Commits + .GroupBy(c => c.Author.UserName) + .Select(g => new RepoStatisticResponse.CommitByPersonDetail + { + Username = g.Key!, + Count = g.Count() + }).ToArray(); + + // 获取每日提交数量统计 + response.CommitByDay = rp.Commits + .GroupBy(c => c.CommittedOn) + .Select(g => new RepoStatisticResponse.CommitByDayDetail + { + Day = g.Key, + Count = g.Count() + }).ToArray(); + + return Ok(response); + } [HttpPost("webhooks/create")] public async Task CreateWebhook(