diff --git a/.github/workflows/dotnet-cd.yml b/.github/workflows/dotnet-cd.yml index c1a6fb3..68379b8 100644 --- a/.github/workflows/dotnet-cd.yml +++ b/.github/workflows/dotnet-cd.yml @@ -10,7 +10,7 @@ permissions: jobs: build: - runs-on: windows-latest + runs-on: self-hosted steps: - uses: actions/checkout@v4 @@ -29,31 +29,14 @@ jobs: - name: dotnet publish run: dotnet publish "rubberduckvba.Server\rubberduckvba.Server.csproj" --configuration Release --output ${{env.DOTNET_ROOT}}\pub - - name: Upload artifact for deployment job - uses: actions/upload-artifact@v4 - with: - name: webapi - path: ${{env.DOTNET_ROOT}}/pub - deploy: - environment: - name: AZ-Test - url: https://test.rubberduckvba.com - permissions: - contents: none - runs-on: windows-latest + runs-on: self-hosted needs: build steps: - - name: Download artifact from build job - uses: actions/download-artifact@v4 - with: - name: webapi - name: Deploy to IIS - id: deploy-to-iis - uses: ChristopheLav/iis-deploy@v1 - with: - website-name: 'api' - msdeploy-service-url: ${{ secrets.MSDEPLOY_URL }} - msdeploy-username: ${{ secrets.MSDEPLOY_USERNAME }} - msdeploy-password: ${{ secrets.MSDEPLOY_PASSWORD }} - source-path: ${{ github.workspace }}\website\publish + run: | + stop-webapppool rubberduckvba + stop-iissite -Name api -Confirm: $false + Copy-Item ${{env.DOTNET_ROOT}}\pub\* C:/inetpub/wwwroot -Recurse -Force + start-webapppool rubberduckvba + start-iissite api diff --git a/.gitignore b/.gitignore index 1a4c036..933f381 100644 --- a/.gitignore +++ b/.gitignore @@ -361,4 +361,5 @@ MigrationBackup/ .ionide/ # Fody - auto-generated XML schema -FodyWeavers.xsd \ No newline at end of file +FodyWeavers.xsd +/rubberduckvba.Server/appsettings.json diff --git a/README.md b/README.md index 7c80217..8d2c9e3 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ The task can be launched manually from the back-end API with an authenticated PO ## Update XmlDoc content Similarly, this job creates a TPL DataFlow pipeline that can be documented as follows: -![diagram](https://github.com/user-attachments/assets/519e1d61-514f-4186-a694-4db69d7e8da9) +![diagram](https://github.com/user-attachments/assets/dac3020c-9335-4573-b1ef-f4984cdf56b4) This pipeline hits GitHub to download the code inspections' default configuration from the **rubberduck-vba/Rubberduck** repository, fetches the current-latest tags from the database to compare against the current-latest tags from GitHub, then downloads the .xml assets and parses them into "feature items" and proceeds to merge the contents: - Items that exist in **next** but not **main** are considered/marked as NEW diff --git a/RubberduckServices/SyntaxHighlighterService.cs b/RubberduckServices/SyntaxHighlighterService.cs index 7f6a9e8..d49d454 100644 --- a/RubberduckServices/SyntaxHighlighterService.cs +++ b/RubberduckServices/SyntaxHighlighterService.cs @@ -18,7 +18,7 @@ public interface ISyntaxHighlighterService /// /// A fragment of VBA code that can be parsed by Rubberduck. /// The provided code, syntax-formatted. - Task FormatAsync(string code); + string Format(string code); } public class SyntaxHighlighterService : ISyntaxHighlighterService @@ -52,7 +52,7 @@ public SyntaxHighlighterService( _attributeValueClass = cssAttributeValues; } - public async Task FormatAsync(string code) + public string Format(string code) { var indenter = new SimpleIndenter(); @@ -96,10 +96,10 @@ public async Task FormatAsync(string code) var lines = builder.ToString().Split("\n").ToArray(); var indent = lines.LastOrDefault()?.TakeWhile(char.IsWhiteSpace)?.Count() ?? 0; var formattedLines = from line in lines - let trimmed = line.Substring(indent) + let trimmed = line[indent..] select FormatIndents(trimmed); - return await Task.FromResult(string.Join("
", formattedLines)); + return string.Join("
", formattedLines); } private void FormatTokens(StringBuilder builder, ITokenStream tokens, IntervalListener[] listeners) @@ -119,13 +119,13 @@ private void FormatTokens(StringBuilder builder, ITokenStream tokens, IntervalLi builder.Append($"{tokens.GetText(listener.Interval)}"); i = listener.Interval.b; } - else if (listener is NewLineListener) - { - if (token.Type == VBAParser.NEWLINE) - { - builder.Append(Environment.NewLine); - } - } + //else if (listener is NewLineListener) + //{ + // if (token.Type == VBAParser.NEWLINE) + // { + // builder.Append(Environment.NewLine); + // } + //} else { if (TokenKinds.StringLiterals.Contains(token.Type)) @@ -148,7 +148,7 @@ private void FormatTokens(StringBuilder builder, ITokenStream tokens, IntervalLi } } - private static ITokenStream Tokenize(string code) + private static CommonTokenStream Tokenize(string code) { AntlrInputStream input; using (var reader = new StringReader(code)) @@ -165,7 +165,7 @@ private static string FormatIndents(string line) var indent = line.TakeWhile(char.IsWhiteSpace).Count(); if (indent > 0) { - formatted = line.Substring(0, indent).Replace(" ", " ") + line.Substring(indent); + formatted = string.Concat(line[..indent].Replace(" ", " "), line.AsSpan(indent)); } return formatted; } diff --git a/rubberduckvba.Server/Api/Admin/AdminController.cs b/rubberduckvba.Server/Api/Admin/AdminController.cs index 9e768f2..1c56ba9 100644 --- a/rubberduckvba.Server/Api/Admin/AdminController.cs +++ b/rubberduckvba.Server/Api/Admin/AdminController.cs @@ -2,13 +2,14 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; -using rubberduckvba.com.Server.ContentSynchronization; -using rubberduckvba.com.Server.Hangfire; +using rubberduckvba.Server; +using rubberduckvba.Server.ContentSynchronization; +using rubberduckvba.Server.Hangfire; +using rubberduckvba.Server.Services; -namespace rubberduckvba.com.Server.Api.Admin; +namespace rubberduckvba.Server.Api.Admin; -[Authorize("github")] [ApiController] public class AdminController(ConfigurationOptions options, IBackgroundJobClient backgroundJob, ILogger logger) : ControllerBase { @@ -20,7 +21,7 @@ public class AdminController(ConfigurationOptions options, IBackgroundJobClient [HttpPost("admin/update/xmldoc")] public async ValueTask UpdateXmldocContent() { - var parameters = new XmldocSyncRequestParameters { RepositoryId = Services.RepositoryId.Rubberduck, RequestId = Guid.NewGuid() }; + var parameters = new XmldocSyncRequestParameters { RepositoryId = RepositoryId.Rubberduck, RequestId = Guid.NewGuid() }; var jobId = backgroundJob.Enqueue(HangfireConstants.ManualQueueName, () => QueuedUpdateOrchestrator.UpdateXmldocContent(parameters, null!)); logger.LogInformation("JobId {jobId} was enqueued (queue: {queueName}) for xmldoc sync request {requestId}", jobId, HangfireConstants.ManualQueueName, parameters.RequestId); @@ -35,14 +36,13 @@ public async ValueTask UpdateXmldocContent() [HttpPost("admin/update/tags")] public async ValueTask UpdateTagMetadata() { - var parameters = new TagSyncRequestParameters { RepositoryId = Services.RepositoryId.Rubberduck, RequestId = Guid.NewGuid() }; + var parameters = new TagSyncRequestParameters { RepositoryId = RepositoryId.Rubberduck, RequestId = Guid.NewGuid() }; var jobId = backgroundJob.Enqueue(HangfireConstants.ManualQueueName, () => QueuedUpdateOrchestrator.UpdateInstallerDownloadStats(parameters, null!)); logger.LogInformation("JobId {jobId} was enqueued (queue: {queueName}) for tag sync request {requestId}", jobId, HangfireConstants.ManualQueueName, parameters.RequestId); return await ValueTask.FromResult(Ok(jobId)); } - [Authorize("github")] [HttpGet("admin/config/current")] public async ValueTask Config() { @@ -52,6 +52,7 @@ public async ValueTask Config() public record class ConfigurationOptions( IOptions ConnectionOptions, + IOptions GitHubOptions, IOptions HangfireOptions) { diff --git a/rubberduckvba.Server/Api/Auth/AuthController.cs b/rubberduckvba.Server/Api/Auth/AuthController.cs index 92ef276..10c8c3d 100644 --- a/rubberduckvba.Server/Api/Auth/AuthController.cs +++ b/rubberduckvba.Server/Api/Auth/AuthController.cs @@ -6,7 +6,7 @@ using System.Security.Claims; using System.Text; -namespace rubberduckvba.com.Server.Api.Auth; +namespace rubberduckvba.Server.Api.Auth; public record class UserViewModel { diff --git a/rubberduckvba.Server/Api/Auth/ClaimsPrincipalExtensions.cs b/rubberduckvba.Server/Api/Auth/ClaimsPrincipalExtensions.cs index 42d63d9..076c126 100644 --- a/rubberduckvba.Server/Api/Auth/ClaimsPrincipalExtensions.cs +++ b/rubberduckvba.Server/Api/Auth/ClaimsPrincipalExtensions.cs @@ -3,14 +3,14 @@ using System.Security.Claims; using System.Text; -namespace rubberduckvba.com.Server.Api.Auth; +namespace rubberduckvba.Server.Api.Auth; public static class ClaimsPrincipalExtensions { public static string AsJWT(this ClaimsPrincipal principal, string secret, string issuer, string audience) { var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret)); - var token = new JwtSecurityToken(issuer, audience, + var token = new JwtSecurityToken(issuer, audience, claims: principal?.Claims, notBefore: new DateTimeOffset(DateTime.UtcNow).UtcDateTime, expires: new DateTimeOffset(DateTime.UtcNow.AddMinutes(60)).UtcDateTime, diff --git a/rubberduckvba.Server/Api/Downloads/AvailableDownload.cs b/rubberduckvba.Server/Api/Downloads/AvailableDownload.cs index 5b4b5b5..abe2cc9 100644 --- a/rubberduckvba.Server/Api/Downloads/AvailableDownload.cs +++ b/rubberduckvba.Server/Api/Downloads/AvailableDownload.cs @@ -1,6 +1,6 @@ -using rubberduckvba.com.Server.Data; +using rubberduckvba.Server.Model; -namespace rubberduckvba.com.Server.Api.Downloads; +namespace rubberduckvba.Server.Api.Downloads; public record class AvailableDownload { diff --git a/rubberduckvba.Server/Api/Downloads/DownloadsController.cs b/rubberduckvba.Server/Api/Downloads/DownloadsController.cs index 0976f59..3a69fc1 100644 --- a/rubberduckvba.Server/Api/Downloads/DownloadsController.cs +++ b/rubberduckvba.Server/Api/Downloads/DownloadsController.cs @@ -1,9 +1,9 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using rubberduckvba.com.Server.Services; +using rubberduckvba.Server.Services; using System.Collections.Immutable; -namespace rubberduckvba.com.Server.Api.Downloads; +namespace rubberduckvba.Server.Api.Downloads; [ApiController] diff --git a/rubberduckvba.Server/Api/Features/FeatureEditViewModel.cs b/rubberduckvba.Server/Api/Features/FeatureEditViewModel.cs index c6b0173..38707d5 100644 --- a/rubberduckvba.Server/Api/Features/FeatureEditViewModel.cs +++ b/rubberduckvba.Server/Api/Features/FeatureEditViewModel.cs @@ -1,7 +1,7 @@ -using rubberduckvba.com.Server.Data; -using rubberduckvba.com.Server.Services; +using rubberduckvba.Server.Model; +using rubberduckvba.Server.Services; -namespace rubberduckvba.com.Server.Api.Features; +namespace rubberduckvba.Server.Api.Features; public class FeatureEditViewModel { @@ -30,7 +30,7 @@ public Feature ToFeature() { Id = Id ?? default, ParentId = ParentId, - RepositoryId = (int)RepositoryId, + RepositoryId = RepositoryId, Name = Name, Title = Title, ShortDescription = ShortDescription, @@ -44,8 +44,8 @@ public FeatureEditViewModel(Feature model, FeatureOptionViewModel[] features, Re { Id = model.Id; ParentId = model.ParentId; - RepositoryId = (RepositoryId)model.RepositoryId; - + RepositoryId = model.RepositoryId; + Name = model.Name; Title = model.Title; ShortDescription = model.ShortDescription; diff --git a/rubberduckvba.Server/Api/Features/FeatureOptionViewModel.cs b/rubberduckvba.Server/Api/Features/FeatureOptionViewModel.cs index 968ef86..99ebc75 100644 --- a/rubberduckvba.Server/Api/Features/FeatureOptionViewModel.cs +++ b/rubberduckvba.Server/Api/Features/FeatureOptionViewModel.cs @@ -1,4 +1,4 @@ -namespace rubberduckvba.com.Server.Api.Features; +namespace rubberduckvba.Server.Api.Features; public class FeatureOptionViewModel { diff --git a/rubberduckvba.Server/Api/Features/FeatureViewModel.cs b/rubberduckvba.Server/Api/Features/FeatureViewModel.cs index 8a51a6c..12b9220 100644 --- a/rubberduckvba.Server/Api/Features/FeatureViewModel.cs +++ b/rubberduckvba.Server/Api/Features/FeatureViewModel.cs @@ -1,7 +1,6 @@ -using rubberduckvba.com.Server.Data; -using System.Text.Json; +using rubberduckvba.Server.Model; -namespace rubberduckvba.com.Server.Api.Features; +namespace rubberduckvba.Server.Api.Features; public class FeatureViewModel { @@ -21,13 +20,8 @@ public FeatureViewModel(Feature model) if (model is FeatureGraph graph) { - FeatureId = graph.ParentId; - FeatureName = graph.ParentName; - FeatureTitle = graph.ParentTitle; - - Features = graph.Features.Select(e => new FeatureViewModel(e)).ToArray(); - Items = graph.Items.Select(e => e with { FeatureId = graph.ParentId!.Value, FeatureName = graph.ParentName, FeatureTitle = graph.ParentTitle }).ToArray(); - Inspections = graph.Items.Select(e => JsonSerializer.Deserialize(e.Serialized)!).ToArray(); + Features = graph.Features.Select(e => new FeatureViewModel(e) { FeatureId = e.ParentId, FeatureName = graph.Name, FeatureTitle = graph.Title }).ToArray(); + Inspections = graph.Inspections.ToArray(); } } @@ -36,8 +30,8 @@ public FeatureViewModel(Feature model) public DateTime? DateUpdated { get; init; } public int? FeatureId { get; init; } - public string FeatureName { get; init; } - public string FeatureTitle { get; init; } + public string? FeatureName { get; init; } + public string? FeatureTitle { get; init; } public string Name { get; init; } public string Title { get; init; } @@ -48,8 +42,7 @@ public FeatureViewModel(Feature model) public bool HasImage { get; init; } public FeatureViewModel[] Features { get; init; } = []; - public FeatureXmlDoc[] Items { get; init; } = []; - public XmlDocInspectionInfo[] Inspections { get; init; } = []; + public Inspection[] Inspections { get; init; } = []; // InspectionViewModel[] } public class FeatureXmlDocViewModel diff --git a/rubberduckvba.Server/Api/Features/FeaturesController.cs b/rubberduckvba.Server/Api/Features/FeaturesController.cs index de60313..43f60c1 100644 --- a/rubberduckvba.Server/Api/Features/FeaturesController.cs +++ b/rubberduckvba.Server/Api/Features/FeaturesController.cs @@ -1,13 +1,12 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using rubberduckvba.com.Server.ContentSynchronization.XmlDoc; -using rubberduckvba.com.Server.Data; -using rubberduckvba.com.Server.Services; +using rubberduckvba.Server.Model; +using rubberduckvba.Server.Services; +using rubberduckvba.Server.Services.rubberduckdb; using System.ComponentModel; using System.Reflection; -using System.Text.Json; -namespace rubberduckvba.com.Server.Api.Features; +namespace rubberduckvba.Server.Api.Features; public record class MarkdownFormattingRequestViewModel { @@ -18,7 +17,7 @@ public record class MarkdownFormattingRequestViewModel [ApiController] [AllowAnonymous] -public class FeaturesController(IRubberduckDbService db, IMarkdownFormattingService md, ICacheService cache) : ControllerBase +public class FeaturesController(IRubberduckDbService db, FeatureServices features, IMarkdownFormattingService md, ICacheService cache) : ControllerBase { private static RepositoryOptionViewModel[] RepositoryOptions { get; } = Enum.GetValues().Select(e => new RepositoryOptionViewModel { Id = e, Name = e.ToString() }).ToArray(); @@ -48,6 +47,9 @@ public async Task>> Index() return Ok(model); } + private static readonly IDictionary _moduleTypeNames = typeof(ExampleModuleType).GetMembers().Where(e => e.GetCustomAttribute() != null) + .ToDictionary(member => member.Name, member => member.GetCustomAttribute()?.Description ?? member.Name); + [HttpGet("features/{name}")] [AllowAnonymous] public async Task> Info([FromRoute] string name) @@ -57,64 +59,13 @@ public async Task> Info([FromRoute] string name) // return cached; //} - var feature = await db.ResolveFeature(RepositoryId.Rubberduck, name); + var feature = features.Get(name); if (feature is null) { return NotFound(); } - var moduleTypeNames = typeof(ExampleModuleType).GetMembers().Where(e => e.GetCustomAttribute() != null).ToDictionary(member => member.Name, member => member.GetCustomAttribute()?.Description ?? member.Name); - - var model = new FeatureViewModel(feature with - { - Description = await md.FormatMarkdownDocument(feature.Description, true), - ShortDescription = feature.ShortDescription, - - ParentId = feature.Id, - ParentName = feature.Name, - ParentTitle = feature.Title, - - Features = feature.Features.Select(subFeature => subFeature with - { - Description = subFeature.Description, - ShortDescription = subFeature.ShortDescription - }).ToArray(), - - Inspections = feature.Items.Select(item => - { - var info = JsonSerializer.Deserialize(item.Serialized)!; - return new XmlDocInspectionInfo - { - Id = item.Id, - DateTimeInserted = item.DateTimeInserted, - DateTimeUpdated = item.DateTimeUpdated, - IsDiscontinued = item.IsDiscontinued, - IsHidden = item.IsHidden, - IsNew = item.IsNew, - Name = item.Name, - Title = item.Title, - SourceUrl = item.SourceUrl, - - FeatureId = feature.Id, - FeatureName = feature.Name, - FeatureTitle = feature.Title, - - Summary = info.Summary, - Reasoning = info.Reasoning, - Remarks = info.Remarks, - HostApp = info.HostApp, - References = info.References, - InspectionType = info.InspectionType, - DefaultSeverity = info.DefaultSeverity, - QuickFixes = info.QuickFixes, - Serialized = item.Serialized, - Examples = info.Examples ?? [] - }; - }).ToArray(), - - Items = feature.Items - }); - + var model = new FeatureViewModel(feature); //cache.Write(HttpContext.Request.Path, model); return Ok(model); } @@ -124,12 +75,12 @@ public async Task> Info([FromRoute] string name) public async Task Resolve([FromQuery] RepositoryId repository, [FromQuery] string name) { var graph = await db.ResolveFeature(repository, name); - var markdown = await md.FormatMarkdownDocument(graph.Description, withSyntaxHighlighting: true); + var markdown = md.FormatMarkdownDocument(graph.Description, withSyntaxHighlighting: true); return Ok(graph with { Description = markdown }); } [HttpGet("features/create")] - //[Authorize("github")] + [Authorize("github")] public async Task> Create([FromQuery] RepositoryId repository = RepositoryId.Rubberduck, [FromQuery] int? parentId = default) { var features = await GetFeatureOptions(repository); @@ -140,7 +91,7 @@ public async Task> Create([FromQuery] Reposit } [HttpPost("create")] - //[Authorize("github")] + [Authorize("github")] public async Task> Create([FromBody] FeatureEditViewModel model) { if (model.Id.HasValue || string.IsNullOrWhiteSpace(model.Name) || model.Name.Trim().Length < 3) @@ -162,7 +113,7 @@ public async Task> Create([FromBody] FeatureE } [HttpPost("features/update")] - //[Authorize("github")] + [Authorize("github")] public async Task> Update([FromBody] FeatureEditViewModel model) { if (model.Id.GetValueOrDefault() == default) @@ -183,8 +134,8 @@ public async Task> Update([FromBody] FeatureE } [HttpPost("features/markdown")] - public async Task FormatMarkdown([FromBody] MarkdownFormattingRequestViewModel model) + public IActionResult FormatMarkdown([FromBody] MarkdownFormattingRequestViewModel model) { - return Ok(await md.FormatMarkdownDocument(model.MarkdownContent, model.WithVbeCodeBlocks)); + return Ok(md.FormatMarkdownDocument(model.MarkdownContent, model.WithVbeCodeBlocks)); } } diff --git a/rubberduckvba.Server/Api/Features/RepositoryOptionViewModel.cs b/rubberduckvba.Server/Api/Features/RepositoryOptionViewModel.cs index 016fd97..906c185 100644 --- a/rubberduckvba.Server/Api/Features/RepositoryOptionViewModel.cs +++ b/rubberduckvba.Server/Api/Features/RepositoryOptionViewModel.cs @@ -1,6 +1,6 @@ -using rubberduckvba.com.Server.Services; +using rubberduckvba.Server.Services; -namespace rubberduckvba.com.Server.Api.Features; +namespace rubberduckvba.Server.Api.Features; public class RepositoryOptionViewModel { diff --git a/rubberduckvba.Server/Api/Tags/LatestTagsViewModel.cs b/rubberduckvba.Server/Api/Tags/LatestTagsViewModel.cs index 98cbe59..1438d04 100644 --- a/rubberduckvba.Server/Api/Tags/LatestTagsViewModel.cs +++ b/rubberduckvba.Server/Api/Tags/LatestTagsViewModel.cs @@ -1,6 +1,6 @@ -using rubberduckvba.com.Server.Data; +using rubberduckvba.Server.Model; -namespace rubberduckvba.com.Server.Api.Tags; +namespace rubberduckvba.Server.Api.Tags; public record struct LatestTagsViewModel { diff --git a/rubberduckvba.Server/Api/Tags/TagViewModel.cs b/rubberduckvba.Server/Api/Tags/TagViewModel.cs index 01591b5..30794c9 100644 --- a/rubberduckvba.Server/Api/Tags/TagViewModel.cs +++ b/rubberduckvba.Server/Api/Tags/TagViewModel.cs @@ -1,6 +1,6 @@ -using rubberduckvba.com.Server.Data; +using rubberduckvba.Server.Model; -namespace rubberduckvba.com.Server.Api.Tags; +namespace rubberduckvba.Server.Api.Tags; public record struct TagViewModel { diff --git a/rubberduckvba.Server/Api/Tags/TagsController.cs b/rubberduckvba.Server/Api/Tags/TagsController.cs index 37922eb..2302514 100644 --- a/rubberduckvba.Server/Api/Tags/TagsController.cs +++ b/rubberduckvba.Server/Api/Tags/TagsController.cs @@ -1,8 +1,8 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using rubberduckvba.com.Server.Services; +using rubberduckvba.Server.Services; -namespace rubberduckvba.com.Server.Api.Tags; +namespace rubberduckvba.Server.Api.Tags; [ApiController] diff --git a/rubberduckvba.Server/ContentSynchronization/InspectionDefaultConfig.cs b/rubberduckvba.Server/ContentSynchronization/InspectionDefaultConfig.cs index 4517d4b..27a21a1 100644 --- a/rubberduckvba.Server/ContentSynchronization/InspectionDefaultConfig.cs +++ b/rubberduckvba.Server/ContentSynchronization/InspectionDefaultConfig.cs @@ -1,4 +1,4 @@ -namespace rubberduckvba.com.Server.ContentSynchronization; +namespace rubberduckvba.Server.ContentSynchronization; /// /// Encapsulates the inspection type and default severity setting override for an inspection. diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/ActionBlockBase.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/ActionBlockBase.cs index e383a87..1409176 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/ActionBlockBase.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/ActionBlockBase.cs @@ -1,6 +1,7 @@ -using System.Threading.Tasks.Dataflow; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using System.Threading.Tasks.Dataflow; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; public abstract class ActionBlockBase : ExecutionDataflowBlockBase, TInput, TContext> where TContext : IPipelineContext diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/BroadcastBlockBase.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/BroadcastBlockBase.cs index 06f0e26..463368b 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/BroadcastBlockBase.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/BroadcastBlockBase.cs @@ -1,6 +1,7 @@ -using System.Threading.Tasks.Dataflow; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using System.Threading.Tasks.Dataflow; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; public abstract class BroadcastBlockBase : ExecutionDataflowBlockBase, TInput, TContext> where TContext : IPipelineContext diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/BufferBlockBase.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/BufferBlockBase.cs index 40e9dbf..31bd8ea 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/BufferBlockBase.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/BufferBlockBase.cs @@ -1,6 +1,7 @@ -using System.Threading.Tasks.Dataflow; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using System.Threading.Tasks.Dataflow; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; public abstract class BufferBlockBase : ExecutionDataflowBlockBase, TInput, TContext> where TContext : IPipelineContext diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/DataflowBlockBase.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/DataflowBlockBase.cs index 4a90b50..55f6f68 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/DataflowBlockBase.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/DataflowBlockBase.cs @@ -1,6 +1,7 @@ -using System.Threading.Tasks.Dataflow; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using System.Threading.Tasks.Dataflow; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; public interface ISectionBlock where TContext : IPipelineContext diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/DataflowJoinBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/DataflowJoinBlock.cs index 020b859..3a57c47 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/DataflowJoinBlock.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/DataflowJoinBlock.cs @@ -1,12 +1,13 @@ -using System.Threading.Tasks.Dataflow; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using System.Threading.Tasks.Dataflow; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; -public class DataflowJoinBlock : DataflowBlockBase, SyncContext>, IDisposable +public class DataflowJoinBlock : DataflowBlockBase, SyncContext>, IDisposable { private readonly ICollection _links = []; - public DataflowJoinBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger, string name) + public DataflowJoinBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger, string name) : base(parent, tokenSource, logger) { Name = name; @@ -42,6 +43,7 @@ public JoinBlock CreateBlock(GroupingDataflowBlockOptions options, ISour Block = new JoinBlock(options); LinkSources(source1, source2); + Parent.TraceBlockCompletion(Block, Name); return Block; } @@ -50,12 +52,11 @@ public JoinBlock CreateBlock(GroupingDataflowBlockOptions opti { Block = new JoinBlock(options); - var sourceBlock1 = source1.TryGetBlock() as ISourceBlock; - var sourceBlock2 = source2.TryGetBlock() as ISourceBlock; - if (sourceBlock1 != null && sourceBlock2 != null) - { - LinkSources(sourceBlock1, sourceBlock2); - } + var sourceBlock1 = source1.TryGetBlock() as ISourceBlock ?? throw new ArgumentNullException(nameof(source1)); + var sourceBlock2 = source2.TryGetBlock() as ISourceBlock ?? throw new ArgumentNullException(nameof(source2)); + + LinkSources(sourceBlock1, sourceBlock2); + Parent.TraceBlockCompletion(Block, Name); return Block; } @@ -120,6 +121,7 @@ public JoinBlock CreateBlock(GroupingDataflowBlockOptions options, I Block = new JoinBlock(options); LinkSources(source1, source2, source3); + Parent.TraceBlockCompletion(Block, Name); return Block; } @@ -128,13 +130,12 @@ public JoinBlock CreateBlock(GroupingDataflowBlockOptions { Block = new JoinBlock(options); - var sourceBlock1 = source1.TryGetBlock() as ISourceBlock; - var sourceBlock2 = source2.TryGetBlock() as ISourceBlock; - var sourceBlock3 = source3.TryGetBlock() as ISourceBlock; - if (sourceBlock1 != null && sourceBlock2 != null && sourceBlock3 != null) - { - LinkSources(sourceBlock1, sourceBlock2, sourceBlock3); - } + var sourceBlock1 = source1.TryGetBlock() as ISourceBlock ?? throw new ArgumentNullException(nameof(source1)); + var sourceBlock2 = source2.TryGetBlock() as ISourceBlock ?? throw new ArgumentNullException(nameof(source2)); + var sourceBlock3 = source3.TryGetBlock() as ISourceBlock ?? throw new ArgumentNullException(nameof(source3)); + + LinkSources(sourceBlock1, sourceBlock2, sourceBlock3); + Parent.TraceBlockCompletion(Block, Name); return Block; } diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/ExecutionDataflowBlockBase.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/ExecutionDataflowBlockBase.cs index 6e8bc81..0fc2885 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/ExecutionDataflowBlockBase.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/ExecutionDataflowBlockBase.cs @@ -1,7 +1,8 @@ -using System.Diagnostics; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using System.Diagnostics; using System.Threading.Tasks.Dataflow; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; public abstract class ExecutionDataflowBlockBase : DataflowBlockBase, IDisposable where TBlock : class, IDataflowBlock, ITargetBlock @@ -123,7 +124,7 @@ protected void LinkToSources(params ISourceBlock[] sources) if (sources != null && sources.Length > 0) { var propagate = sources.Count() == 1; - + foreach (var source in sources) { var propagateSourceCompletion = propagate @@ -164,9 +165,9 @@ protected void LinkToSources(params ISectionBlock[] sources) } else if (srcBlock != null) { - // could be e.g. ActionBlock, which can't directly source another block. - completionTasks.Add(srcBlock.Completion); - Logger.LogDebug(Context.Parameters, $"{Name} | ⚠️ Source block ({srcBlock.GetType().Name}) is not ISourceBlock<{typeof(TInput).Name}>. If this is accidental, pipeline may not complete."); + //completionTasks.Add(srcBlock.Completion); + Logger.LogWarning(Context.Parameters, $"{Name} | ⚠️ Source block ({srcBlock.GetType().Name}) is not ISourceBlock<{typeof(TInput).Name}>. Pipeline may not complete."); + throw new InvalidOperationException($"Source block ({srcBlock.GetType().Name}) is not ISourceBlock<{typeof(TInput).Name}>"); } else { @@ -193,6 +194,7 @@ protected void WaitAllTasks(params Task[] completionTasks) sw.Stop(); Block.Complete(); Logger.LogInformation(Context.Parameters, $"{Name} | ☑️ Block completed | ⏱️ {sw.Elapsed}"); + Parent.LogBlockCompletionDetails(); })); } } @@ -202,6 +204,7 @@ protected void LinkFromSource(Func blockOutput, params Task[] waitForTas if (waitForTasks?.Any() ?? false) { Logger.LogTrace(Context.Parameters, $"{Name} | 🕑 Awaiting the completion of {waitForTasks.Length} source block task{(waitForTasks.Length > 1 ? "s" : string.Empty)}"); + _whenAllTasks.Add(Task.WhenAll(waitForTasks).ContinueWith(t => { try @@ -215,18 +218,14 @@ protected void LinkFromSource(Func blockOutput, params Task[] waitForTas } Logger.LogTrace(Context.Parameters, $"{Name} | 🚀 Block accepted input ({typeof(TInput).Name})"); + Block.Complete(); } catch (Exception exception) { Logger.LogException(Context.Parameters, exception); throw; } - finally - { - Block.Complete(); - Logger.LogTrace(Context.Parameters, $"{Name} | ☑️ Block completed"); - } - }, Token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current)); + }, Token, TaskContinuationOptions.NotOnCanceled, TaskScheduler.Current)); } } diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/IPipeline.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/IPipeline.cs index 693e844..b753f5c 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/IPipeline.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/IPipeline.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks.Dataflow; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; public interface IPipeline { diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/ISynchronizationPipeline.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/ISynchronizationPipeline.cs index fa5cb16..7bc8c94 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/ISynchronizationPipeline.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/ISynchronizationPipeline.cs @@ -1,4 +1,4 @@ -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; public interface ISynchronizationPipeline : IPipeline where TContext : class diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/ISynchronizationPipelineFactory.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/ISynchronizationPipelineFactory.cs index d89ed05..f115f4d 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/ISynchronizationPipelineFactory.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/ISynchronizationPipelineFactory.cs @@ -1,4 +1,4 @@ -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; public interface ISynchronizationPipelineFactory where TContext : class diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/PipelineBase.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/PipelineBase.cs index 85830c2..ba3ccc4 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/PipelineBase.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/PipelineBase.cs @@ -1,6 +1,7 @@ -using System.Threading.Tasks.Dataflow; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using System.Threading.Tasks.Dataflow; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; public abstract class PipelineBase : IDisposable, IPipeline where TContext : IPipelineContext diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/PipelineResult.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/PipelineResult.cs index 5084ad3..361f339 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/PipelineResult.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/PipelineResult.cs @@ -1,6 +1,6 @@ using System.Collections.Concurrent; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; public class PipelineResult : IPipelineResult { diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/PipelineSection.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/PipelineSection.cs index 13b0b31..a81f83e 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/PipelineSection.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/PipelineSection.cs @@ -1,6 +1,7 @@ -using System.Threading.Tasks.Dataflow; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using System.Threading.Tasks.Dataflow; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; public interface IPipelineResult { @@ -26,6 +27,7 @@ protected PipelineSection(IPipeline parent, CancellationTokenSou private readonly List _tracedCompletionTasks = []; public void TraceBlockCompletion(IDataflowBlock block, string name) { + Logger.LogTrace(Context.Parameters, $"{GetType().Name} | ☑️ Tracing block completion for {name}."); _tracedCompletionTasks.Add(block.Completion.ContinueWith(t => { string status; @@ -41,22 +43,26 @@ public void TraceBlockCompletion(IDataflowBlock block, string name) status = "task ran to completion."; break; default: - status = $"task is in unexpected state {t.Status}."; + status = $"task is in unexpected state '{t.Status}'."; break; } Logger.LogTrace(Context.Parameters, $"{GetType().Name} | ✅ Dataflow block completion task completed | {name} ({block.GetType().Name}) | {status}"); - }).ContinueWith(t => - { - var details = Blocks.Select( - block => $"{( - block.Value.Completion.IsCompletedSuccessfully ? "✔️" - : block.Value.Completion.IsFaulted ? "✖️" - : block.Value.Completion.IsCanceled ? "⛔" - : "🕑")} {block.Key} : {block.Value.Completion.Status}"); - Logger.LogTrace(Context.Parameters, "Pipeline block completion status details" + Environment.NewLine + string.Join(Environment.NewLine, details)); + LogBlockCompletionDetails(); })); } + public void LogBlockCompletionDetails() + { + var details = Blocks.Select( + block => $"{( + block.Value.Completion.IsCompletedSuccessfully ? "✔️" + : block.Value.Completion.IsFaulted ? "✖️" + : block.Value.Completion.IsCanceled ? "⛔" + : "🕑")} {block.Key} : {block.Value.Completion.Status}"); + + Logger.LogTrace(Context.Parameters, "Pipeline block completion status details" + Environment.NewLine + string.Join(Environment.NewLine, details)); + } + public void FaultPipelineBlock(IDataflowBlock block, Exception exception) { block.Fault(exception); diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/SynchronizationPipelineFactory.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/SynchronizationPipelineFactory.cs index 01ae599..ca01999 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/SynchronizationPipelineFactory.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/SynchronizationPipelineFactory.cs @@ -1,8 +1,12 @@ using Hangfire; -using rubberduckvba.com.Server.ContentSynchronization.XmlDoc.Abstract; -using rubberduckvba.com.Server.Services; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.ContentSynchronization.XmlDoc; +using rubberduckvba.Server.ContentSynchronization.XmlDoc.Abstract; +using rubberduckvba.Server.Data; +using rubberduckvba.Server.Model.Entity; +using rubberduckvba.Server.Services; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; public class SynchronizationPipelineFactory : ISynchronizationPipelineFactory { @@ -12,8 +16,17 @@ public class SynchronizationPipelineFactory : ISynchronizationPipelineFactory _inspections; + private readonly IRepository _quickfixes; + private readonly IRepository _annotations; - public SynchronizationPipelineFactory(ILogger logger, IRubberduckDbService content, IGitHubClientService github, IXmlDocMerge merge, IStagingServices staging, IMarkdownFormattingService markdown) + private readonly XmlDocAnnotationParser _annotationParser; + private readonly XmlDocQuickFixParser _quickFixParser; + private readonly XmlDocInspectionParser _inspectionParser; + + public SynchronizationPipelineFactory(ILogger logger, IRubberduckDbService content, IGitHubClientService github, IXmlDocMerge merge, IStagingServices staging, IMarkdownFormattingService markdown, + IRepository inspections, IRepository quickfixes, IRepository annotations, + XmlDocAnnotationParser xmlAnnotationParser, XmlDocQuickFixParser xmlQuickFixParser, XmlDocInspectionParser xmlInspectionParser) { _logger = logger; _content = content; @@ -21,20 +34,26 @@ public SynchronizationPipelineFactory(ILogger logger, IRubberduc _merge = merge; _staging = staging; _markdown = markdown; + _inspections = inspections; + _quickfixes = quickfixes; + _annotations = annotations; + + _annotationParser = xmlAnnotationParser; + _quickFixParser = xmlQuickFixParser; + _inspectionParser = xmlInspectionParser; } public ISynchronizationPipeline Create(TParameters parameters, CancellationTokenSource tokenSource) where TParameters : IRequestParameters { return parameters switch { - XmldocSyncRequestParameters => new SynchronizeXmlPipeline(parameters, _logger, _content, _github, _merge, _staging, _markdown, tokenSource), + XmldocSyncRequestParameters => new SynchronizeXmlPipeline(parameters, _logger, _content, _github, _merge, _staging, _markdown, tokenSource, _inspections, _quickfixes, _annotations, _annotationParser, _quickFixParser, _inspectionParser), TagSyncRequestParameters => new SynchronizeTagsPipeline(parameters, _logger, _content, _github, _merge, _staging, tokenSource), _ => throw new NotSupportedException(), }; } } - public class HangfireTokenSource(IJobCancellationToken token) : CancellationTokenSource { public void ThrowIfCancellationRequested() => token.ThrowIfCancellationRequested(); -} \ No newline at end of file +} diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/TransformBlockBase.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/TransformBlockBase.cs index b41f590..0e44521 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/TransformBlockBase.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/TransformBlockBase.cs @@ -1,6 +1,7 @@ -using System.Threading.Tasks.Dataflow; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using System.Threading.Tasks.Dataflow; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; public abstract class TransformBlockBase : ExecutionDataflowBlockBase, TInput, TContext> where TContext : IPipelineContext diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/TransformManyBlockBase.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/TransformManyBlockBase.cs index af9e971..512cba0 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/TransformManyBlockBase.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/TransformManyBlockBase.cs @@ -1,12 +1,13 @@ -using System.Threading.Tasks.Dataflow; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using System.Threading.Tasks.Dataflow; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; public abstract class TransformManyBlockBase : ExecutionDataflowBlockBase, TInput, TContext> where TContext : IPipelineContext { protected TransformManyBlockBase(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) - : base(parent, tokenSource, logger) + : base(parent, tokenSource, logger) { } diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/PipelineLogger.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/PipelineLogger.cs index f94a8db..6ed7f74 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/PipelineLogger.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/PipelineLogger.cs @@ -1,3 +1,3 @@ -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline; public class PipelineLogger { } diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/Context/InitializeContextSection.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/Context/InitializeContextSection.cs index d49d1a8..d494ac6 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/Context/InitializeContextSection.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/Context/InitializeContextSection.cs @@ -1,12 +1,12 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncTags; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncTags; using System.Threading.Tasks.Dataflow; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; public class InitializeContextSection : PipelineSection { - public InitializeContextSection(IPipeline parent, CancellationTokenSource tokenSource, ILogger logger) + public InitializeContextSection(IPipeline parent, CancellationTokenSource tokenSource, ILogger logger) : base(parent, tokenSource, logger) { ReceiveRequest = new ReceiveRequestBlock(this, tokenSource, logger); diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/Context/StagingContext.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/Context/StagingContext.cs index 56f913c..3cae870 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/Context/StagingContext.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/Context/StagingContext.cs @@ -1,7 +1,7 @@ -using rubberduckvba.com.Server.Data; +using rubberduckvba.Server.Model; using System.Collections.Concurrent; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; public class StagingContext { @@ -12,11 +12,9 @@ public StagingContext(SyncRequestParameters parameters) Parameters = parameters; } - public ConcurrentBag ProcessedTags { get; } = []; - public ICollection NewTags { get; set; } = []; - public ICollection UpdatedTags { get; set; } = []; + public ConcurrentBag Tags { get; } = []; - public ConcurrentBag ProcessedFeatureItems { get; } = []; - public ICollection NewFeatureItems { get; set; } = []; - public ICollection UpdatedFeatureItems { get; set; } = []; + public ConcurrentBag Inspections { get; set; } = []; + public ConcurrentBag QuickFixes { get; set; } = []; + public ConcurrentBag Annotations { get; set; } = []; } diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/Context/SyncContext.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/Context/SyncContext.cs index 18925e3..cdafb1f 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/Context/SyncContext.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/Context/SyncContext.cs @@ -1,7 +1,7 @@ -using System.Collections.Immutable; -using rubberduckvba.com.Server.Data; +using rubberduckvba.Server.Model; +using System.Collections.Immutable; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; public interface IPipelineContext { @@ -17,11 +17,10 @@ public SyncContext(IRequestParameters parameters) private IRequestParameters? _parameters; public IRequestParameters Parameters => ContextNotInitializedException.ThrowIfNull(_parameters); - IRequestParameters IPipelineContext.Parameters => Parameters; + IRequestParameters IPipelineContext.Parameters => Parameters; public void LoadParameters(SyncRequestParameters parameters) { InvalidContextParameterException.ThrowIfNull(nameof(parameters), parameters); - //ContextAlreadyInitializedException.ThrowIfNotNull(_parameters); _parameters = parameters; _staging = new StagingContext(parameters); } @@ -30,8 +29,9 @@ public void LoadParameters(SyncRequestParameters parameters) public StagingContext StagingContext => ContextNotInitializedException.ThrowIfNull(_staging); + private Dictionary _latestTagsByAssetId = []; private TagGraph? _main; - public TagGraph RubberduckDbMain + public TagGraph RubberduckDbMain => ContextNotInitializedException.ThrowIfNull(_main); public void LoadRubberduckDbMain(TagGraph main) @@ -39,10 +39,14 @@ public void LoadRubberduckDbMain(TagGraph main) InvalidContextParameterException.ThrowIfNull(nameof(main), main); ContextAlreadyInitializedException.ThrowIfNotNull(_main); _main ??= main; + foreach (var asset in _main.Assets) + { + _latestTagsByAssetId[asset.Id] = _main; + } } private TagGraph? _next; - public TagGraph RubberduckDbNext + public TagGraph RubberduckDbNext => ContextNotInitializedException.ThrowIfNull(_next); public void LoadRubberduckDbNext(TagGraph next) @@ -50,6 +54,17 @@ public void LoadRubberduckDbNext(TagGraph next) InvalidContextParameterException.ThrowIfNull(nameof(next), next); ContextAlreadyInitializedException.ThrowIfNotNull(_next); _next ??= next; + foreach (var asset in _next.Assets) + { + _latestTagsByAssetId[asset.Id] = _next; + } + } + + + public bool TryGetTagByAssetId(int assetId, out TagGraph tag) + { + var tags = ContextNotInitializedException.ThrowIfNull(_latestTagsByAssetId); + return tags.TryGetValue(assetId, out tag!); } private ImmutableDictionary? _tagsById; @@ -66,8 +81,9 @@ public void LoadDbTags(IEnumerable tags) _tagsById = tags.ToImmutableDictionary(tag => tag.Id); } + private ImmutableDictionary? _inspectionConfig; - public ImmutableDictionary InspectionDefaultConfig + public ImmutableDictionary InspectionDefaultConfig => ContextNotInitializedException.ThrowIfNull(_inspectionConfig); public void LoadInspectionDefaultConfig(IEnumerable config) { @@ -76,21 +92,42 @@ public void LoadInspectionDefaultConfig(IEnumerable con _inspectionConfig = config.ToImmutableDictionary(e => e.InspectionName); } - private ImmutableHashSet? _featureItems; - public ImmutableHashSet FeatureItems - => ContextNotInitializedException.ThrowIfNull(_featureItems); + private ImmutableHashSet? _inspections; + public ImmutableHashSet Inspections + => ContextNotInitializedException.ThrowIfNull(_inspections); + + private ImmutableHashSet? _quickfixes; + public ImmutableHashSet QuickFixes + => ContextNotInitializedException.ThrowIfNull(_quickfixes); + + private ImmutableHashSet? _annotations; + public ImmutableHashSet Annotations + => ContextNotInitializedException.ThrowIfNull(_annotations); + + + public void LoadInspections(IEnumerable inspections) + { + ContextAlreadyInitializedException.ThrowIfNotNull(_inspections); + _inspections = inspections.ToImmutableHashSet(); + } + + public void LoadQuickFixes(IEnumerable quickfixes) + { + ContextAlreadyInitializedException.ThrowIfNotNull(_quickfixes); + _quickfixes = quickfixes.ToImmutableHashSet(); + } - public void LoadFeatureItems(IEnumerable items) + public void LoadAnnotations(IEnumerable annotations) { - ContextAlreadyInitializedException.ThrowIfNotNull(_featureItems); - _featureItems = items.ToImmutableHashSet(); + ContextAlreadyInitializedException.ThrowIfNotNull(_annotations); + _annotations = annotations.ToImmutableHashSet(); } private TagGraph? _githubMain; private TagGraph? _githubNext; private ImmutableHashSet? _githubOthers; - public TagGraph GitHubMain + public TagGraph GitHubMain => ContextNotInitializedException.ThrowIfNull(_githubMain); public TagGraph GitHubNext => ContextNotInitializedException.ThrowIfNull(_githubNext); @@ -142,12 +179,12 @@ public static void ThrowIfNullOrEmpty(string paramName, IEnumerable values } } - private InvalidContextParameterException(string paramName, string message) - : base(paramName, message) + private InvalidContextParameterException(string paramName, string message) + : base(paramName, message) { } } -public class ContextNotInitializedException : InvalidOperationException +public class ContextNotInitializedException : InvalidOperationException { public static T ThrowIfNull(T? value) => value ?? throw new ContextNotInitializedException(); diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/AccumulateProcessedTagsBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/AccumulateProcessedTagsBlock.cs index 528c951..5bd4a50 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/AccumulateProcessedTagsBlock.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/AccumulateProcessedTagsBlock.cs @@ -1,8 +1,9 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Data; -using rubberduckvba.com.Server.Services; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Model; +using rubberduckvba.Server.Services; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncTags; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncTags; public class AccumulateProcessedTagsBlock : TransformBlockBase { @@ -13,7 +14,7 @@ public AccumulateProcessedTagsBlock(PipelineSection parent, Cancell public override SyncContext Transform(TagGraph input) { - Context.StagingContext.ProcessedTags.Add(input); + Context.StagingContext.Tags.Add(input); return Context; } } diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/AcquireDbMainTagGraphBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/AcquireDbMainTagGraphBlock.cs index d4b5cdf..ad7fe23 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/AcquireDbMainTagGraphBlock.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/AcquireDbMainTagGraphBlock.cs @@ -1,8 +1,9 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Data; -using rubberduckvba.com.Server.Services; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Model; +using rubberduckvba.Server.Services; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncTags; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncTags; public class AcquireDbMainTagGraphBlock : TransformBlockBase { diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/AcquireDbNextTagGraphBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/AcquireDbNextTagGraphBlock.cs index 97989fd..e492f47 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/AcquireDbNextTagGraphBlock.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/AcquireDbNextTagGraphBlock.cs @@ -1,8 +1,9 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Data; -using rubberduckvba.com.Server.Services; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Model; +using rubberduckvba.Server.Services; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncTags; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncTags; public class AcquireDbNextTagGraphBlock : TransformBlockBase { diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/BroadcastParametersBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/BroadcastParametersBlock.cs index 4ac09ea..838f15d 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/BroadcastParametersBlock.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/BroadcastParametersBlock.cs @@ -1,6 +1,7 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncTags; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncTags; public class BroadcastParametersBlock : BroadcastBlockBase { diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/BroadcastTagsBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/BroadcastTagsBlock.cs index b09fada..4793a84 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/BroadcastTagsBlock.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/BroadcastTagsBlock.cs @@ -1,6 +1,7 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncTags; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncTags; public class BroadcastTagsBlock : BroadcastBlockBase, SyncContext> { diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/GetTagAssetsBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/GetTagAssetsBlock.cs index 25871e0..b6a5f8b 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/GetTagAssetsBlock.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/GetTagAssetsBlock.cs @@ -1,8 +1,9 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Data; -using rubberduckvba.com.Server.Services; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Model; +using rubberduckvba.Server.Services; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncTags; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncTags; public class GetTagAssetsBlock : TransformBlockBase { diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/LoadDbFeatureItemsBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/LoadDbFeatureItemsBlock.cs index 3bb489f..1d6eaeb 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/LoadDbFeatureItemsBlock.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/LoadDbFeatureItemsBlock.cs @@ -1,21 +1,34 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Services; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Data; +using rubberduckvba.Server.Model; +using rubberduckvba.Server.Model.Entity; +using rubberduckvba.Server.Services; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncTags; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncTags; public class LoadDbFeatureItemsBlock : ActionBlockBase { private IRubberduckDbService _db; + private readonly IRepository _inspections; + private readonly IRepository _quickfixes; + private readonly IRepository _annotations; - public LoadDbFeatureItemsBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger, IRubberduckDbService db) + public LoadDbFeatureItemsBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger, + IRepository inspections, IRepository quickfixes, IRepository annotations) : base(parent, tokenSource, logger) { - _db = db; + _inspections = inspections; + _quickfixes = quickfixes; + _annotations = annotations; } protected override async Task ActionAsync(SyncRequestParameters input) { - var items = await _db.GetXmlDocFeaturesAsync(input.RepositoryId); - Context.LoadFeatureItems(items); + await Task.WhenAll([ + Task.Run(() => _inspections.GetAll()).ContinueWith(t => Context.LoadInspections(t.Result.Select(e => new Inspection(e)))), + Task.Run(() => _quickfixes.GetAll()).ContinueWith(t => Context.LoadQuickFixes(t.Result.Select(e => new QuickFix(e)))), + Task.Run(() => _annotations.GetAll()).ContinueWith(t => Context.LoadAnnotations(t.Result.Select(e => new Annotation(e)))) + ]); } } \ No newline at end of file diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/LoadDbLatestTagsBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/LoadDbLatestTagsBlock.cs index 2e3fc39..ef5a082 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/LoadDbLatestTagsBlock.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/LoadDbLatestTagsBlock.cs @@ -1,11 +1,12 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Data; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Model; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncTags; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncTags; public class LoadDbLatestTagsBlock : TransformBlockBase, SyncContext, SyncContext> { - public LoadDbLatestTagsBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) + public LoadDbLatestTagsBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) : base(parent, tokenSource, logger) { } diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/LoadFeaturesBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/LoadFeaturesBlock.cs index afde93c..796bd47 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/LoadFeaturesBlock.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/LoadFeaturesBlock.cs @@ -1,7 +1,8 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Services; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Services; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncTags; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncTags; public class LoadFeaturesBlock : ActionBlockBase { diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/LoadGitHubTagsBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/LoadGitHubTagsBlock.cs index b1fc654..d9dfc63 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/LoadGitHubTagsBlock.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/LoadGitHubTagsBlock.cs @@ -1,7 +1,8 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Services; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Services; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncTags; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncTags; public class LoadGitHubTagsBlock : TransformBlockBase { @@ -17,7 +18,7 @@ public override async Task TransformAsync(SyncRequestParameters inp { var githubTags = await _github.GetAllTagsAsync(); // does not get the assets var (gitHubMain, gitHubNext, gitHubOthers) = githubTags.GetLatestTags(); - + Context.LoadGitHubTags(gitHubMain, gitHubNext, gitHubOthers); return Context; } diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/LoadInspectionDefaultConfigBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/LoadInspectionDefaultConfigBlock.cs index 6109009..14aac87 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/LoadInspectionDefaultConfigBlock.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/LoadInspectionDefaultConfigBlock.cs @@ -1,7 +1,8 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Services; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Services; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncTags; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncTags; public class LoadInspectionDefaultConfigBlock : ActionBlockBase { diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/ReceiveRequestBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/ReceiveRequestBlock.cs index 9840085..7077e58 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/ReceiveRequestBlock.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/ReceiveRequestBlock.cs @@ -1,6 +1,7 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncTags; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncTags; public class ReceiveRequestBlock : TransformBlockBase { diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/StreamGitHubOtherTagsBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/StreamGitHubOtherTagsBlock.cs index 057f3e1..fffac39 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/StreamGitHubOtherTagsBlock.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/StreamGitHubOtherTagsBlock.cs @@ -1,7 +1,8 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Data; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Model; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncTags; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncTags; public class StreamGitHubOtherTagsBlock : TransformManyBlockBase, TagGraph, SyncContext> { diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/StreamGitHubTagsBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/StreamGitHubTagsBlock.cs index ec9d6ff..4f4ebfb 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/StreamGitHubTagsBlock.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/StreamGitHubTagsBlock.cs @@ -1,7 +1,8 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Data; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Model; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncTags; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncTags; public class StreamGitHubTagsBlock : TransformManyBlockBase, TagGraph, SyncContext> { diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/SyncTagsSection.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/SyncTagsSection.cs index cdc603a..143576b 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/SyncTagsSection.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/SyncTagsSection.cs @@ -1,9 +1,10 @@ -using System.Threading.Tasks.Dataflow; -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Data; -using rubberduckvba.com.Server.Services; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Model; +using rubberduckvba.Server.Services; +using System.Threading.Tasks.Dataflow; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncTags; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncTags; public class SyncTagsSection : PipelineSection { @@ -43,7 +44,7 @@ public SyncTagsSection(IPipeline parent, CancellationTokenSou private BulkSaveStagingBlock SaveTags { get; } public ITargetBlock InputBlock => ReceiveRequest.Block; - public Task OutputTask => AccumulateProcessedTags.Block.Completion; + public Task OutputTask => SaveTags.Block.Completion; protected override IReadOnlyDictionary Blocks => new Dictionary { diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/TagBufferBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/TagBufferBlock.cs index 1562a9b..b35964b 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/TagBufferBlock.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/TagBufferBlock.cs @@ -1,7 +1,8 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Data; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Model; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncTags; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncTags; public class TagBufferBlock : BufferBlockBase { diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/TagExtensions.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/TagExtensions.cs index 22ebf9f..61a3da0 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/TagExtensions.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncTags/TagExtensions.cs @@ -1,6 +1,6 @@ -using rubberduckvba.com.Server.Data; +using rubberduckvba.Server.Model; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncTags; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncTags; public static class TagExtensions { @@ -9,9 +9,9 @@ public static (TagGraph main, TagGraph next, IEnumerable others) GetLa var sortedTags = tags.OrderByDescending(tag => tag.DateCreated) .GroupBy(tag => tag.IsPreRelease) .ToDictionary(grouping => grouping.Key, grouping => grouping.AsEnumerable()); - + return ( - sortedTags[false].First(), + sortedTags[false].First(), sortedTags[true].First(), sortedTags[false].Skip(1).Concat(sortedTags[true].Skip(1).ToArray()) ); diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/AccumulateFeatureItemsBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/AccumulateFeatureItemsBlock.cs deleted file mode 100644 index 200a4fe..0000000 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/AccumulateFeatureItemsBlock.cs +++ /dev/null @@ -1,20 +0,0 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Data; - -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; - -public class AccumulateFeatureItemsBlock : ActionBlockBase -{ - public AccumulateFeatureItemsBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) - : base(parent, tokenSource, logger) - { - } - - protected override void Action(FeatureXmlDoc input) - { - if (input != null) - { - Context.StagingContext.ProcessedFeatureItems.Add(input); - } - } -} diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/AccumulateProcessedFeatureItemsBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/AccumulateProcessedFeatureItemsBlock.cs deleted file mode 100644 index fbe291b..0000000 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/AccumulateProcessedFeatureItemsBlock.cs +++ /dev/null @@ -1,30 +0,0 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Data; - -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; - -public class AccumulateProcessedFeatureItemsBlock : ActionBlockBase -{ - public AccumulateProcessedFeatureItemsBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) - : base(parent, tokenSource, logger) - { - } - - protected override void Action(FeatureXmlDoc input) - { - if (input != null) - { - foreach (var featureItem in input.Examples.GroupBy(e => e.FeatureItemId)) - { - var exampleNumber = 1; - foreach (var example in featureItem) - { - example.SortOrder = exampleNumber; - exampleNumber++; - } - } - - Context.StagingContext.ProcessedFeatureItems.Add(input); - } - } -} diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/AccumulateProcessedInspectionsBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/AccumulateProcessedInspectionsBlock.cs new file mode 100644 index 0000000..b7365aa --- /dev/null +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/AccumulateProcessedInspectionsBlock.cs @@ -0,0 +1,59 @@ +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Model; + +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; + +public class AccumulateProcessedInspectionsBlock : ActionBlockBase +{ + public AccumulateProcessedInspectionsBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) + : base(parent, tokenSource, logger) + { + } + + protected override void Action(Inspection input) + { + if (input is null) + { + return; + } + + Context.StagingContext.Inspections.Add(input); + } +} + +public class AccumulateProcessedQuickFixesBlock : ActionBlockBase +{ + public AccumulateProcessedQuickFixesBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) + : base(parent, tokenSource, logger) + { + } + + protected override void Action(QuickFix input) + { + if (input is null) + { + return; + } + + Context.StagingContext.QuickFixes.Add(input); + } +} + +public class AccumulateProcessedAnnotationsBlock : ActionBlockBase +{ + public AccumulateProcessedAnnotationsBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) + : base(parent, tokenSource, logger) + { + } + + protected override void Action(Annotation input) + { + if (input is null) + { + return; + } + + Context.StagingContext.Annotations.Add(input); + } +} diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/BroadcastLatestTagsBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/BroadcastLatestTagsBlock.cs deleted file mode 100644 index c1f5fed..0000000 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/BroadcastLatestTagsBlock.cs +++ /dev/null @@ -1,14 +0,0 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Data; - -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; - -public class BroadcastLatestTagsBlock : TransformManyBlockBase, TagGraph, SyncContext> -{ - public BroadcastLatestTagsBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) - : base(parent, tokenSource, logger) - { - } - - public override IEnumerable Transform(Tuple input) => [Context.RubberduckDbMain, Context.RubberduckDbNext]; -} diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/BroadcastQuickFixesBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/BroadcastQuickFixesBlock.cs new file mode 100644 index 0000000..e3225f2 --- /dev/null +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/BroadcastQuickFixesBlock.cs @@ -0,0 +1,29 @@ +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Model; + +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; + +public class BroadcastQuickFixesBlock : BroadcastBlockBase, SyncContext> +{ + public BroadcastQuickFixesBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) + : base(parent, tokenSource, logger) + { + } +} + +public class BroadcastAnnotationsBlock : BroadcastBlockBase, SyncContext> +{ + public BroadcastAnnotationsBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) + : base(parent, tokenSource, logger) + { + } +} + +public class BroadcastInspectionsBlock : BroadcastBlockBase, SyncContext> +{ + public BroadcastInspectionsBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) + : base(parent, tokenSource, logger) + { + } +} diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/BroadcastTagAssetsBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/BroadcastTagAssetsBlock.cs deleted file mode 100644 index c831c83..0000000 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/BroadcastTagAssetsBlock.cs +++ /dev/null @@ -1,14 +0,0 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Data; - -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; - -public class BroadcastTagAssetsBlock : TransformManyBlockBase -{ - public BroadcastTagAssetsBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) - : base(parent, tokenSource, logger) - { - } - - public override IEnumerable Transform(TagGraph input) => input.Assets; -} diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/BroadcastXDocumentBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/BroadcastXDocumentBlock.cs index 0fc6f6c..0325955 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/BroadcastXDocumentBlock.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/BroadcastXDocumentBlock.cs @@ -1,8 +1,9 @@ -using System.Xml.Linq; -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Data; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Model; +using System.Xml.Linq; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; public class BroadcastXDocumentBlock : BroadcastBlockBase<(TagAsset, XDocument), SyncContext> { diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/BulkSaveStagingBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/BulkSaveStagingBlock.cs index f40d921..28eb80c 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/BulkSaveStagingBlock.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/BulkSaveStagingBlock.cs @@ -1,10 +1,10 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Data; -using rubberduckvba.com.Server.Services; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Services; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; -public class BulkSaveStagingBlock : ActionBlockBase, SyncContext> +public class BulkSaveStagingBlock : ActionBlockBase { private readonly IStagingServices _staging; @@ -14,8 +14,8 @@ public BulkSaveStagingBlock(PipelineSection parent, CancellationTok _staging = staging; } - protected override async Task ActionAsync(IEnumerable input) + protected override async Task ActionAsync(StagingContext input) { - await _staging.StageAsync(Context.StagingContext, Token); + await _staging.StageAsync(input, Token); } } diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/DownloadXmlAssetBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/DownloadXmlTagAssetBlock.cs similarity index 75% rename from rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/DownloadXmlAssetBlock.cs rename to rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/DownloadXmlTagAssetBlock.cs index 6ff3d26..5cb6ab8 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/DownloadXmlAssetBlock.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/DownloadXmlTagAssetBlock.cs @@ -1,12 +1,13 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Data; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Model; using System.Xml.Linq; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; -public class DownloadXmlAssetBlock : TransformBlockBase +public class DownloadXmlTagAssetBlock : TransformBlockBase { - public DownloadXmlAssetBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) + public DownloadXmlTagAssetBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) : base(parent, tokenSource, logger) { } diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/FeatureItemBufferBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/FeatureItemBufferBlock.cs deleted file mode 100644 index 9e254ed..0000000 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/FeatureItemBufferBlock.cs +++ /dev/null @@ -1,10 +0,0 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Data; - -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; - -public class FeatureItemBufferBlock : BufferBlockBase -{ - public FeatureItemBufferBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) - : base(parent, tokenSource, logger){ } -} diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/GetInspectionNodesBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/GetInspectionNodesBlock.cs deleted file mode 100644 index 5288d43..0000000 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/GetInspectionNodesBlock.cs +++ /dev/null @@ -1,31 +0,0 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Data; -using System.Xml.Linq; - -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; - -public class GetInspectionNodesBlock : GetXElementInfoBlock -{ - public GetInspectionNodesBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) - : base(parent, tokenSource, logger) - { - } - - public override IEnumerable<(TagAsset, XElementInfo)> Transform((TagAsset asset, XDocument document) input) - { - if (input.asset is null) - { - return []; - } - - var result = (from node in input.document.Descendants("member").AsParallel() - let fullName = GetNameOrDefault(node, "Inspection") - where !string.IsNullOrWhiteSpace(fullName) - let typeName = fullName.Substring(fullName.LastIndexOf(".", StringComparison.Ordinal) + 1) - select (input.asset, new XElementInfo(typeName, node))) - .ToList(); - - Logger.LogInformation(Context.Parameters, $"{Name} | Extracted {result.Count} inspection nodes from tag asset {input.asset.Name}"); - return result; - } -} diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/GetTagAssetBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/GetTagAssetBlock.cs index 1b98643..766e92c 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/GetTagAssetBlock.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/GetTagAssetBlock.cs @@ -1,7 +1,8 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Data; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Model; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; public class GetTagAssetBlock : TransformBlockBase { diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/GetXElementInfoBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/GetXElementInfoBlock.cs index 5556dbe..6bd63ef 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/GetXElementInfoBlock.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/GetXElementInfoBlock.cs @@ -1,8 +1,9 @@ -using System.Xml.Linq; -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Data; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Model; +using System.Xml.Linq; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; public abstract class GetXElementInfoBlock : TransformManyBlockBase<(TagAsset asset, XDocument document), (TagAsset, XElementInfo), SyncContext> { diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/LoadDbTagsBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/LoadDbTagsBlock.cs index 91609f2..c7c4759 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/LoadDbTagsBlock.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/LoadDbTagsBlock.cs @@ -1,15 +1,16 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Data; -using rubberduckvba.com.Server.Services; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Model; +using rubberduckvba.Server.Services; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; public class AcquireDbTagsBlock : TransformBlockBase, SyncContext> { private readonly IRubberduckDbService _service; public AcquireDbTagsBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger, - IRubberduckDbService service) + IRubberduckDbService service) : base(parent, tokenSource, logger) { _service = service; diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/LoadFeaturesBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/LoadFeaturesBlock.cs index 1fe9180..7424584 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/LoadFeaturesBlock.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/LoadFeaturesBlock.cs @@ -1,7 +1,8 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Services; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Services; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; public class LoadFeaturesBlock : TransformBlockBase { diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/LoadInspectionDefaultConfigBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/LoadInspectionDefaultConfigBlock.cs index b760e7a..a001930 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/LoadInspectionDefaultConfigBlock.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/LoadInspectionDefaultConfigBlock.cs @@ -1,7 +1,8 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Services; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Services; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; public class LoadInspectionDefaultConfigBlock : TransformBlockBase { diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/MergeAnnotationsBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/MergeAnnotationsBlock.cs new file mode 100644 index 0000000..49e38f0 --- /dev/null +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/MergeAnnotationsBlock.cs @@ -0,0 +1,46 @@ +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.ContentSynchronization.XmlDoc.Abstract; +using rubberduckvba.Server.Model; + +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; + +public class MergeAnnotationsBlock : TransformBlockBase, IEnumerable, SyncContext> +{ + private readonly IXmlDocMerge _service; + + public MergeAnnotationsBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger, IXmlDocMerge service) + : base(parent, tokenSource, logger) + { + _service = service; + } + + public override IEnumerable Transform(IEnumerable input) + { + try + { + var main = Context.StagingContext.Annotations + .Where(annotation => Context.RubberduckDbMain.Assets.Any(asset => asset.Id == annotation.TagAssetId)) + .ToList(); + + var next = Context.StagingContext.Annotations + .Where(annotation => Context.RubberduckDbNext.Assets.Any(asset => asset.Id == annotation.TagAssetId)) + .ToList(); + + Logger.LogDebug(Context.Parameters, $"Merging annotations. Main x{main.Count} | Next x{next.Count}"); + var dbItems = Context.Annotations.ToDictionary(e => e.Name); + + var merged = _service.Merge(dbItems, main, next); + Logger.LogDebug(Context.Parameters, $"Merged x{merged.Count()}"); + + Context.StagingContext.Annotations = new(merged); + + Logger.LogDebug(Context.Parameters, $"Updated: {Context.StagingContext.Annotations.Count(e => e.Id != default)} | New: {Context.StagingContext.Annotations.Count(e => e.Id == default)}"); + return merged; + } + finally + { + Block.Complete(); + } + } +} diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/MergeFeatureItemsBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/MergeFeatureItemsBlock.cs deleted file mode 100644 index 892e255..0000000 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/MergeFeatureItemsBlock.cs +++ /dev/null @@ -1,46 +0,0 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.ContentSynchronization.XmlDoc.Abstract; -using rubberduckvba.com.Server.Data; -using System.Collections.Immutable; - -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; - -public class MergeFeatureItemsBlock : TransformBlockBase, IEnumerable, SyncContext> -{ - private readonly IXmlDocMerge _service; - - public MergeFeatureItemsBlock(IXmlDocMerge merge, PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) - : base(parent, tokenSource, logger) - { - _service = merge; - } - - public override IEnumerable Transform(IEnumerable input) - { - try - { - var main = input - .Where(e => Context.RubberduckDbMain.Assets.Any(asset => asset.TagId == e.TagId && asset.Id == e.TagAssetId)) - .ToList(); - - var next = input - .Where(e => Context.RubberduckDbNext.Assets.Any(asset => asset.TagId == e.TagId && asset.Id == e.TagAssetId)) - .ToList(); - - Logger.LogDebug(Context.Parameters, $"Merging feature items. Main x{main.Count} | Next x{next.Count}"); - var dbItems = Context.FeatureItems.ToDictionary(e => e.Name); - var merged = _service.Merge(dbItems, main, next); - Logger.LogDebug(Context.Parameters, $"Merged x{merged.Count()}"); - - Context.StagingContext.UpdatedFeatureItems = merged.Where(e => e.Id != default && e.DateTimeUpdated.HasValue).ToImmutableArray(); - Context.StagingContext.NewFeatureItems = merged.Where(e => e.Id == default).ToImmutableArray(); - - Logger.LogDebug(Context.Parameters, $"Updated: {Context.StagingContext.UpdatedFeatureItems.Count} | New: {Context.StagingContext.NewFeatureItems.Count}"); - return merged; - } - finally - { - Block.Complete(); - } - } -} diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/MergeInspectionsBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/MergeInspectionsBlock.cs new file mode 100644 index 0000000..c7f247f --- /dev/null +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/MergeInspectionsBlock.cs @@ -0,0 +1,45 @@ +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.ContentSynchronization.XmlDoc.Abstract; +using rubberduckvba.Server.Model; + +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; + +public class MergeInspectionsBlock : TransformBlockBase, IEnumerable, SyncContext> +{ + private readonly IXmlDocMerge _service; + + public MergeInspectionsBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger, IXmlDocMerge service) + : base(parent, tokenSource, logger) + { + _service = service; + } + + public override IEnumerable Transform(IEnumerable input) + { + try + { + var main = Context.StagingContext.Inspections + .Where(inspection => Context.RubberduckDbMain.Assets.Any(asset => asset.Id == inspection.TagAssetId)) + .ToList(); + + var next = Context.StagingContext.Inspections + .Where(inspection => Context.RubberduckDbNext.Assets.Any(asset => asset.Id == inspection.TagAssetId)) + .ToList(); + + Logger.LogDebug(Context.Parameters, $"Merging inspections. Main x{main.Count} | Next x{next.Count}"); + var dbItems = Context.Inspections.ToDictionary(e => e.Name); + var merged = _service.Merge(dbItems, main, next); + Logger.LogDebug(Context.Parameters, $"Merged x{merged.Count()}"); + + Context.StagingContext.Inspections = new(merged); + + Logger.LogDebug(Context.Parameters, $"Updated: {Context.StagingContext.Inspections.Count(e => e.Id != default)} | New: {Context.StagingContext.Inspections.Count(e => e.Id == default)}"); + return merged; + } + finally + { + Block.Complete(); + } + } +} diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/MergeQuickFixesBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/MergeQuickFixesBlock.cs new file mode 100644 index 0000000..b5e9b34 --- /dev/null +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/MergeQuickFixesBlock.cs @@ -0,0 +1,45 @@ +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.ContentSynchronization.XmlDoc.Abstract; +using rubberduckvba.Server.Model; + +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; + +public class MergeQuickFixesBlock : TransformBlockBase, IEnumerable, SyncContext> +{ + private readonly IXmlDocMerge _service; + + public MergeQuickFixesBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger, IXmlDocMerge service) + : base(parent, tokenSource, logger) + { + _service = service; + } + + public override IEnumerable Transform(IEnumerable input) + { + try + { + var main = Context.StagingContext.QuickFixes + .Where(quickfix => Context.RubberduckDbMain.Assets.Any(asset => asset.Id == quickfix.TagAssetId)) + .ToList(); + + var next = Context.StagingContext.QuickFixes + .Where(e => Context.RubberduckDbNext.Assets.Any(asset => asset.Id == e.TagAssetId)) + .ToList(); + + Logger.LogDebug(Context.Parameters, $"Merging quickfixes. Main x{main.Count} | Next x{next.Count}"); + var dbItems = Context.QuickFixes.ToDictionary(e => e.Name); + var merged = _service.Merge(dbItems, main, next); + Logger.LogDebug(Context.Parameters, $"Merged x{merged.Count()}"); + + Context.StagingContext.QuickFixes = new(merged); + + Logger.LogDebug(Context.Parameters, $"Updated: {Context.StagingContext.QuickFixes.Count(e => e.Id != default)} | New: {Context.StagingContext.QuickFixes.Count(e => e.Id == default)}"); + return merged; + } + finally + { + Block.Complete(); + } + } +} diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/ParseAnnotationXElementInfoBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/ParseAnnotationXElementInfoBlock.cs index fce9d47..f729130 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/ParseAnnotationXElementInfoBlock.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/ParseAnnotationXElementInfoBlock.cs @@ -1,29 +1,27 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.ContentSynchronization.XmlDoc; -using rubberduckvba.com.Server.Data; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.ContentSynchronization.XmlDoc; +using rubberduckvba.Server.Model; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; -public class ParseAnnotationXElementInfoBlock : TransformBlockBase<(TagAsset asset, XElementInfo info), FeatureXmlDoc, SyncContext> +public class ParseAnnotationXElementInfoBlock : TransformBlockBase<(TagAsset asset, XElementInfo info), Annotation, SyncContext> { - public ParseAnnotationXElementInfoBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) + private readonly XmlDocAnnotationParser _parser; + + public ParseAnnotationXElementInfoBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger, XmlDocAnnotationParser parser) : base(parent, tokenSource, logger) { + _parser = parser; } - public override FeatureXmlDoc Transform((TagAsset asset, XElementInfo info) input) + public override Annotation Transform((TagAsset asset, XElementInfo info) input) { - if (input.asset is null) - { - return null; - } - var feature = Context.Features["Annotations"]; var isPreRelease = Context.RubberduckDbNext.Assets.Any(asset => asset.Id == input.asset.Id); - var parser = new XmlDocAnnotation(input.info.Name, input.info.Element, isPreRelease); - var result = parser.Parse(input.asset.Id, feature.Id); + var result = _parser.Parse(input.asset.Id, feature.Id, input.info.Name, input.info.Element, isPreRelease); - return result; + return result ?? throw new InvalidOperationException("Failed to parse annotation xmldoc"); } } diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/ParseInspectionXElementInfoBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/ParseInspectionXElementInfoBlock.cs index 812ed19..29f5cb2 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/ParseInspectionXElementInfoBlock.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/ParseInspectionXElementInfoBlock.cs @@ -1,22 +1,22 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.ContentSynchronization.XmlDoc; -using rubberduckvba.com.Server.Data; -using rubberduckvba.com.Server.Services; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.ContentSynchronization.XmlDoc; +using rubberduckvba.Server.Model; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; -public class ParseInspectionXElementInfoBlock : TransformBlockBase<(TagAsset asset, XElementInfo info), FeatureXmlDoc, SyncContext> +public class ParseInspectionXElementInfoBlock : TransformBlockBase<(TagAsset asset, XElementInfo info, IEnumerable quickfixes), Inspection, SyncContext> { - private readonly IMarkdownFormattingService _markdownService; + private readonly XmlDocInspectionParser _parser; - public ParseInspectionXElementInfoBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger, - IMarkdownFormattingService markdownService) + public ParseInspectionXElementInfoBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger, + XmlDocInspectionParser parser) : base(parent, tokenSource, logger) { - _markdownService = markdownService; + _parser = parser; } - public override async Task TransformAsync((TagAsset asset, XElementInfo info) input) + public override async Task TransformAsync((TagAsset asset, XElementInfo info, IEnumerable quickfixes) input) { if (input.asset is null) { @@ -24,19 +24,13 @@ public override async Task TransformAsync((TagAsset asset, XEleme } var feature = Context.Features["Inspections"]; - var quickfixes = Context.Features["QuickFixes"].Items; // FIXME not loaded in initial run + var config = Context.InspectionDefaultConfig.TryGetValue(input.info.Name, out var value) ? value : null; var isPreRelease = Context.RubberduckDbNext.Assets.Any(asset => asset.Id == input.asset.Id); var name = input.info.Element.Attribute("name")!.Value; - if (Context.TryGetTagById(input.asset.TagId, out var tag)) - { - var parser = new XmlDocInspection(_markdownService); - var result = await parser.ParseAsync(input.asset.Id, tag.Name, feature.Id, quickfixes, name, input.info.Element, config, isPreRelease); - - return result with { TagId = tag.Id }; - } + var result = await _parser.ParseAsync(input.asset.Id, feature.Id, input.quickfixes, name, input.info.Element, config, isPreRelease); - throw new InvalidOperationException($"[Tag.Id]:{input.asset.TagId} was not loaded or does not exist."); + return result with { TagAssetId = input.asset.Id }; } } diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/ParseQuickFixXElementInfoBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/ParseQuickFixXElementInfoBlock.cs index ee393f4..e2e8ba3 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/ParseQuickFixXElementInfoBlock.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/ParseQuickFixXElementInfoBlock.cs @@ -1,29 +1,32 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.ContentSynchronization.XmlDoc; -using rubberduckvba.com.Server.Data; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.ContentSynchronization.XmlDoc; +using rubberduckvba.Server.Model; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; -public class ParseQuickFixXElementInfoBlock : TransformBlockBase<(TagAsset asset, XElementInfo info), FeatureXmlDoc, SyncContext> +public class ParseQuickFixXElementInfoBlock : TransformBlockBase<(TagAsset asset, XElementInfo info), QuickFix, SyncContext> { - public ParseQuickFixXElementInfoBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) + private readonly XmlDocQuickFixParser _parser; + + public ParseQuickFixXElementInfoBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger, XmlDocQuickFixParser parser) : base(parent, tokenSource, logger) { + _parser = parser; } - public override FeatureXmlDoc Transform((TagAsset asset, XElementInfo info) input) + public override QuickFix Transform((TagAsset asset, XElementInfo info) input) { if (input.asset is null) { - return null; + return null!; } var feature = Context.Features["QuickFixes"]; var isPreRelease = Context.RubberduckDbNext.Assets.Any(asset => asset.Id == input.asset.Id); - var parser = new XmlDocQuickFix(input.info.Name, input.info.Element, isPreRelease); - var result = parser.Parse(input.asset.Id, feature.Id) with { TagAssetId = input.asset.Id }; + var result = _parser.Parse(input.info.Name, input.asset.Id, feature.Id, input.info.Element, isPreRelease) with { TagAssetId = input.asset.Id }; - return result; + return result ?? throw new InvalidOperationException("Failed to parse quickfix xmldoc"); } } diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/PrepareStagingBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/PrepareStagingBlock.cs new file mode 100644 index 0000000..2090ecb --- /dev/null +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/PrepareStagingBlock.cs @@ -0,0 +1,26 @@ +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Model; + +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; + +public class PrepareStagingBlock : TransformBlockBase, IEnumerable, IEnumerable>, StagingContext, SyncContext> +{ + public PrepareStagingBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) + : base(parent, tokenSource, logger) + { + } + + public override StagingContext Transform(Tuple, IEnumerable, IEnumerable> input) + { + var parameters = Context.StagingContext.Parameters; + var (annotations, quickfixes, inspections) = (input.Item1, input.Item2, input.Item3); + + return new StagingContext(parameters) + { + Inspections = new(inspections), + QuickFixes = new(quickfixes), + Annotations = new(annotations) + }; + } +} \ No newline at end of file diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/GetAnnotationNodesBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/StreamAnnotationNodesBlock.cs similarity index 66% rename from rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/GetAnnotationNodesBlock.cs rename to rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/StreamAnnotationNodesBlock.cs index dd3a64e..591f88f 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/GetAnnotationNodesBlock.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/StreamAnnotationNodesBlock.cs @@ -1,12 +1,13 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Data; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Model; using System.Xml.Linq; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; -public class GetAnnotationNodesBlock : GetXElementInfoBlock +public class StreamAnnotationNodesBlock : GetXElementInfoBlock { - public GetAnnotationNodesBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) + public StreamAnnotationNodesBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) : base(parent, tokenSource, logger) { } diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/StreamInspectionNodesBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/StreamInspectionNodesBlock.cs new file mode 100644 index 0000000..8f4e894 --- /dev/null +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/StreamInspectionNodesBlock.cs @@ -0,0 +1,43 @@ +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Model; +using System.Xml.Linq; + +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; + +public class StreamInspectionNodesBlock : TransformManyBlockBase>, (TagAsset, XElementInfo, IEnumerable), SyncContext> +{ + public StreamInspectionNodesBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) + : base(parent, tokenSource, logger) + { + } + + public override IEnumerable<(TagAsset, XElementInfo, IEnumerable)> Transform(Tuple<(TagAsset, XDocument), IEnumerable> input) + { + if (input.Item1.Item1 is null) + { + return []; + } + + var result = (from node in input.Item1.Item2.Descendants("member").AsParallel() + let fullName = GetNameOrDefault(node, "Inspection") + where !string.IsNullOrWhiteSpace(fullName) + let typeName = fullName.Substring(fullName.LastIndexOf(".", StringComparison.Ordinal) + 1) + select (input.Item1.Item1, new XElementInfo(typeName, node), input.Item2)) + .ToList(); + + Logger.LogInformation(Context.Parameters, $"{Name} | Extracted {result.Count} inspection nodes from tag asset {input.Item1.Item1.Name}"); + return result; + } + + private static string GetNameOrDefault(XElement memberNode, string suffix) + { + var name = memberNode.Attribute("name")?.Value; + if (name == null || !name.StartsWith("T:") || !name.EndsWith(suffix) || name.EndsWith($"I{suffix}")) + { + return default!; + } + + return name.Substring(2); + } +} diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/StreamLatestTagsBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/StreamLatestTagsBlock.cs new file mode 100644 index 0000000..00893cc --- /dev/null +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/StreamLatestTagsBlock.cs @@ -0,0 +1,15 @@ +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Model; + +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; + +public class StreamLatestTagsBlock : TransformManyBlockBase, TagGraph, SyncContext> +{ + public StreamLatestTagsBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) + : base(parent, tokenSource, logger) + { + } + + public override IEnumerable Transform(Tuple input) => [Context.RubberduckDbMain, Context.RubberduckDbNext]; +} diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/GetQuickFixNodesBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/StreamQuickFixNodesBlock.cs similarity index 64% rename from rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/GetQuickFixNodesBlock.cs rename to rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/StreamQuickFixNodesBlock.cs index c64c6f2..6f924f9 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/GetQuickFixNodesBlock.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/StreamQuickFixNodesBlock.cs @@ -1,12 +1,13 @@ -using System.Xml.Linq; -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Data; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Model; +using System.Xml.Linq; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; -public class GetQuickFixNodesBlock : GetXElementInfoBlock +public class StreamQuickFixNodesBlock : GetXElementInfoBlock { - public GetQuickFixNodesBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) + public StreamQuickFixNodesBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) : base(parent, tokenSource, logger) { } diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/StreamTagAssetsBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/StreamTagAssetsBlock.cs new file mode 100644 index 0000000..798fd0c --- /dev/null +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/StreamTagAssetsBlock.cs @@ -0,0 +1,15 @@ +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Model; + +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; + +public class StreamTagAssetsBlock : TransformManyBlockBase +{ + public StreamTagAssetsBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) + : base(parent, tokenSource, logger) + { + } + + public override IEnumerable Transform(TagGraph input) => input.Assets; +} diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/StreamXmlAssetsBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/StreamXmlAssetsBlock.cs deleted file mode 100644 index f6e97f3..0000000 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/StreamXmlAssetsBlock.cs +++ /dev/null @@ -1,10 +0,0 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Data; - -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; - -public class StreamXmlAssetsBlock : BufferBlockBase -{ - public StreamXmlAssetsBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) - : base(parent, tokenSource, logger) { } -} diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/SyncXmldocSection.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/SyncXmldocSection.cs index 9cc5f1b..d34d90a 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/SyncXmldocSection.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/SyncXmldocSection.cs @@ -1,47 +1,69 @@ -using System.Threading.Tasks.Dataflow; -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncTags; -using rubberduckvba.com.Server.ContentSynchronization.XmlDoc.Abstract; -using rubberduckvba.com.Server.Data; -using rubberduckvba.com.Server.Services; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncTags; +using rubberduckvba.Server.ContentSynchronization.XmlDoc; +using rubberduckvba.Server.ContentSynchronization.XmlDoc.Abstract; +using rubberduckvba.Server.Data; +using rubberduckvba.Server.Model; +using rubberduckvba.Server.Model.Entity; +using rubberduckvba.Server.Services; +using System.Threading.Tasks.Dataflow; +using System.Xml.Linq; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; public class SyncXmldocSection : PipelineSection { - public SyncXmldocSection(IPipeline parent, CancellationTokenSource tokenSource, ILogger logger, - IRubberduckDbService content, - IGitHubClientService github, - IXmlDocMerge mergeService, - IStagingServices staging, - IMarkdownFormattingService markdownService) + public SyncXmldocSection(IPipeline parent, CancellationTokenSource tokenSource, ILogger logger, + IRubberduckDbService content, + IRepository inspections, + IRepository quickfixes, + IRepository annotations, + IGitHubClientService github, + IXmlDocMerge mergeService, + IStagingServices staging, + XmlDocAnnotationParser xmlAnnotationParser, + XmlDocQuickFixParser xmlQuickFixParser, + XmlDocInspectionParser xmlInspectionParser) : base(parent, tokenSource, logger) { ReceiveRequest = new ReceiveRequestBlock(this, tokenSource, logger); BroadcastParameters = new BroadcastParametersBlock(this, tokenSource, logger); LoadInspectionDefaultConfig = new LoadInspectionDefaultConfigBlock(this, tokenSource, github, logger); LoadFeatures = new LoadFeaturesBlock(this, tokenSource, content, logger); - LoadDbFeatureItems = new LoadDbFeatureItemsBlock(this, tokenSource, logger, content); + LoadDbFeatureItems = new LoadDbFeatureItemsBlock(this, tokenSource, logger, inspections, quickfixes, annotations); AcquireDbMainTagGraph = new AcquireDbMainTagGraphBlock(this, tokenSource, content, logger); AcquireDbNextTagGraph = new AcquireDbNextTagGraphBlock(this, tokenSource, content, logger); AcquireDbTags = new AcquireDbTagsBlock(this, tokenSource, logger, content); JoinDbTags = new DataflowJoinBlock>(this, tokenSource, logger, nameof(JoinDbTags)); LoadDbTags = new LoadDbTagsBlock(this, tokenSource, logger); JoinAsyncSources = new DataflowJoinBlock(this, tokenSource, logger, nameof(JoinAsyncSources)); - BroadcastTags = new BroadcastLatestTagsBlock(this, tokenSource, logger); - BroadcastAssets = new BroadcastTagAssetsBlock(this, tokenSource, logger); - StreamXmlAssets = new StreamXmlAssetsBlock(this, tokenSource, logger); - DownloadXmlAsset = new DownloadXmlAssetBlock(this, tokenSource, logger); + StreamLatestTags = new StreamLatestTagsBlock(this, tokenSource, logger); + StreamTagAssets = new StreamTagAssetsBlock(this, tokenSource, logger); + BufferXmlAsset = new XmlTagAssetBufferBlock(this, tokenSource, logger); + DownloadXmlAsset = new DownloadXmlTagAssetBlock(this, tokenSource, logger); BroadcastXDocument = new BroadcastXDocumentBlock(this, tokenSource, logger); - GetInspectionNodes = new GetInspectionNodesBlock(this, tokenSource, logger); - GetQuickFixNodes = new GetQuickFixNodesBlock(this, tokenSource, logger); - GetAnnotationNodes = new GetAnnotationNodesBlock(this, tokenSource, logger); - ParseInspectionInfo = new ParseInspectionXElementInfoBlock(this, tokenSource, logger, markdownService); - ParseQuickFixInfo = new ParseQuickFixXElementInfoBlock(this, tokenSource, logger); - ParseAnnotationInfo = new ParseAnnotationXElementInfoBlock(this, tokenSource, logger); - FeatureItemBuffer = new FeatureItemBufferBlock(this, tokenSource, logger); - MergeItems = new MergeFeatureItemsBlock(mergeService, this, tokenSource, logger); - AccumulateProcessedItems = new AccumulateProcessedFeatureItemsBlock(this, tokenSource, logger); + JoinQuickFixes = new DataflowJoinBlock<(TagAsset, XDocument), IEnumerable>(this, tokenSource, logger, nameof(JoinQuickFixes)); + StreamInspectionNodes = new StreamInspectionNodesBlock(this, tokenSource, logger); + StreamQuickFixNodes = new StreamQuickFixNodesBlock(this, tokenSource, logger); + StreamAnnotationNodes = new StreamAnnotationNodesBlock(this, tokenSource, logger); + BufferInspections = new InspectionBufferBlock(this, tokenSource, logger); + BufferQuickFixes = new QuickFixBufferBlock(this, tokenSource, logger); + BufferAnnotations = new AnnotationBufferBlock(this, tokenSource, logger); + ParseXmlDocInspections = new ParseInspectionXElementInfoBlock(this, tokenSource, logger, xmlInspectionParser); + ParseXmlDocQuickFixes = new ParseQuickFixXElementInfoBlock(this, tokenSource, logger, xmlQuickFixParser); + ParseXmlDocAnnotations = new ParseAnnotationXElementInfoBlock(this, tokenSource, logger, xmlAnnotationParser); + MergeInspections = new MergeInspectionsBlock(this, tokenSource, logger, mergeService); + MergeQuickFixes = new MergeQuickFixesBlock(this, tokenSource, logger, mergeService); + BroadcastQuickFixes = new BroadcastQuickFixesBlock(this, tokenSource, logger); + BroadcastAnnotations = new BroadcastAnnotationsBlock(this, tokenSource, logger); + BroadcastInspections = new BroadcastInspectionsBlock(this, tokenSource, logger); + MergeAnnotations = new MergeAnnotationsBlock(this, tokenSource, logger, mergeService); + AccumulateProcessedInspections = new AccumulateProcessedInspectionsBlock(this, tokenSource, logger); + AccumulateProcessedQuickFixes = new AccumulateProcessedQuickFixesBlock(this, tokenSource, logger); + AccumulateProcessedAnnotations = new AccumulateProcessedAnnotationsBlock(this, tokenSource, logger); + JoinStagingSources = new DataflowJoinBlock, IEnumerable, IEnumerable>(this, tokenSource, logger, nameof(JoinStagingSources)); + PrepareStaging = new PrepareStagingBlock(this, tokenSource, logger); SaveStaging = new BulkSaveStagingBlock(this, tokenSource, staging, logger); } @@ -57,20 +79,36 @@ public SyncXmldocSection(IPipeline parent, CancellationTokenS private DataflowJoinBlock> JoinDbTags { get; } private LoadDbTagsBlock LoadDbTags { get; } private DataflowJoinBlock JoinAsyncSources { get; } - private BroadcastLatestTagsBlock BroadcastTags { get; } - private BroadcastTagAssetsBlock BroadcastAssets { get; } - private StreamXmlAssetsBlock StreamXmlAssets { get; } - private DownloadXmlAssetBlock DownloadXmlAsset { get; } + private StreamLatestTagsBlock StreamLatestTags { get; } + private StreamTagAssetsBlock StreamTagAssets { get; } + private XmlTagAssetBufferBlock BufferXmlAsset { get; } + private DownloadXmlTagAssetBlock DownloadXmlAsset { get; } private BroadcastXDocumentBlock BroadcastXDocument { get; } - private GetInspectionNodesBlock GetInspectionNodes { get; } - private GetQuickFixNodesBlock GetQuickFixNodes { get; } - private GetAnnotationNodesBlock GetAnnotationNodes { get; } - private ParseInspectionXElementInfoBlock ParseInspectionInfo { get; } - private ParseQuickFixXElementInfoBlock ParseQuickFixInfo { get; } - private ParseAnnotationXElementInfoBlock ParseAnnotationInfo { get; } - private FeatureItemBufferBlock FeatureItemBuffer { get; } - private MergeFeatureItemsBlock MergeItems { get; } - private AccumulateProcessedFeatureItemsBlock AccumulateProcessedItems { get; } + + private StreamQuickFixNodesBlock StreamQuickFixNodes { get; } + private QuickFixBufferBlock BufferQuickFixes { get; } + private ParseQuickFixXElementInfoBlock ParseXmlDocQuickFixes { get; } + private MergeQuickFixesBlock MergeQuickFixes { get; } + private BroadcastQuickFixesBlock BroadcastQuickFixes { get; } + private AccumulateProcessedQuickFixesBlock AccumulateProcessedQuickFixes { get; } + + private DataflowJoinBlock<(TagAsset, XDocument), IEnumerable> JoinQuickFixes { get; } + private StreamInspectionNodesBlock StreamInspectionNodes { get; } + private InspectionBufferBlock BufferInspections { get; } + private ParseInspectionXElementInfoBlock ParseXmlDocInspections { get; } + private MergeInspectionsBlock MergeInspections { get; } + private BroadcastInspectionsBlock BroadcastInspections { get; } + private AccumulateProcessedInspectionsBlock AccumulateProcessedInspections { get; } + + private StreamAnnotationNodesBlock StreamAnnotationNodes { get; } + private AnnotationBufferBlock BufferAnnotations { get; } + private ParseAnnotationXElementInfoBlock ParseXmlDocAnnotations { get; } + private MergeAnnotationsBlock MergeAnnotations { get; } + private BroadcastAnnotationsBlock BroadcastAnnotations { get; } + private AccumulateProcessedAnnotationsBlock AccumulateProcessedAnnotations { get; } + + private DataflowJoinBlock, IEnumerable, IEnumerable> JoinStagingSources { get; } + private PrepareStagingBlock PrepareStaging { get; } private BulkSaveStagingBlock SaveStaging { get; } public ITargetBlock InputBlock => ReceiveRequest.Block; @@ -89,20 +127,37 @@ public SyncXmldocSection(IPipeline parent, CancellationTokenS [nameof(JoinDbTags)] = JoinDbTags.Block, [nameof(LoadDbTags)] = LoadDbTags.Block, [nameof(JoinAsyncSources)] = JoinAsyncSources.Block, - [nameof(BroadcastTags)] = BroadcastTags.Block, - [nameof(BroadcastAssets)] = BroadcastAssets.Block, - [nameof(StreamXmlAssets)] = StreamXmlAssets.Block, + + [nameof(StreamLatestTags)] = StreamLatestTags.Block, + [nameof(StreamTagAssets)] = StreamTagAssets.Block, + [nameof(BufferXmlAsset)] = BufferXmlAsset.Block, [nameof(DownloadXmlAsset)] = DownloadXmlAsset.Block, [nameof(BroadcastXDocument)] = BroadcastXDocument.Block, - [nameof(GetInspectionNodes)] = GetInspectionNodes.Block, - [nameof(GetQuickFixNodes)] = GetQuickFixNodes.Block, - [nameof(GetAnnotationNodes)] = GetAnnotationNodes.Block, - [nameof(ParseInspectionInfo)] = ParseInspectionInfo.Block, - [nameof(ParseQuickFixInfo)] = ParseQuickFixInfo.Block, - [nameof(ParseAnnotationInfo)] = ParseAnnotationInfo.Block, - [nameof(FeatureItemBuffer)] = FeatureItemBuffer.Block, - [nameof(MergeItems)] = MergeItems.Block, - [nameof(AccumulateProcessedItems)] = AccumulateProcessedItems.Block, + + [nameof(StreamAnnotationNodes)] = StreamAnnotationNodes.Block, + [nameof(ParseXmlDocAnnotations)] = ParseXmlDocAnnotations.Block, + [nameof(AccumulateProcessedAnnotations)] = AccumulateProcessedAnnotations.Block, + [nameof(MergeAnnotations)] = MergeAnnotations.Block, + [nameof(BufferAnnotations)] = BufferAnnotations.Block, + [nameof(BroadcastAnnotations)] = BroadcastAnnotations.Block, + + [nameof(StreamQuickFixNodes)] = StreamQuickFixNodes.Block, + [nameof(ParseXmlDocQuickFixes)] = ParseXmlDocQuickFixes.Block, + [nameof(AccumulateProcessedQuickFixes)] = AccumulateProcessedQuickFixes.Block, + [nameof(MergeQuickFixes)] = MergeQuickFixes.Block, + [nameof(BufferQuickFixes)] = BufferQuickFixes.Block, + [nameof(BroadcastQuickFixes)] = BroadcastQuickFixes.Block, + + [nameof(JoinQuickFixes)] = JoinQuickFixes.Block, + [nameof(StreamInspectionNodes)] = StreamInspectionNodes.Block, + [nameof(ParseXmlDocInspections)] = ParseXmlDocInspections.Block, + [nameof(AccumulateProcessedInspections)] = AccumulateProcessedInspections.Block, + [nameof(MergeInspections)] = MergeInspections.Block, + [nameof(BufferInspections)] = BufferInspections.Block, + [nameof(BroadcastInspections)] = BroadcastInspections.Block, + + [nameof(JoinStagingSources)] = JoinStagingSources.Block, + [nameof(PrepareStaging)] = PrepareStaging.Block, [nameof(SaveStaging)] = SaveStaging.Block, }; #endregion @@ -120,20 +175,38 @@ public override void CreateBlocks() JoinDbTags.CreateBlock(AcquireDbMainTagGraph, AcquireDbNextTagGraph, AcquireDbTags); LoadDbTags.CreateBlock(JoinDbTags); JoinAsyncSources.CreateBlock(LoadDbTags, LoadInspectionDefaultConfig, LoadFeatures); - BroadcastTags.CreateBlock(JoinAsyncSources); - BroadcastAssets.CreateBlock(BroadcastTags); - StreamXmlAssets.CreateBlock(BroadcastAssets); - DownloadXmlAsset.CreateBlock(StreamXmlAssets); + + StreamLatestTags.CreateBlock(JoinAsyncSources); + StreamTagAssets.CreateBlock(StreamLatestTags); + BufferXmlAsset.CreateBlock(StreamTagAssets); + DownloadXmlAsset.CreateBlock(BufferXmlAsset); BroadcastXDocument.CreateBlock(DownloadXmlAsset); - GetInspectionNodes.CreateBlock(BroadcastXDocument); - GetQuickFixNodes.CreateBlock(BroadcastXDocument); - GetAnnotationNodes.CreateBlock(BroadcastXDocument); - ParseInspectionInfo.CreateBlock(GetInspectionNodes); - ParseQuickFixInfo.CreateBlock(GetQuickFixNodes); - ParseAnnotationInfo.CreateBlock(GetAnnotationNodes); - FeatureItemBuffer.CreateBlock(ParseInspectionInfo, ParseQuickFixInfo, ParseAnnotationInfo); - AccumulateProcessedItems.CreateBlock(FeatureItemBuffer); - MergeItems.CreateBlock(() => Context.StagingContext.ProcessedFeatureItems, AccumulateProcessedItems.Block.Completion); - SaveStaging.CreateBlock(MergeItems); + + StreamAnnotationNodes.CreateBlock(BroadcastXDocument); + ParseXmlDocAnnotations.CreateBlock(StreamAnnotationNodes); + AccumulateProcessedAnnotations.CreateBlock(ParseXmlDocAnnotations); + MergeAnnotations.CreateBlock(() => Context.StagingContext.Annotations, AccumulateProcessedAnnotations.Block.Completion); + BufferAnnotations.CreateBlock(MergeAnnotations); + BroadcastAnnotations.CreateBlock(BufferAnnotations); + + StreamQuickFixNodes.CreateBlock(BroadcastXDocument); + ParseXmlDocQuickFixes.CreateBlock(StreamQuickFixNodes); + AccumulateProcessedQuickFixes.CreateBlock(ParseXmlDocQuickFixes); + MergeQuickFixes.CreateBlock(() => Context.StagingContext.QuickFixes, AccumulateProcessedQuickFixes.Block.Completion); + BufferQuickFixes.CreateBlock(MergeQuickFixes); + BroadcastQuickFixes.CreateBlock(BufferQuickFixes); + + JoinQuickFixes.CreateBlock(BroadcastXDocument, BroadcastQuickFixes); + + StreamInspectionNodes.CreateBlock(JoinQuickFixes); + ParseXmlDocInspections.CreateBlock(StreamInspectionNodes); + AccumulateProcessedInspections.CreateBlock(ParseXmlDocInspections); + MergeInspections.CreateBlock(() => Context.StagingContext.Inspections, AccumulateProcessedInspections.Block.Completion); + BufferInspections.CreateBlock(MergeInspections); + BroadcastInspections.CreateBlock(BufferInspections); + + JoinStagingSources.CreateBlock(BroadcastAnnotations, BroadcastQuickFixes, BroadcastInspections); + PrepareStaging.CreateBlock(JoinStagingSources); + SaveStaging.CreateBlock(PrepareStaging); } } diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/XElementInfo.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/XElementInfo.cs index 6f61845..0a8dad8 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/XElementInfo.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/XElementInfo.cs @@ -1,5 +1,5 @@ using System.Xml.Linq; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; public record XElementInfo(string Name, XElement Element); diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/XmlTagAssetBufferBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/XmlTagAssetBufferBlock.cs new file mode 100644 index 0000000..69afc5e --- /dev/null +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/XmlTagAssetBufferBlock.cs @@ -0,0 +1,29 @@ +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Model; + +namespace rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; + +public class XmlTagAssetBufferBlock : BufferBlockBase +{ + public XmlTagAssetBufferBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) + : base(parent, tokenSource, logger) { } +} + +public class AnnotationBufferBlock : BufferBlockBase, SyncContext> +{ + public AnnotationBufferBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) + : base(parent, tokenSource, logger) { } +} + +public class QuickFixBufferBlock : BufferBlockBase, SyncContext> +{ + public QuickFixBufferBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) + : base(parent, tokenSource, logger) { } +} + +public class InspectionBufferBlock : BufferBlockBase, SyncContext> +{ + public InspectionBufferBlock(PipelineSection parent, CancellationTokenSource tokenSource, ILogger logger) + : base(parent, tokenSource, logger) { } +} diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/SynchronizeTagsPipeline.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/SynchronizeTagsPipeline.cs index e135568..e8ed1ba 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/SynchronizeTagsPipeline.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/SynchronizeTagsPipeline.cs @@ -1,9 +1,10 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncTags; -using rubberduckvba.com.Server.ContentSynchronization.XmlDoc.Abstract; -using rubberduckvba.com.Server.Services; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncTags; +using rubberduckvba.Server.ContentSynchronization.XmlDoc.Abstract; +using rubberduckvba.Server.Services; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline; public class SynchronizeTagsPipeline : PipelineBase, ISynchronizationPipeline { diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/SynchronizeXmlPipeline.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/SynchronizeXmlPipeline.cs index 33eebf0..4e50ac3 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/SynchronizeXmlPipeline.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/SynchronizeXmlPipeline.cs @@ -1,9 +1,13 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; -using rubberduckvba.com.Server.ContentSynchronization.XmlDoc.Abstract; -using rubberduckvba.com.Server.Services; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; +using rubberduckvba.Server.ContentSynchronization.XmlDoc; +using rubberduckvba.Server.ContentSynchronization.XmlDoc.Abstract; +using rubberduckvba.Server.Data; +using rubberduckvba.Server.Model.Entity; +using rubberduckvba.Server.Services; -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline; +namespace rubberduckvba.Server.ContentSynchronization.Pipeline; public class SynchronizeXmlPipeline : PipelineBase, ISynchronizationPipeline { @@ -12,8 +16,16 @@ public class SynchronizeXmlPipeline : PipelineBase, ISynchron private readonly IXmlDocMerge _merge; private readonly IStagingServices _staging; private readonly IMarkdownFormattingService _markdown; + private readonly IRepository _inspections; + private readonly IRepository _quickfixes; + private readonly IRepository _annotations; + private readonly XmlDocAnnotationParser _annotationParser; + private readonly XmlDocQuickFixParser _quickFixParser; + private readonly XmlDocInspectionParser _inspectionParser; - public SynchronizeXmlPipeline(IRequestParameters parameters, ILogger logger, IRubberduckDbService content, IGitHubClientService github, IXmlDocMerge merge, IStagingServices staging, IMarkdownFormattingService markdown, CancellationTokenSource tokenSource) + public SynchronizeXmlPipeline(IRequestParameters parameters, ILogger logger, IRubberduckDbService content, IGitHubClientService github, IXmlDocMerge merge, IStagingServices staging, IMarkdownFormattingService markdown, CancellationTokenSource tokenSource, + IRepository inspections, IRepository quickfixes, IRepository annotations, + XmlDocAnnotationParser xmlAnnotationParser, XmlDocQuickFixParser xmlQuickFixParser, XmlDocInspectionParser xmlInspectionParser) : base(new SyncContext(parameters), tokenSource, logger) { _content = content; @@ -21,6 +33,13 @@ public SynchronizeXmlPipeline(IRequestParameters parameters, ILogger logger, IRu _merge = merge; _staging = staging; _markdown = markdown; + _inspections = inspections; + _quickfixes = quickfixes; + _annotations = annotations; + + _annotationParser = xmlAnnotationParser; + _quickFixParser = xmlQuickFixParser; + _inspectionParser = xmlInspectionParser; } public async Task> ExecuteAsync(SyncRequestParameters parameters, CancellationTokenSource tokenSource) @@ -32,7 +51,7 @@ public async Task> ExecuteAsync(SyncRequestParameters para } // 01. Create the pipeline sections - var synchronizeFeatureItems = new SyncXmldocSection(this, tokenSource, Logger, _content, _github, _merge, _staging, _markdown); + var synchronizeFeatureItems = new SyncXmldocSection(this, tokenSource, Logger, _content, _inspections, _quickfixes, _annotations, _github, _merge, _staging, _annotationParser, _quickFixParser, _inspectionParser); // 02. Wire up the pipeline AddSections(parameters, synchronizeFeatureItems); diff --git a/rubberduckvba.Server/ContentSynchronization/SyncRequestParameters.cs b/rubberduckvba.Server/ContentSynchronization/SyncRequestParameters.cs index d27ad93..4571e31 100644 --- a/rubberduckvba.Server/ContentSynchronization/SyncRequestParameters.cs +++ b/rubberduckvba.Server/ContentSynchronization/SyncRequestParameters.cs @@ -1,6 +1,6 @@ -using rubberduckvba.com.Server.Services; +using rubberduckvba.Server.Services; -namespace rubberduckvba.com.Server.ContentSynchronization; +namespace rubberduckvba.Server.ContentSynchronization; public interface IRequestParameters { @@ -18,11 +18,11 @@ public record class SyncRequestParameters : IRequestParameters public string? Token { get; init; } } -public record class XmldocSyncRequestParameters : SyncRequestParameters +public record class XmldocSyncRequestParameters : SyncRequestParameters { } -public record class TagSyncRequestParameters : SyncRequestParameters +public record class TagSyncRequestParameters : SyncRequestParameters { public string? Tag { get; init; } } diff --git a/rubberduckvba.Server/ContentSynchronization/XmlDoc/Abstract/IXmlDocMerge.cs b/rubberduckvba.Server/ContentSynchronization/XmlDoc/Abstract/IXmlDocMerge.cs index dbf54e7..a085819 100644 --- a/rubberduckvba.Server/ContentSynchronization/XmlDoc/Abstract/IXmlDocMerge.cs +++ b/rubberduckvba.Server/ContentSynchronization/XmlDoc/Abstract/IXmlDocMerge.cs @@ -1,8 +1,10 @@ -using rubberduckvba.com.Server.Data; +using rubberduckvba.Server.Model; -namespace rubberduckvba.com.Server.ContentSynchronization.XmlDoc.Abstract; +namespace rubberduckvba.Server.ContentSynchronization.XmlDoc.Abstract; public interface IXmlDocMerge { - IEnumerable Merge(IDictionary dbItems, IEnumerable main, IEnumerable next); + IEnumerable Merge(IDictionary dbItems, IEnumerable main, IEnumerable next); + IEnumerable Merge(IDictionary dbItems, IEnumerable main, IEnumerable next); + IEnumerable Merge(IDictionary dbItems, IEnumerable main, IEnumerable next); } diff --git a/rubberduckvba.Server/ContentSynchronization/XmlDoc/Abstract/IXmlDocParser.cs b/rubberduckvba.Server/ContentSynchronization/XmlDoc/Abstract/IXmlDocParser.cs index 284cedf..5b6cf88 100644 --- a/rubberduckvba.Server/ContentSynchronization/XmlDoc/Abstract/IXmlDocParser.cs +++ b/rubberduckvba.Server/ContentSynchronization/XmlDoc/Abstract/IXmlDocParser.cs @@ -1,11 +1,14 @@ -using rubberduckvba.com.Server.Data; +using rubberduckvba.Server.Model; +using System.Xml.Linq; -namespace rubberduckvba.com.Server.ContentSynchronization.XmlDoc.Abstract; +namespace rubberduckvba.Server.ContentSynchronization.XmlDoc.Abstract; public interface IXmlDocParser { string AssetName { get; } - Task> ParseAsync(TagGraph tag); + IEnumerable ParseInspections(XDocument document); + IEnumerable ParseQuickFixes(XDocument document); + IEnumerable ParseAnnotations(XDocument document); } /// diff --git a/rubberduckvba.Server/ContentSynchronization/XmlDoc/Abstract/XmlDocParserBase.cs b/rubberduckvba.Server/ContentSynchronization/XmlDoc/Abstract/XmlDocParserBase.cs index ff81f51..f2838ec 100644 --- a/rubberduckvba.Server/ContentSynchronization/XmlDoc/Abstract/XmlDocParserBase.cs +++ b/rubberduckvba.Server/ContentSynchronization/XmlDoc/Abstract/XmlDocParserBase.cs @@ -1,7 +1,7 @@ -using System.Xml.Linq; -using rubberduckvba.com.Server.Data; +using rubberduckvba.Server.Model; +using System.Xml.Linq; -namespace rubberduckvba.com.Server.ContentSynchronization.XmlDoc.Abstract; +namespace rubberduckvba.Server.ContentSynchronization.XmlDoc.Abstract; public abstract class XmlDocParserBase : IXmlDocParser { @@ -12,11 +12,11 @@ protected XmlDocParserBase(string assetName) public string AssetName { get; } - public async Task> ParseAsync(TagGraph tag) + public async Task GetTagAssetXDocumentAsync(TagGraph tag) { if (tag is null) { - return Enumerable.Empty(); + throw new ArgumentNullException(nameof(tag)); } var asset = tag.Assets.SingleOrDefault(a => a.Name.Contains(AssetName)) @@ -27,34 +27,29 @@ public async Task> ParseAsync(TagGraph tag) throw new UriFormatException($"Unexpected host in download URL '{uri}' from asset ID {asset.Id} (tag ID {tag.Id}, '{tag.Name}')."); } - using (var client = new HttpClient()) - using (var response = await client.GetAsync(uri)) - { - if (response.IsSuccessStatusCode) - { - XDocument document; - using (var stream = await response.Content.ReadAsStreamAsync()) - { - document = XDocument.Load(stream, LoadOptions.None); - } - var items = await ParseAsync(asset.Id, tag.Name, document, tag.IsPreRelease); - return items; - } - } + using var client = new HttpClient(); + using var response = await client.GetAsync(uri); - return Enumerable.Empty(); - } + response.EnsureSuccessStatusCode(); + using var stream = await response.Content.ReadAsStreamAsync(); - protected abstract Task> ParseAsync(int assetId, string tagName, XDocument document, bool isPreRelease); + return XDocument.Load(stream, LoadOptions.None); + } - protected static string GetFullTypeNameOrDefault(XElement memberNode, string suffix) + protected static string? GetFullTypeNameOrDefault(XElement memberNode, string suffix) { var name = memberNode.Attribute("name")?.Value; - if (name == null || !name.StartsWith("T:") || !name.EndsWith(suffix)) + if (name is null || !name.StartsWith("T:") || !name.EndsWith(suffix)) { return default; } - return name.Substring(2); + return name[2..]; } + + public virtual IEnumerable ParseInspections(XDocument document) => []; + + public virtual IEnumerable ParseQuickFixes(XDocument document) => []; + + public virtual IEnumerable ParseAnnotations(XDocument document) => []; } diff --git a/rubberduckvba.Server/ContentSynchronization/XmlDoc/BeforeAndAfterCodeExample.cs b/rubberduckvba.Server/ContentSynchronization/XmlDoc/BeforeAndAfterCodeExample.cs index 36101af..9c2f652 100644 --- a/rubberduckvba.Server/ContentSynchronization/XmlDoc/BeforeAndAfterCodeExample.cs +++ b/rubberduckvba.Server/ContentSynchronization/XmlDoc/BeforeAndAfterCodeExample.cs @@ -1,6 +1,6 @@ -using rubberduckvba.com.Server.Data; +using rubberduckvba.Server.Model; -namespace rubberduckvba.com.Server.ContentSynchronization.XmlDoc; +namespace rubberduckvba.Server.ContentSynchronization.XmlDoc; public class BeforeAndAfterCodeExample { @@ -13,10 +13,11 @@ public BeforeAndAfterCodeExample(IEnumerable modulesBefore, IEnum public IEnumerable ModulesBefore { get; } public IEnumerable ModulesAfter { get; } - public Example AsExample(string description = "", int sortOrder = 0) => new() + public Example AsExample(string description = "", int sortOrder = 0) => new QuickFixExample() { - Properties = description, + Description = description, SortOrder = sortOrder, - Modules = ModulesBefore.Concat(ModulesAfter).ToList() + ModulesBefore = ModulesBefore.ToList(), + ModulesAfter = ModulesAfter.ToList() }; } diff --git a/rubberduckvba.Server/ContentSynchronization/XmlDoc/CodeAnalysisXmlDocParser.cs b/rubberduckvba.Server/ContentSynchronization/XmlDoc/CodeAnalysisXmlDocParser.cs deleted file mode 100644 index b88bb84..0000000 --- a/rubberduckvba.Server/ContentSynchronization/XmlDoc/CodeAnalysisXmlDocParser.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System.Xml.Linq; -using System.Collections.Concurrent; -using rubberduckvba.com.Server.ContentSynchronization.XmlDoc.Schema; -using rubberduckvba.com.Server.ContentSynchronization.XmlDoc.Abstract; -using rubberduckvba.com.Server.Data; -using rubberduckvba.com.Server.Services; -using RubberduckServices; - -namespace rubberduckvba.com.Server.ContentSynchronization.XmlDoc; - -public interface ICodeAnalysisXmlDocParserFactory -{ - /// - /// Creates a new CodeAnalysisXmlDocParser service instance. - /// - /// Default inspection severity levels, obtained from Rubberduck.WebApi.Services.Abstract.IGitHubDataService - IXmlDocParser CreateXmlDocParser(IEnumerable inspectionDefaults); -} - -public class CodeAnalysisXmlDocParserFactory : ICodeAnalysisXmlDocParserFactory -{ - private readonly IRubberduckDbService _content; - private readonly IMarkdownFormattingService _markdown; - - public CodeAnalysisXmlDocParserFactory(IRubberduckDbService content, IMarkdownFormattingService markdown) - { - _content = content; - _markdown = markdown; - } - - public IXmlDocParser CreateXmlDocParser(IEnumerable inspectionDefaults) - { - return new CodeAnalysisXmlDocParser(_content, _markdown, inspectionDefaults); - } -} - - -public class CodeAnalysisXmlDocParser : XmlDocParserBase, ICodeAnalysisXmlDocParser -{ - private readonly IRubberduckDbService _content; - private readonly IMarkdownFormattingService _markdownService; - private readonly IDictionary _inspectionDefaults; - - public CodeAnalysisXmlDocParser(IRubberduckDbService content, IMarkdownFormattingService markdownService, IEnumerable inspectionDefaults) - : base("Rubberduck.CodeAnalysis.xml") - { - _content = content; - _markdownService = markdownService; - _inspectionDefaults = inspectionDefaults.ToDictionary(e => e.InspectionName, e => e); - } - - protected override async Task> ParseAsync(int assetId, string tagName, XDocument document, bool isPreRelease) - { - var quickfixesFeatureId = await _content.GetFeatureId(RepositoryId.Rubberduck, "QuickFixes") - ?? throw new InvalidOperationException("Could not retrieve a FeatureId for the 'QuickFixes' feature."); - var quickFixes = ReadQuickFixes(assetId, tagName, quickfixesFeatureId, document, !isPreRelease); - - var inspectionsFeatureId = await _content.GetFeatureId(RepositoryId.Rubberduck, "Inspections") - ?? throw new InvalidOperationException("Could not retrieve a FeatureId for the 'Inspections' feature."); - var inspections = ReadInspections(assetId, tagName, inspectionsFeatureId, document, !isPreRelease, quickFixes); - - return inspections.Concat(quickFixes); - } - - private IEnumerable ReadInspections(int assetId, string tagName, int featureId, XDocument doc, bool hasReleased, IEnumerable quickFixes) - { - var nodes = from node in doc.Descendants("member").AsParallel() - let name = GetFullTypeNameOrDefault(node, "Inspection") - where !string.IsNullOrWhiteSpace(name) - let inspectionName = name.Substring(name.LastIndexOf(".", StringComparison.Ordinal) + 1) - let config = _inspectionDefaults.ContainsKey(inspectionName) ? _inspectionDefaults[inspectionName] : default - select (name, node, config); - - var results = new ConcurrentBag>(); - Parallel.ForEach(nodes, info => - { - var xmldoc = new XmlDocInspection(_markdownService); - results.Add(xmldoc.ParseAsync(assetId, tagName, featureId, quickFixes, info.name, info.node, info.config, !hasReleased)); - }); - - return results.Select(t => t.GetAwaiter().GetResult()); - } - - private IEnumerable ReadQuickFixes(int assetId, string tagName, int featureId, XDocument doc, bool hasReleased) - { - var nodes = from node in doc.Descendants("member").AsParallel() - let name = GetFullTypeNameOrDefault(node, "QuickFix") - where !string.IsNullOrEmpty(name) && node.Descendants(XmlDocSchema.QuickFix.CanFix.ElementName).Any() // this excludes any quickfixes added to main (master) prior to v2.5.0 - select (name, node); - - var results = new ConcurrentBag(); - Parallel.ForEach(nodes, info => - { - var xmldoc = new XmlDocQuickFix(info.name, info.node, !hasReleased); - results.Add(xmldoc.Parse(assetId, featureId)); - }); - - return results; - } -} diff --git a/rubberduckvba.Server/ContentSynchronization/XmlDoc/ParsingXmlDocParser.cs b/rubberduckvba.Server/ContentSynchronization/XmlDoc/ParsingXmlDocParser.cs deleted file mode 100644 index 5041a45..0000000 --- a/rubberduckvba.Server/ContentSynchronization/XmlDoc/ParsingXmlDocParser.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Xml.Linq; -using System.Collections.Concurrent; -using rubberduckvba.com.Server.ContentSynchronization.XmlDoc.Abstract; -using rubberduckvba.com.Server.Data; -using rubberduckvba.com.Server.Services; - -namespace rubberduckvba.com.Server.ContentSynchronization.XmlDoc; - -public class ParsingXmlDocParser : XmlDocParserBase, IParsingXmlDocParser -{ - private readonly IRubberduckDbService _content; - - public ParsingXmlDocParser(IRubberduckDbService content) - : base("Rubberduck.Parsing.xml") - { - _content = content; - } - - protected override async Task> ParseAsync(int assetId, string tagName, XDocument document, bool isPreRelease) - { - var featureId = await _content.GetFeatureId(RepositoryId.Rubberduck, "Annotations") - ?? throw new InvalidOperationException("Could not retrieve a FeatureId for the 'Annotations' feature."); - return await Task.FromResult(ReadAnnotations(assetId, tagName, featureId, document, !isPreRelease)); - } - - private IEnumerable ReadAnnotations(int assetId, string tagName, int featureId, XDocument doc, bool hasReleased) - { - var nodes = from node in doc.Descendants("member").AsParallel() - let name = GetFullTypeNameOrDefault(node, "Annotation") - where !string.IsNullOrWhiteSpace(name) - select (name, node); - - var results = new ConcurrentBag(); - Parallel.ForEach(nodes, info => - { - var xmldoc = new XmlDocAnnotation(info.name, info.node, !hasReleased); - var item = xmldoc.Parse(assetId, featureId); - results.Add(item); - }); - - return results; - } -} diff --git a/rubberduckvba.Server/ContentSynchronization/XmlDoc/Schema/XmlDocSchema.cs b/rubberduckvba.Server/ContentSynchronization/XmlDoc/Schema/XmlDocSchema.cs index 31564cc..8f680ab 100644 --- a/rubberduckvba.Server/ContentSynchronization/XmlDoc/Schema/XmlDocSchema.cs +++ b/rubberduckvba.Server/ContentSynchronization/XmlDoc/Schema/XmlDocSchema.cs @@ -1,4 +1,4 @@ -namespace rubberduckvba.com.Server.ContentSynchronization.XmlDoc.Schema; +namespace rubberduckvba.Server.ContentSynchronization.XmlDoc.Schema; public static class XmlDocSchema { @@ -61,6 +61,7 @@ public static class QuickFix public static class Summary { public static readonly string ElementName = "summary"; + public static readonly string IsHiddenAttribute = "hidden"; } public static class Remarks @@ -74,6 +75,8 @@ public static class CanFix public static readonly string ProcedureAttribute = "procedure"; public static readonly string ModuleAttribute = "module"; public static readonly string ProjectAttribute = "project"; + public static readonly string MultipleAttribute = "multiple"; + public static readonly string AllAttribute = "all"; } public static class Inspections @@ -122,6 +125,7 @@ public static class Annotation public static class Summary { public static readonly string ElementName = "summary"; + public static readonly string IsHiddenAttribute = "hidden"; } public static class Remarks @@ -134,6 +138,7 @@ public static class Parameter public static readonly string ElementName = "parameter"; public static readonly string NameAttribute = "name"; public static readonly string TypeAttribute = "type"; + public static readonly string RequiredAttribute = "required"; } public static class Example diff --git a/rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocAnnotation.cs b/rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocAnnotationParser.cs similarity index 61% rename from rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocAnnotation.cs rename to rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocAnnotationParser.cs index 762f116..17d79f7 100644 --- a/rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocAnnotation.cs +++ b/rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocAnnotationParser.cs @@ -1,77 +1,57 @@ -using Newtonsoft.Json; -using rubberduckvba.com.Server.ContentSynchronization.XmlDoc.Schema; -using rubberduckvba.com.Server.Data; +using rubberduckvba.Server.ContentSynchronization.XmlDoc.Schema; +using rubberduckvba.Server.Model; using System.Reflection; using System.Xml.Linq; -namespace rubberduckvba.com.Server.ContentSynchronization.XmlDoc; +namespace rubberduckvba.Server.ContentSynchronization.XmlDoc; -public class AnnotationProperties +public class XmlDocAnnotationParser { - public AnnotationArgInfo[] Parameters { get; set; } = []; -} - -public class XmlDocAnnotation -{ - public XmlDocAnnotation(string name, XElement node, bool isPreRelease) + public Annotation Parse(int assetId, int featureId, string name, XElement node, bool isPreRelease) { - SourceObject = node.Attribute("name")!.Value.Substring(2).Substring(name.LastIndexOf(".", StringComparison.Ordinal) + 1); - IsPreRelease = isPreRelease; + var sourceObject = node.Attribute("name")!.Value[2..][(name.LastIndexOf('.') + 1)..]; + //var sourceEditUrl = $"https://github.com/rubberduck-vba/Rubberduck/edit/next/{sourceObject}.cs"; + //var sourceViewUrl = $"https://github.com/rubberduck-vba/Rubberduck/blob/{tagName}/{sourceObject}.cs"; + + var summary = node.Element(XmlDocSchema.Annotation.Summary.ElementName)?.Value.Trim() ?? string.Empty; + var remarks = node.Element(XmlDocSchema.Annotation.Remarks.ElementName)?.Value.Trim() ?? string.Empty; - AnnotationName = name; - Summary = node.Element(XmlDocSchema.Annotation.Summary.ElementName)?.Value.Trim() ?? string.Empty; - Remarks = node.Element(XmlDocSchema.Annotation.Remarks.ElementName)?.Value.Trim() ?? string.Empty; + var isHidden = node.Element(XmlDocSchema.Annotation.Summary.ElementName)?.Attribute(XmlDocSchema.Annotation.Summary.IsHiddenAttribute)?.Value.Equals(true.ToString(), StringComparison.InvariantCultureIgnoreCase) ?? false; - Parameters = node.Elements(XmlDocSchema.Annotation.Parameter.ElementName) + var parameters = node.Elements(XmlDocSchema.Annotation.Parameter.ElementName) .Select(e => (Name: e.Attribute(XmlDocSchema.Annotation.Parameter.NameAttribute)?.Value ?? string.Empty, + Required: e.Attribute(XmlDocSchema.Annotation.Parameter.RequiredAttribute)?.Value ?? string.Empty, Type: e.Attribute(XmlDocSchema.Annotation.Parameter.TypeAttribute)?.Value ?? string.Empty, Description: e.Value)) - .Select(e => new AnnotationArgInfo(e.Name, e.Type, e.Description)) + .Select(e => new AnnotationParameter { Name = e.Name, Type = e.Type, Description = e.Description, Required = e.Required == true.ToString() }) .ToArray(); - Properties = JsonConvert.SerializeObject(Parameters); - - Examples = ParseExamples(node).ToArray(); - } - - public string SourceObject { get; } - public string Properties { get; } - public bool IsPreRelease { get; } - - public string AnnotationName { get; } - public string Summary { get; } - public string Remarks { get; } + var examples = ParseExamples(node).ToArray(); - public IReadOnlyList Parameters { get; } - public IReadOnlyList Examples { get; } - - public FeatureXmlDoc Parse(int assetId, int featureId) - { - var parameters = new AnnotationProperties - { - Parameters = Parameters.ToArray() - }; - - return new FeatureXmlDoc + return new Annotation { FeatureId = featureId, - Name = AnnotationName, - IsNew = IsPreRelease, - Title = $"@{AnnotationName}", - Summary = Summary, TagAssetId = assetId, - SourceUrl = SourceObject, - Serialized = JsonConvert.SerializeObject(parameters, Formatting.None), + SourceUrl = sourceObject, + + Name = name, + Summary = summary, + Remarks = remarks, + + IsNew = isPreRelease, + IsDiscontinued = default, + IsHidden = isHidden, - Examples = Examples.Select((e, i) => e.AsExample(string.Empty, i)).ToList() + Parameters = parameters, + Examples = examples }; } - private BeforeAndAfterCodeExample[] ParseExamples(XElement node) + private IEnumerable ParseExamples(XElement node) { try { - var examples = new List(); + var examples = new List(); var exampleNodes = node.Elements(XmlDocSchema.Annotation.Example.ElementName); foreach (var example in exampleNodes) { @@ -84,15 +64,15 @@ private BeforeAndAfterCodeExample[] ParseExamples(XElement node) */ var modules = example.Elements(XmlDocSchema.Annotation.Example.Module.ElementName).AsParallel(); var simpleExamples = modules.Where(m => m.Nodes().OfType().Any()) - .Select((e, i) => new BeforeAndAfterCodeExample([ExtractCodeModule(e, i)], modulesAfter: [])) + .Select((e, i) => new AnnotationExample { Modules = [ExtractCodeModule(e, i)] }) .ToArray(); if (simpleExamples.Length > 0) { - examples.AddRange(simpleExamples.ToArray()); + examples.AddRange(simpleExamples); continue; } - IEnumerable before = Enumerable.Empty(); + IEnumerable before = []; IEnumerable? after = null; if (modules.Any()) @@ -111,7 +91,6 @@ private BeforeAndAfterCodeExample[] ParseExamples(XElement node) * */ before = modules.Select((e, i) => ExtractCodeModule(e.Element(XmlDocSchema.Annotation.Example.Module.Before.ElementName)!, i, "(code pane)")); - after = modules.Select((e, i) => ExtractCodeModule(e.Element(XmlDocSchema.Annotation.Example.Module.After.ElementName)!, i, "(synchronized, hidden attributes shown)")); } if (example.Elements(XmlDocSchema.Annotation.Example.Before.ElementName).Any()) @@ -133,21 +112,19 @@ private BeforeAndAfterCodeExample[] ParseExamples(XElement node) */ before = example.Elements(XmlDocSchema.Annotation.Example.Before.ElementName) .Select((e, i) => ExtractCodeModule(e.Element(XmlDocSchema.Annotation.Example.Before.Module.ElementName)!, i, "(code pane)")); - after = example.Elements(XmlDocSchema.Annotation.Example.After.ElementName) - .Select((e, i) => ExtractCodeModule(e.Element(XmlDocSchema.Annotation.Example.After.Module.ElementName)!, i, "(synchronized, hidden attributes shown)")); } - if (before.Any() && after.Any()) + if (before.Any() && (after?.Any() ?? false)) { - examples.Add(new BeforeAndAfterCodeExample(before, after)); + examples.Add(new AnnotationExample { ModulesBefore = before.ToArray(), ModulesAfter = after.ToArray() }); } } - return examples.ToArray(); + return examples; } catch (Exception) { var errorExample = new[] { ExampleModule.ParseError("AnnotationExample") }; - return new[] { new BeforeAndAfterCodeExample(errorExample, errorExample) }; + return [new AnnotationExample { Modules = errorExample }]; } } @@ -185,7 +162,7 @@ private ExampleModule ExtractCodeModule(XElement cdataParent, int index, string? { ModuleName = name, ModuleType = moduleType, - Properties = $"\"Description\": \"{description}\"".Replace(" ", " "), + Description = description, HtmlContent = code.Trim().Replace(" ", " ") }; diff --git a/rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocInspection.cs b/rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocInspection.cs deleted file mode 100644 index 7da610b..0000000 --- a/rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocInspection.cs +++ /dev/null @@ -1,182 +0,0 @@ -using rubberduckvba.com.Server.ContentSynchronization.XmlDoc.Schema; -using rubberduckvba.com.Server.Data; -using rubberduckvba.com.Server.Services; -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; -using System.Reflection; -using System.Text.Json; -using System.Xml.Linq; - -namespace rubberduckvba.com.Server.ContentSynchronization.XmlDoc; - -public record class InspectionProperties -{ - public string Reasoning { get; init; } = default!; - public string[] References { get; init; } = []; - public string[] QuickFixes { get; init; } = []; - public string HostApp { get; init; } = default!; - public string Summary { get; init; } = default!; - public string Remarks { get; init; } = default!; - public string DefaultSeverity { get; init; } = default!; - public string InspectionType { get; init; } = default!; - public Example[] Examples { get; init; } = []; -} - -public class XmlDocInspection -{ - private static readonly string _defaultSeverity = "Warning"; - private static readonly string _defaultInspectionType = "CodeQualityIssues"; - private readonly IMarkdownFormattingService _markdownService; - - public XmlDocInspection(IMarkdownFormattingService markdownService) - { - _markdownService = markdownService; - } - - public string Properties { get; private set; } - - public string SourceObject { get; } - public bool IsPreRelease { get; } - - public bool IsHidden { get; } - public string TypeName { get; } - public string InspectionName { get; } - public string Summary { get; } - public string[] References { get; } - public string HostApp { get; } - public string Reasoning { get; } - public string Remarks { get; } - public string InspectionType { get; } - public string DefaultSeverity { get; } - - public Example[] Examples { get; } - - public async Task ParseAsync(int assetId, string tagName, int featureId, IEnumerable quickFixes, string name, XElement node, InspectionDefaultConfig? config, bool isPreRelease) - { - var typeName = name.Substring(name.LastIndexOf(".", StringComparison.Ordinal) + 1); - var inspectionName = typeName.Replace("Inspection", string.Empty).Trim(); - - var fixes = quickFixes.Select(e => (e.Name, e.Serialized)); - var fixesByName = fixes.ToLookup(e => e.Name, e => JsonSerializer.Deserialize(e.Serialized, new JsonSerializerOptions { PropertyNameCaseInsensitive = true })?.Inspections); - var filteredFixes = quickFixes.Where(fix => fixesByName[fix.Name].FirstOrDefault()?.Contains(inspectionName) ?? false).ToList(); - - var sourceObject = name.Substring(2).Replace(".", "/").Replace("Rubberduck/CodeAnalysis/", "Rubberduck.CodeAnalysis/"); - var sourceEditUrl = $"https://github.com/rubberduck-vba/Rubberduck/edit/next/{sourceObject}.cs"; - var sourceViewUrl = $"https://github.com/rubberduck-vba/Rubberduck/blob/{tagName}/{sourceObject}.cs"; - - var summaryTask = _markdownService.FormatMarkdownDocument(node.Element(XmlDocSchema.Inspection.Summary.ElementName)?.Value.Trim() ?? string.Empty); - var reasoningTask = _markdownService.FormatMarkdownDocument(node.Element(XmlDocSchema.Inspection.Reasoning.ElementName)?.Value.Trim() ?? string.Empty); - var remarksTask = _markdownService.FormatMarkdownDocument(node.Element(XmlDocSchema.Inspection.Remarks.ElementName)?.Value.Trim() ?? string.Empty); - - var references = node.Elements(XmlDocSchema.Inspection.Reference.ElementName).Select(e => e.Attribute(XmlDocSchema.Inspection.Reference.NameAttribute)!.Value.Trim()).ToArray(); - var hostApp = node.Element(XmlDocSchema.Inspection.HostApp.ElementName)?.Attribute(XmlDocSchema.Inspection.HostApp.NameAttribute)?.Value.Trim() ?? string.Empty; - var isHidden = node.Element(XmlDocSchema.Inspection.Summary.ElementName)?.Attribute(XmlDocSchema.Inspection.Summary.IsHiddenAttribute)?.Value.Equals(true.ToString(), StringComparison.InvariantCultureIgnoreCase) ?? false; - - var defaultSeverity = config?.DefaultSeverity ?? _defaultSeverity; - var inspectionType = config?.InspectionType ?? _defaultInspectionType; - - if (Enum.TryParse(InspectionType, out var enumInspectionType)) - { - inspectionType = InspectionTypes[enumInspectionType]; - } - var examples = ParseExamples(node).ToArray(); - - var (summary, reasoning, remarks) = (await summaryTask, await reasoningTask, await remarksTask); - - var properties = new InspectionProperties - { - Reasoning = reasoning, - Summary = summary, - Remarks = remarks, - HostApp = hostApp, - DefaultSeverity = defaultSeverity, - InspectionType = inspectionType, - QuickFixes = filteredFixes.Select(e => e.Name).ToArray(), - References = references, - Examples = examples - }; - - return new FeatureXmlDoc - { - FeatureId = featureId, - FeatureName = "Inspections", - FeatureTitle = "Inspections", - - Id = default, - DateTimeInserted = DateTime.UtcNow, - DateTimeUpdated = default, - TagAssetId = assetId, - SourceUrl = sourceObject, - - Name = inspectionName, - Title = inspectionName, - Summary = summary, - - IsHidden = isHidden, - IsNew = isPreRelease, - IsDiscontinued = default, - - Examples = examples, - Serialized = JsonSerializer.Serialize(properties) - }; - } - - private static readonly IDictionary ModuleTypes = - Enum.GetValues() - .Select(m => (Name: m.ToString(), Description: typeof(ExampleModuleType).GetMember(m.ToString()).Single() - .GetCustomAttributes().OfType().SingleOrDefault()?.Description ?? string.Empty)) - .ToDictionary(m => m.Description, m => Enum.Parse(m.Name, ignoreCase: true)); - - private static readonly IDictionary InspectionTypes = - Enum.GetValues() - .Select(m => (Name: m.ToString(), Description: typeof(CodeInspectionType).GetMember(m.ToString()).Single() - .GetCustomAttributes().OfType().SingleOrDefault()?.Description ?? string.Empty)) - .ToDictionary(m => Enum.Parse(m.Name, ignoreCase: true), m => m.Description); - - private enum CodeInspectionType - { - [Display(Name = "Rubberduck Opportunities")] - RubberduckOpportunities, - [Display(Name = "Language Opportunities")] - LanguageOpportunities, - [Display(Name = "Maintainability/Readability Issues")] - MaintainabilityAndReadabilityIssues, - [Display(Name = "Code Quality Issues")] - CodeQualityIssues, - [Display(Name = "Performance Opportunities")] - Performance, - } - - private IEnumerable ParseExamples(XElement node) - { - return node.Elements(XmlDocSchema.Inspection.Example.ElementName).AsParallel() - .Select((e, i) => - { - var hasResult = e.Attributes().Any(a => string.Equals(a.Name.LocalName, XmlDocSchema.Inspection.Example.HasResultAttribute, StringComparison.InvariantCultureIgnoreCase) && bool.TryParse(a.Value, out var value) && value); - var example = new Example - { - Properties = $"{{\"HasResult\":\"{hasResult}\"}}", - SortOrder = i, - Modules = e.Elements(XmlDocSchema.Inspection.Example.Module.ElementName) - .Select(async m => - new ExampleModule - { - HtmlContent = await _markdownService.FormatMarkdownDocument("" + m.Nodes().OfType().Single().Value.Trim().Replace(" ", " ") + "", withSyntaxHighlighting: true), - ModuleName = m.Attribute(XmlDocSchema.Inspection.Example.Module.ModuleNameAttribute)?.Value ?? string.Empty, - ModuleType = ModuleTypes.TryGetValue(m.Attribute(XmlDocSchema.Inspection.Example.Module.ModuleTypeAttribute)?.Value ?? string.Empty, out var type) ? type : ExampleModuleType.Any, - }) - .Select(t => t.GetAwaiter().GetResult()) - .Concat(e.Nodes().OfType().Select(async x => - new ExampleModule - { - HtmlContent = await _markdownService.FormatMarkdownDocument(x.Value, withSyntaxHighlighting: true), - ModuleName = "Module1", - ModuleType = ExampleModuleType.Any - }) - .Select(t => t.GetAwaiter().GetResult()) - .Take(1)).ToList() - }; - return example; - }); - } -} diff --git a/rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocInspectionParser.cs b/rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocInspectionParser.cs new file mode 100644 index 0000000..4f5ed6b --- /dev/null +++ b/rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocInspectionParser.cs @@ -0,0 +1,124 @@ +using rubberduckvba.Server.ContentSynchronization.XmlDoc.Schema; +using rubberduckvba.Server.Model; +using rubberduckvba.Server.Services; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Reflection; +using System.Xml.Linq; + +namespace rubberduckvba.Server.ContentSynchronization.XmlDoc; + +public class XmlDocInspectionParser(IMarkdownFormattingService markdownService) +{ + private static readonly string _defaultSeverity = "Warning"; + private static readonly string _defaultInspectionType = "CodeQualityIssues"; + + public async Task ParseAsync(int assetId, int featureId, IEnumerable quickFixes, string name, XElement node, InspectionDefaultConfig? config, bool isPreRelease) + { + var typeName = name[(name.LastIndexOf('.') + 1)..]; + var inspectionName = typeName.Replace("Inspection", string.Empty).Trim(); + + var fixesByName = quickFixes.ToLookup(e => e.Name, e => e.Inspections); + var filteredFixes = quickFixes.Where(fix => fixesByName[fix.Name].FirstOrDefault()?.Contains(inspectionName) ?? false).ToList(); + + var sourceObject = name[2..].Replace('.', '/').Replace("Rubberduck/CodeAnalysis/", "Rubberduck.CodeAnalysis/"); + //var sourceEditUrl = $"https://github.com/rubberduck-vba/Rubberduck/edit/next/{sourceObject}.cs"; + //var sourceViewUrl = $"https://github.com/rubberduck-vba/Rubberduck/blob/{tagName}/{sourceObject}.cs"; + + var summaryTask = Task.Run(() => markdownService.FormatMarkdownDocument(node.Element(XmlDocSchema.Inspection.Summary.ElementName)?.Value.Trim() ?? string.Empty)); + var reasoningTask = Task.Run(() => markdownService.FormatMarkdownDocument(node.Element(XmlDocSchema.Inspection.Reasoning.ElementName)?.Value.Trim() ?? string.Empty)); + var remarksTask = Task.Run(() => markdownService.FormatMarkdownDocument(node.Element(XmlDocSchema.Inspection.Remarks.ElementName)?.Value.Trim() ?? string.Empty)); + + var references = node.Elements(XmlDocSchema.Inspection.Reference.ElementName).Select(e => e.Attribute(XmlDocSchema.Inspection.Reference.NameAttribute)!.Value.Trim()).ToArray(); + var hostApp = node.Element(XmlDocSchema.Inspection.HostApp.ElementName)?.Attribute(XmlDocSchema.Inspection.HostApp.NameAttribute)?.Value.Trim() ?? string.Empty; + var isHidden = node.Element(XmlDocSchema.Inspection.Summary.ElementName)?.Attribute(XmlDocSchema.Inspection.Summary.IsHiddenAttribute)?.Value.Equals(true.ToString(), StringComparison.InvariantCultureIgnoreCase) ?? false; + + var defaultSeverity = config?.DefaultSeverity ?? _defaultSeverity; + var inspectionType = config?.InspectionType ?? _defaultInspectionType; + + if (Enum.TryParse(inspectionType, out var enumInspectionType)) + { + inspectionType = InspectionTypes[enumInspectionType]; + } + + var examples = ParseExamples(node).ToArray(); + var (summary, reasoning, remarks) = (await summaryTask, await reasoningTask, await remarksTask); + + return new Inspection + { + FeatureId = featureId, + + Id = default, + DateTimeInserted = DateTime.UtcNow, + DateTimeUpdated = default, + TagAssetId = assetId, + SourceUrl = sourceObject, + + IsHidden = isHidden, + IsNew = isPreRelease, + IsDiscontinued = default, + + Name = inspectionName, + Summary = summary, + Reasoning = reasoning, + Remarks = remarks, + HostApp = hostApp, + DefaultSeverity = defaultSeverity, + InspectionType = inspectionType, + QuickFixes = filteredFixes.Select(e => e.Name).ToArray(), + References = references, + Examples = examples + }; + } + + private static readonly Dictionary ModuleTypes = + Enum.GetValues() + .Select(m => (Name: m.ToString(), Description: typeof(ExampleModuleType).GetMember(m.ToString()).Single() + .GetCustomAttributes().OfType().SingleOrDefault()?.Description ?? string.Empty)) + .ToDictionary(m => m.Description, m => Enum.Parse(m.Name, ignoreCase: true)); + + private static readonly Dictionary InspectionTypes = + Enum.GetValues() + .Select(m => (Name: m.ToString(), Description: typeof(CodeInspectionType).GetMember(m.ToString()).Single() + .GetCustomAttributes().OfType().SingleOrDefault()?.Description ?? string.Empty)) + .ToDictionary(m => Enum.Parse(m.Name, ignoreCase: true), m => m.Description); + + private enum CodeInspectionType + { + [Display(Name = "Rubberduck Opportunities")] + RubberduckOpportunities, + [Display(Name = "Language Opportunities")] + LanguageOpportunities, + [Display(Name = "Maintainability/Readability Issues")] + MaintainabilityAndReadabilityIssues, + [Display(Name = "Code Quality Issues")] + CodeQualityIssues, + [Display(Name = "Performance Opportunities")] + Performance, + } + + private IEnumerable ParseExamples(XElement node) => + node.Elements(XmlDocSchema.Inspection.Example.ElementName) + .AsParallel() + .Select((e, i) => new InspectionExample + { + SortOrder = i, + HasResult = e.Attributes().Any(a => string.Equals(a.Name.LocalName, XmlDocSchema.Inspection.Example.HasResultAttribute, StringComparison.InvariantCultureIgnoreCase) && bool.TryParse(a.Value, out var value) && value), + Modules = e.Elements(XmlDocSchema.Inspection.Example.Module.ElementName) + .Select(m => + new ExampleModule + { + HtmlContent = markdownService.FormatMarkdownDocument("" + m.Nodes().OfType().Single().Value.Trim().Replace(" ", " ") + "", withSyntaxHighlighting: true), + ModuleName = m.Attribute(XmlDocSchema.Inspection.Example.Module.ModuleNameAttribute)?.Value ?? string.Empty, + ModuleType = ModuleTypes.TryGetValue(m.Attribute(XmlDocSchema.Inspection.Example.Module.ModuleTypeAttribute)?.Value.Trim() ?? string.Empty, out var type) ? type : ExampleModuleType.Any, + }) + .Concat(e.Nodes().OfType().Select(x => + new ExampleModule + { + HtmlContent = markdownService.FormatMarkdownDocument("" + x.Value.Trim().Replace(" ", " ") + "", withSyntaxHighlighting: true), + ModuleName = "Module1", + ModuleType = ExampleModuleType.Any + }) + .Take(1)).ToList() + }); +} diff --git a/rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocMerge.cs b/rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocMerge.cs index 0bc6a13..19491b3 100644 --- a/rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocMerge.cs +++ b/rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocMerge.cs @@ -1,28 +1,140 @@ -using rubberduckvba.com.Server.ContentSynchronization.XmlDoc.Abstract; -using rubberduckvba.com.Server.Data; +using rubberduckvba.Server.ContentSynchronization.XmlDoc.Abstract; +using rubberduckvba.Server.Data; +using rubberduckvba.Server.Model; -namespace rubberduckvba.com.Server.ContentSynchronization.XmlDoc; +namespace rubberduckvba.Server.ContentSynchronization.XmlDoc; public class XmlDocMerge(ILogger logger) : IXmlDocMerge { + //public IEnumerable Merge(IDictionary dbItems, IEnumerable main, IEnumerable next) where T : IFeature + //{ + // var mainBranch = main.ToHashSet(); + // var nextBranch = (next.Any() ? next : main).ToHashSet(); - public IEnumerable Merge(IDictionary dbItems, IEnumerable main, IEnumerable next) + // var merged = new HashSet(); + // var timestamp = TimeProvider.System.GetUtcNow().DateTime; + + // var updatedItems = new HashSet( + // from item in nextBranch + // where dbItems.ContainsKey(item.Name) + // let dbItem = dbItems[item.Name] + // where dbItem.GetContentHash() != item.GetContentHash() + // select item with + // { + // Id = dbItem.Id, + // IsNew = mainBranch.Any() && !mainBranch.Any(a => a.Name == item.Name), + // IsDiscontinued = dbItem.IsDiscontinued || !nextBranch.Any(a => a.Name == item.Name), + // DateTimeUpdated = timestamp + // } + // ); + + // var comparer = new XmlDocBranchIntersectComparer(); + // var insertItems = new HashSet( + // from item in nextBranch.Intersect(mainBranch, comparer) + // where !dbItems.ContainsKey(item.Name) + // select item with + // { + // IsNew = mainBranch.Any() && !mainBranch.Any(a => a.Name == item.Name), + // IsDiscontinued = !nextBranch.Any(a => a.Name == item.Name), + // DateTimeInserted = timestamp + // } + // ); + + // return merged; + //} + + public IEnumerable Merge(IDictionary dbItems, IEnumerable main, IEnumerable next) { - var mainBranch = main.ToHashSet(); - var nextBranch = (next.Any() ? next : main).ToHashSet(); + var mainBranch = (main.Any() ? main : dbItems.Values).ToHashSet(); + var nextBranch = (next.Any() ? next : mainBranch).ToHashSet(); + + var merged = new HashSet(); + var timestamp = TimeProvider.System.GetUtcNow().DateTime; + + var updatedItems = new HashSet( + from item in nextBranch + where dbItems.ContainsKey(item.Name) + let dbItem = dbItems[item.Name] + where dbItem.GetContentHash() != item.GetContentHash() + select item with + { + Id = dbItem.Id, + IsNew = mainBranch.Any() && !mainBranch.Any(a => a.Name == item.Name), + IsDiscontinued = dbItem.IsDiscontinued || !nextBranch.Any(a => a.Name == item.Name), + DateTimeUpdated = timestamp + } + ); + + var comparer = new XmlDocBranchIntersectComparer(); + var insertItems = new HashSet( + from item in nextBranch.Intersect(mainBranch, comparer) + where !dbItems.ContainsKey(item.Name) + select item with + { + IsNew = mainBranch.Any() && !mainBranch.Any(a => a.Name == item.Name), + IsDiscontinued = !nextBranch.Any(a => a.Name == item.Name), + DateTimeInserted = timestamp + } + ); + + merged.UnionWith(updatedItems); + merged.UnionWith(insertItems); + return merged; + } - var isInitialLoad = !dbItems.Any(); - var comparer = new XmlDocBranchIntersectComparer(); + public IEnumerable Merge(IDictionary dbItems, IEnumerable main, IEnumerable next) + { + var mainBranch = (main.Any() ? main : dbItems.Values).ToHashSet(); + var nextBranch = (next.Any() ? next : mainBranch).ToHashSet(); + + var merged = new HashSet(); var timestamp = TimeProvider.System.GetUtcNow().DateTime; - var merged = new HashSet(); + var updatedItems = new HashSet( + from item in nextBranch + where dbItems.ContainsKey(item.Name) + let dbItem = dbItems[item.Name] + where dbItem.GetContentHash() != item.GetContentHash() + select item with + { + Id = dbItem.Id, + IsNew = mainBranch.Any() && !mainBranch.Any(a => a.Name == item.Name), + IsDiscontinued = dbItem.IsDiscontinued || !nextBranch.Any(a => a.Name == item.Name), + DateTimeUpdated = timestamp + } + ); + + var comparer = new XmlDocBranchIntersectComparer(); + var insertItems = new HashSet( + from item in nextBranch.Intersect(mainBranch, comparer) + where !dbItems.ContainsKey(item.Name) + select item with + { + IsNew = mainBranch.Any() && !mainBranch.Any(a => a.Name == item.Name), + IsDiscontinued = !nextBranch.Any(a => a.Name == item.Name), + DateTimeInserted = timestamp + } + ); + + merged.UnionWith(updatedItems); + merged.UnionWith(insertItems); + return merged; + } + + public IEnumerable Merge(IDictionary dbItems, IEnumerable main, IEnumerable next) + { + var mainBranch = (main.Any() ? main : dbItems.Values).ToHashSet(); + var nextBranch = (next.Any() ? next : mainBranch).ToHashSet(); + + var merged = new HashSet(); + var timestamp = TimeProvider.System.GetUtcNow().DateTime; - var updatedItems = new HashSet( + var updatedItems = new HashSet( from item in nextBranch where dbItems.ContainsKey(item.Name) let dbItem = dbItems[item.Name] - where dbItem.Serialized != item.Serialized + where dbItem.GetContentHash() != item.GetContentHash() select item with { Id = dbItem.Id, @@ -32,7 +144,8 @@ select item with } ); - var insertItems = new HashSet( + var comparer = new XmlDocBranchIntersectComparer(); + var insertItems = new HashSet( from item in nextBranch.Intersect(mainBranch, comparer) where !dbItems.ContainsKey(item.Name) select item with diff --git a/rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocQuickFix.cs b/rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocQuickFix.cs deleted file mode 100644 index 517a6ee..0000000 --- a/rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocQuickFix.cs +++ /dev/null @@ -1,165 +0,0 @@ -using System.Reflection; -using System.Text.Json; -using System.Xml.Linq; -using rubberduckvba.com.Server.ContentSynchronization.XmlDoc.Schema; -using rubberduckvba.com.Server.Data; - -namespace rubberduckvba.com.Server.ContentSynchronization.XmlDoc; - -public class QuickFixProperties -{ - public string Summary { get; set; } - public string Remarks { get; set; } - public bool CanFixInProcedure { get; set; } - public bool CanFixInModule { get; set; } - public bool CanFixInProject { get; set; } - public string[] Inspections { get; set; } -} - -public class XmlDocQuickFix : IEquatable -{ - public XmlDocQuickFix() { } - public XmlDocQuickFix(string name, XElement node, bool isPreRelease) - { - SourceObject = name; - TypeName = name/*.Substring(name.LastIndexOf(".", StringComparison.Ordinal) + 1)/*.Replace("QuickFix", string.Empty)*/; - - SourceObject = node.Attribute("name")?.Value.Substring(2) ?? string.Empty; - QuickFixName = name; - - Summary = node.Element(XmlDocSchema.QuickFix.Summary.ElementName)?.Value.Trim().Replace(" ", " ") ?? string.Empty; - Remarks = node.Element(XmlDocSchema.QuickFix.Remarks.ElementName)?.Value.Trim().Replace(" ", " ") ?? string.Empty; - IsPreRelease = isPreRelease; - - var canFixNode = node.Element(XmlDocSchema.QuickFix.CanFix.ElementName); - CanFixInProcedure = Convert.ToBoolean(canFixNode?.Attribute(XmlDocSchema.QuickFix.CanFix.ProcedureAttribute)?.Value ?? true.ToString()); - CanFixInModule = Convert.ToBoolean(canFixNode?.Attribute(XmlDocSchema.QuickFix.CanFix.ModuleAttribute)?.Value ?? true.ToString()); - CanFixInProject = Convert.ToBoolean(canFixNode?.Attribute(XmlDocSchema.QuickFix.CanFix.ProjectAttribute)?.Value ?? true.ToString()); - - var nodes = node.Element(XmlDocSchema.QuickFix.Inspections.ElementName)? - .Elements(XmlDocSchema.QuickFix.Inspections.Inspection.ElementName); - - if (nodes is IEnumerable inspectionNodes) - { - Inspections = inspectionNodes - .Select(e => e.Attribute(XmlDocSchema.QuickFix.Inspections.Inspection.NameAttribute)?.Value.Replace("Inspection", string.Empty)) - .OfType() - .ToArray(); - } - - Info = new XmlDocQuickFixInfo - { - Summary = Summary, - Remarks = Remarks, - CanFixInProcedure = CanFixInProcedure, - CanFixInModule = CanFixInModule, - CanFixInProject = CanFixInProject, - Inspections = Inspections?.ToArray() ?? [], - }; - - Examples = ParseExamples(node).ToArray(); - } - - public XmlDocQuickFixInfo Info { get; init; } - - public FeatureXmlDoc Parse(int assetId, int featureId) - { - return new FeatureXmlDoc - { - FeatureId = featureId, - - Name = QuickFixName, - IsHidden = false, - IsNew = IsPreRelease, - Title = QuickFixName, - Summary = Summary.Trim().Replace(" ", " "), - TagAssetId = assetId, - SourceUrl = SourceObject, - Info = Info, - Examples = Examples.Select((e, i) => e.AsExample(string.Empty, i)).ToList(), - Serialized = JsonSerializer.Serialize(Info) - }; - } - - public IEnumerable Inspections { get; } = Enumerable.Empty(); - - public string SourceObject { get; init; } - public string TypeName { get; init; } - - public string QuickFixName { get; init; } - - public string Summary { get; init; } - public string Remarks { get; init; } - public bool IsPreRelease { get; init; } - - public bool CanFixInProcedure { get; init; } - public bool CanFixInModule { get; init; } - public bool CanFixInProject { get; init; } - - public BeforeAndAfterCodeExample[] Examples { get; init; } = []; - - public bool Equals(XmlDocQuickFix? other) => other?.QuickFixName == QuickFixName; - - public override bool Equals(object? obj) => Equals(obj as XmlDocQuickFix); - public override int GetHashCode() => QuickFixName.GetHashCode(); - - private static readonly IDictionary ModuleTypes = - typeof(ExampleModuleType) - .GetMembers() - .Select(m => (m.Name, Description: m.GetCustomAttributes().OfType().SingleOrDefault()?.Description ?? string.Empty)) - .Where(m => m.Description != null) - .ToDictionary(m => m.Description, m => (ExampleModuleType)Enum.Parse(typeof(ExampleModuleType), m.Name, true)); - - private IEnumerable ParseExamples(XElement node) - { - var results = new List(); - foreach (var exampleNode in node.Elements(XmlDocSchema.QuickFix.Example.ElementName)) - { - var before = exampleNode.Element(XmlDocSchema.QuickFix.Example.Before.ElementName)? - .Elements(XmlDocSchema.QuickFix.Example.Before.Module.ElementName)?.OfType().Select(m => - new ExampleModule - { - ModuleName = m.Attribute(XmlDocSchema.QuickFix.Example.Before.Module.ModuleNameAttribute)?.Value ?? string.Empty, - Properties = $"{{\"IsBefore\":\"true\"}}", - ModuleType = ModuleTypes.TryGetValue(m.Attribute(XmlDocSchema.QuickFix.Example.Before.Module.ModuleTypeAttribute)?.Value ?? string.Empty, out var type) ? type : ExampleModuleType.Any, - HtmlContent = m.Nodes().OfType().Single().Value.Trim().Replace(" ", " ") - }) - .Concat(exampleNode.Element(XmlDocSchema.QuickFix.Example.Before.ElementName)?.Nodes().OfType().Take(1).Select(x => - new ExampleModule - { - ModuleName = "Module1", - Properties = $"{{\"IsBefore\":\"true\"}}", - ModuleType = ExampleModuleType.Any, - HtmlContent = x.Value.Trim().Replace(" ", " ") - }) ?? []); - - var after = exampleNode.Element(XmlDocSchema.QuickFix.Example.After.ElementName)? - .Elements(XmlDocSchema.QuickFix.Example.After.Module.ElementName)?.Select(m => - new ExampleModule - { - ModuleName = m.Attribute(XmlDocSchema.QuickFix.Example.After.Module.ModuleNameAttribute)?.Value ?? string.Empty, - Properties = $"{{\"IsBefore\":\"false\"}}", - ModuleType = ModuleTypes.TryGetValue(m.Attribute(XmlDocSchema.QuickFix.Example.After.Module.ModuleTypeAttribute)?.Value ?? string.Empty, out var type) ? type : ExampleModuleType.Any, - HtmlContent = m.Nodes().OfType().Single().Value.Trim().Replace(" ", " ") - }) - .Concat(exampleNode.Element(XmlDocSchema.QuickFix.Example.After.ElementName)?.Nodes().OfType().Take(1).Select(x => - new ExampleModule - { - ModuleName = "Module1", - Properties = $"{{\"IsBefore\":\"false\"}}", - ModuleType = ExampleModuleType.Any, - HtmlContent = x.Value.Trim().Replace(" ", " ") - }) ?? []); - - if (before != null && after != null) - { - results.Add(new BeforeAndAfterCodeExample(before, after)); - } - else - { - // TODO log a warning, something. - } - } - return results; - } -} diff --git a/rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocQuickFixParser.cs b/rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocQuickFixParser.cs new file mode 100644 index 0000000..fa89574 --- /dev/null +++ b/rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocQuickFixParser.cs @@ -0,0 +1,118 @@ +using rubberduckvba.Server.ContentSynchronization.XmlDoc.Schema; +using rubberduckvba.Server.Model; +using System.Reflection; +using System.Xml.Linq; + +namespace rubberduckvba.Server.ContentSynchronization.XmlDoc; + +public class XmlDocQuickFixParser +{ + public QuickFix Parse(string name, int assetId, int featureId, XElement node, bool isPreRelease) + { + var sourceObject = name[2..].Replace('.', '/').Replace("Rubberduck/CodeAnalysis/", "Rubberduck.CodeAnalysis/"); + var typeName = name/*.Substring(name.LastIndexOf(".", StringComparison.Ordinal) + 1)/*.Replace("QuickFix", string.Empty)*/; + + var summary = node.Element(XmlDocSchema.QuickFix.Summary.ElementName)?.Value.Trim().Replace(" ", " ") ?? string.Empty; + var remarks = node.Element(XmlDocSchema.QuickFix.Remarks.ElementName)?.Value.Trim().Replace(" ", " ") ?? string.Empty; + + var canFixNode = node.Element(XmlDocSchema.QuickFix.CanFix.ElementName); + var canFixInProcedure = Convert.ToBoolean(canFixNode?.Attribute(XmlDocSchema.QuickFix.CanFix.ProcedureAttribute)?.Value ?? true.ToString()); + var canFixInModule = Convert.ToBoolean(canFixNode?.Attribute(XmlDocSchema.QuickFix.CanFix.ModuleAttribute)?.Value ?? true.ToString()); + var canFixInProject = Convert.ToBoolean(canFixNode?.Attribute(XmlDocSchema.QuickFix.CanFix.ProjectAttribute)?.Value ?? true.ToString()); + var canFixMultiple = Convert.ToBoolean(canFixNode?.Attribute(XmlDocSchema.QuickFix.CanFix.MultipleAttribute)?.Value ?? true.ToString()); + var canFixAll = Convert.ToBoolean(canFixNode?.Attribute(XmlDocSchema.QuickFix.CanFix.AllAttribute)?.Value ?? true.ToString()); + + var nodes = node.Element(XmlDocSchema.QuickFix.Inspections.ElementName)? + .Elements(XmlDocSchema.QuickFix.Inspections.Inspection.ElementName); + + string[] inspections = []; + if (nodes is IEnumerable quickfixNodes) + { + inspections = quickfixNodes + .Select(e => e.Attribute(XmlDocSchema.QuickFix.Inspections.Inspection.NameAttribute)?.Value.Replace("Inspection", string.Empty)) + .OfType() + .ToArray(); + } + + return new QuickFix + { + FeatureId = featureId, + TagAssetId = assetId, + SourceUrl = sourceObject, + + Name = name, + Summary = summary.Trim().Replace(" ", " "), + Remarks = remarks, + + IsHidden = false, + IsNew = isPreRelease, // if initially true, will be set to false if same quickfix exists in regular release branch (main) + IsDiscontinued = default, // initially false, will be set to true if same quickfix does not exist in pre-release branch (next) + + CanFixAll = canFixAll, + CanFixMultiple = canFixMultiple, + CanFixProcedure = canFixInProcedure, + CanFixModule = canFixInModule, + CanFixProject = canFixInProject, + + Examples = [.. ParseExamples(node)], + Inspections = inspections + }; + } + + + private static readonly Dictionary ModuleTypes = + typeof(ExampleModuleType) + .GetMembers() + .Select(m => (m.Name, Description: m.GetCustomAttributes().OfType().SingleOrDefault()?.Description ?? string.Empty)) + .Where(m => m.Description != null) + .ToDictionary(m => m.Description, m => (ExampleModuleType)Enum.Parse(typeof(ExampleModuleType), m.Name, true)); + + private static List ParseExamples(XElement node) + { + var examples = new List(); + foreach (var exampleNode in node.Elements(XmlDocSchema.QuickFix.Example.ElementName)) + { + var before = exampleNode.Element(XmlDocSchema.QuickFix.Example.Before.ElementName)? + .Elements(XmlDocSchema.QuickFix.Example.Before.Module.ElementName)?.OfType().Select(m => + new ExampleModule + { + ModuleName = m.Attribute(XmlDocSchema.QuickFix.Example.Before.Module.ModuleNameAttribute)?.Value ?? string.Empty, + ModuleType = ModuleTypes.TryGetValue(m.Attribute(XmlDocSchema.QuickFix.Example.Before.Module.ModuleTypeAttribute)?.Value ?? string.Empty, out var type) ? type : ExampleModuleType.Any, + HtmlContent = m.Nodes().OfType().Single().Value.Trim().Replace(" ", " ") + }) + .Concat(exampleNode.Element(XmlDocSchema.QuickFix.Example.Before.ElementName)?.Nodes().OfType().Take(1).Select(x => + new ExampleModule + { + ModuleName = "Module1", + ModuleType = ExampleModuleType.Any, + HtmlContent = x.Value.Trim().Replace(" ", " ") + }) ?? []); + + var after = exampleNode.Element(XmlDocSchema.QuickFix.Example.After.ElementName)? + .Elements(XmlDocSchema.QuickFix.Example.After.Module.ElementName)?.Select(m => + new ExampleModule + { + ModuleName = m.Attribute(XmlDocSchema.QuickFix.Example.After.Module.ModuleNameAttribute)?.Value ?? string.Empty, + ModuleType = ModuleTypes.TryGetValue(m.Attribute(XmlDocSchema.QuickFix.Example.After.Module.ModuleTypeAttribute)?.Value ?? string.Empty, out var type) ? type : ExampleModuleType.Any, + HtmlContent = m.Nodes().OfType().Single().Value.Trim().Replace(" ", " ") + }) + .Concat(exampleNode.Element(XmlDocSchema.QuickFix.Example.After.ElementName)?.Nodes().OfType().Take(1).Select(x => + new ExampleModule + { + ModuleName = "Module1", + ModuleType = ExampleModuleType.Any, + HtmlContent = x.Value.Trim().Replace(" ", " ") + }) ?? []); + + if (before != null && after != null) + { + examples.Add(new QuickFixExample { ModulesBefore = before.ToList(), ModulesAfter = after.ToList() }); + } + else + { + // TODO log a warning, something. + } + } + return examples; + } +} diff --git a/rubberduckvba.Server/Data/AnnotationsRepository.cs b/rubberduckvba.Server/Data/AnnotationsRepository.cs new file mode 100644 index 0000000..37d7d87 --- /dev/null +++ b/rubberduckvba.Server/Data/AnnotationsRepository.cs @@ -0,0 +1,55 @@ +using Microsoft.Extensions.Options; +using rubberduckvba.Server.Model.Entity; + +namespace rubberduckvba.Server.Data; + +public class AnnotationsRepository : Repository, IRepository +{ + public AnnotationsRepository(IOptions settings) + : base(settings) { } + + protected override string TableName { get; } = "Annotations"; + protected override string SelectSql { get; } = @" +SELECT + [Id], + [DateTimeInserted], + [DateTimeUpdated], + [FeatureId], + [TagAssetId], + [SourceUrl], + [Name], + [Remarks], + [JsonParameters], + [JsonExamples] +FROM [dbo].[Annotations]"; + + protected override string InsertSql { get; } = @" +INSERT INTO [dbo].[Annotations] ( + [DateTimeInserted], + [FeatureId], + [TagAssetId], + [SourceUrl], + [Name], + [Remarks], + [JsonParameters], + [JsonExamples]) +VALUES ( + GETDATE(), + @featureId, + @tagAssetId, + @sourceUrl, + @name, + @remarks, + @jsonParameters, + @jsonExamples)"; + + protected override string UpdateSql { get; } = @" +UPDATE [dbo].[Annotations] +SET [DateTimeUpdated]=GETDATE(), + [TagAssetId]=@tagAssetId, + [Remarks]=@remarks, + [JsonParameters]=@jsonParameters, + [JsonExamples]=@jsonExamples +WHERE [Id]=@id"; + +} \ No newline at end of file diff --git a/rubberduckvba.Server/Data/DTO.cs b/rubberduckvba.Server/Data/DTO.cs deleted file mode 100644 index 96120f1..0000000 --- a/rubberduckvba.Server/Data/DTO.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.ComponentModel; - -namespace rubberduckvba.com.Server.Data; - -public enum ExampleModuleType -{ - None = 0, - [Description("(Any)")] Any, - [Description("Class Module")] ClassModule, - [Description("Document Module")] DocumentModule, - [Description("Interface Module")] InterfaceModule, - [Description("Predeclared Class")] PredeclaredClass, - [Description("Standard Module")] StandardModule, - [Description("UserForm Module")] UserFormModule -} - -public record class ExampleModule -{ - public static ExampleModule ParseError(string name) => new() - { - ModuleName = name, - HtmlContent = "(error parsing code example from source xmldoc)" - }; - - public int ExampleId { get; init; } - public int SortOrder { get; init; } - public string ModuleName { get; init; } = default!; - public ExampleModuleType ModuleType { get; init; } - public string ModuleTypeName { get; init; } - public string Properties { get; init; } = default!; - public string HtmlContent { get; init; } = default!; - - public Example Example { get; init; } = default!; -} - -public record class Example -{ - public int FeatureItemId { get; init; } - public int SortOrder { get; set; } - public string Properties { get; init; } = default!; - - public FeatureXmlDoc FeatureItem { get; init; } = default!; - public ICollection Modules { get; init; } = []; -} diff --git a/rubberduckvba.Server/Data/DataModel.cs b/rubberduckvba.Server/Data/DataModel.cs deleted file mode 100644 index 27e3481..0000000 --- a/rubberduckvba.Server/Data/DataModel.cs +++ /dev/null @@ -1,202 +0,0 @@ -using rubberduckvba.com.Server.ContentSynchronization.XmlDoc; -using System.Diagnostics.CodeAnalysis; - -namespace rubberduckvba.com.Server.Data; - -public record class Tag -{ - public int Id { get; init; } - public DateTime DateTimeInserted { get; init; } - public DateTime? DateTimeUpdated { get; init; } - public long ReleaseId { get; init; } - public string Name { get; init; } = default!; - public DateTime DateCreated { get; init; } - public string InstallerDownloadUrl { get; init; } = default!; - public int InstallerDownloads { get; init; } - public bool IsPreRelease { get; init; } -} - -public record class TagAsset -{ - public int Id { get; init; } - public DateTime DateTimeInserted { get; init; } - public DateTime? DateTimeUpdated { get; init; } - public int TagId { get; init; } - public string Name { get; init; } = default!; - public string DownloadUrl { get; init; } = default!; -} - - -public record class TagGraph : Tag -{ - public IEnumerable Assets { get; init; } = []; -} - -public record class Feature -{ - public int Id { get; init; } - public int? ParentId { get; init; } - public DateTime DateTimeInserted { get; init; } - public DateTime? DateTimeUpdated { get; init; } - public int RepositoryId { get; init; } - public string Name { get; init; } = default!; - public string Title { get; init; } = default!; - public string ShortDescription { get; init; } = default!; - public string Description { get; init; } = default!; - public bool IsHidden { get; init; } - public bool IsNew { get; init; } - public bool HasImage { get; init; } -} - -public record class FeatureGraph : Feature -{ - public static FeatureGraph FromFeature(Feature feature) => new() - { - Id = feature.Id, - DateTimeInserted = feature.DateTimeInserted, - DateTimeUpdated = feature.DateTimeUpdated, - RepositoryId = feature.RepositoryId, - ParentId = feature.ParentId, - Name = feature.Name, - Title = feature.Title, - ShortDescription = feature.ShortDescription, - Description = feature.Description, - IsHidden = feature.IsHidden, - IsNew = feature.IsNew, - }; - - public string ParentName { get; init; } = default!; - public string ParentTitle { get; init; } = default!; - - public IEnumerable Features { get; init; } = []; - public IEnumerable Items { get; init; } = []; - - public IEnumerable Inspections { get; init; } = []; -} - -public record class FeatureXmlDoc : FeatureXmlDoc -{ - public TInfo? Info { get; init; } = default!; -} - -public record class FeatureXmlDoc -{ - public int Id { get; init; } - public DateTime DateTimeInserted { get; init; } - public DateTime? DateTimeUpdated { get; init; } - - public int FeatureId { get; init; } - public string FeatureName { get; init; } - public string FeatureTitle { get; init; } - - public string Name { get; init; } = default!; - public string Title { get; init; } = default!; - public string Summary { get; init; } = default!; - - public bool IsNew { get; init; } - public bool IsDiscontinued { get; init; } - public bool IsHidden { get; init; } - - public int TagAssetId { get; init; } - public int TagId { get; init; } - - public string SourceUrl { get; init; } = default!; - - public string Serialized { get; init; } = default!; - public string TagName { get; init; } = default!; - - public IEnumerable Examples { get; init; } = []; -} - -public class XmlDocBranchIntersectComparer : EqualityComparer -{ - public override bool Equals(FeatureXmlDoc? x, FeatureXmlDoc? y) - { - if (x is null && y is null) - { - return true; - } - - if (x is null || y is null) - { - return false; - } - - if (ReferenceEquals(x, y)) - { - return true; - } - - return x.Name == y.Name; - } - - public override int GetHashCode([DisallowNull] FeatureXmlDoc obj) - { - return HashCode.Combine(obj.Name); - } -} - -public record class XmlDocInspectionInfo : FeatureXmlDoc -{ - public string Reasoning { get; init; } = default!; - public string Remarks { get; init; } = default!; - public string DefaultSeverity { get; init; } = default!; - public string InspectionType { get; init; } = default!; - - public string[] References { get; init; } = []; - public string[] QuickFixes { get; init; } = []; - public string? HostApp { get; init; } = default!; - - -} - -public record class XmlDocQuickFixInfo : FeatureXmlDoc -{ - public string Remarks { get; init; } = default!; - public bool CanFixInProcedure { get; init; } - public bool CanFixInModule { get; init; } - public bool CanFixInProject { get; init; } - public string[] Inspections { get; init; } = []; -} - -public record class XmlDocAnnotationInfo : FeatureXmlDoc -{ - public XmlDocAnnotationParameterInfo[] Parameters { get; init; } = []; -} - -public record class XmlDocAnnotationParameterInfo -{ - public string Name { get; init; } = default!; - public string Type { get; init; } = default!; - public string Description { get; init; } = default!; -} - -public enum SyncStatus -{ - Received = 0, - Started = 1, - Success = 2, - Error = -1 -} - -public record class SynchronizationRequest -{ - public Guid RequestId { get; init; } = default!; - public string JobId { get; init; } = default!; - public DateTime UtcDateTimeStarted { get; init; } = default!; - public DateTime? UtcDateTimeEnded { get; init; } = default!; - public SyncStatus Status { get; init; } = default!; - public string Message { get; init; } = default!; -} - -public static class SynchronizationMessage -{ - public static string FromStatus(SyncStatus status) => status switch - { - SyncStatus.Received => "Synchronization queued.", - SyncStatus.Started => "Synchronization started.", - SyncStatus.Success => "Synchronization completed.", - SyncStatus.Error => "Synchronization failed.", - _ => "(unknown status)" - }; -} \ No newline at end of file diff --git a/rubberduckvba.Server/Data/FeaturesRepository.cs b/rubberduckvba.Server/Data/FeaturesRepository.cs new file mode 100644 index 0000000..94cb0b8 --- /dev/null +++ b/rubberduckvba.Server/Data/FeaturesRepository.cs @@ -0,0 +1,67 @@ +using Microsoft.Extensions.Options; +using rubberduckvba.Server.Model.Entity; + +namespace rubberduckvba.Server.Data; + +public class FeaturesRepository : Repository, IRepository +{ + public FeaturesRepository(IOptions settings) + : base(settings) { } + + protected override string TableName { get; } = "Features"; + protected override string? ParentFKColumnName { get; } = "ParentId"; + + protected override string SelectSql { get; } = @" +SELECT + [Id], + [DateTimeInserted], + [DateTimeUpdated], + [RepositoryId], + [ParentId], + [Name], + [Title], + [ShortDescription], + [Description], + [IsNew], + [HasImage] +FROM [dbo].[Features]"; + + protected override string InsertSql { get; } = @" +INSERT INTO [dbo].[Features] ( + [DateTimeInserted], + [RepositoryId], + [ParentId], + [Name], + [Title], + [ShortDescription], + [Description], + [IsHidden], + [IsNew], + [HasImage]) +VALUES ( + GETDATE(), + @repositoryId, + @parentId, + @name, + @title, + @shortDescription, + @description, + @isHidden, + @isNew, + @hasImage)"; + + protected override string UpdateSql { get; } = @" +UPDATE [dbo].[Features] SET + [DateTimeUpdated]=GETDATE(), + [RepositoryId]=@repositoryId, + [ParentId]=@parentId, + [Name]=@name, + [Title]=@title, + [ShortDescription]=@shortDescription, + [Description]=@description, + [IsHidden]=@isHidden, + [IsNew]=@isNew, + [HasImage]=@hasImage +WHERE [Id]=@id"; + +} diff --git a/rubberduckvba.Server/Data/InspectionsRepository.cs b/rubberduckvba.Server/Data/InspectionsRepository.cs new file mode 100644 index 0000000..8e3a3aa --- /dev/null +++ b/rubberduckvba.Server/Data/InspectionsRepository.cs @@ -0,0 +1,79 @@ +using Microsoft.Extensions.Options; +using rubberduckvba.Server.Model.Entity; + +namespace rubberduckvba.Server.Data; + +public class InspectionsRepository : Repository, IRepository +{ + public InspectionsRepository(IOptions settings) + : base(settings) { } + + protected override string TableName { get; } = "Inspections"; + protected override string SelectSql { get; } = @" +SELECT + [Id], + [DateTimeInserted], + [DateTimeUpdated], + [FeatureId], + [TagAssetId], + [SourceUrl], + [Name], + [InspectionType], + [DefaultSeverity], + [Summary], + [Reasoning], + [Remarks], + [HostApp], + [References], + [QuickFixes], + [JsonExamples] +FROM [dbo].[Inspections]"; + + protected override string InsertSql { get; } = @" +INSERT INTO [dbo].[Inspections] ( + [DateTimeInserted], + [FeatureId], + [TagAssetId], + [SourceUrl], + [Name], + [InspectionType], + [DefaultSeverity], + [Summary], + [Reasoning], + [Remarks], + [HostApp], + [References], + [QuickFixes], + [JsonExamples]) +VALUES ( + GETDATE(), + @featureId, + @tagAssetId, + @sourceUrl, + @name, + @inspectionType, + @defaultSeverity, + @summary, + @reasoning, + @remarks, + @hostApp, + @references, + @quickfixes, + @jsonExamples)"; + + protected override string UpdateSql { get; } = @" +UPDATE [dbo].[Inspections] +SET [DateTimeUpdated]=GETDATE(), + [TagAssetId]=@tagAssetId, + [InspectionType]=@inspectionType, + [DefaultSeverity]=@defaultSeverity, + [Summary]=@summary, + [Reasoning]=@reasoning, + [Remarks]=@remarks, + [HostApp]=@hostApp, + [References]=@references, + [QuickFixes]=@quickFixes, + [JsonExamples]=@jsonExamples +WHERE [Id]=@id"; + +} diff --git a/rubberduckvba.Server/Data/QuickFixRepository.cs b/rubberduckvba.Server/Data/QuickFixRepository.cs new file mode 100644 index 0000000..9247fb2 --- /dev/null +++ b/rubberduckvba.Server/Data/QuickFixRepository.cs @@ -0,0 +1,78 @@ +using Microsoft.Extensions.Options; +using rubberduckvba.Server.Model.Entity; + +namespace rubberduckvba.Server.Data; + +public class QuickFixRepository : Repository, IRepository +{ + public QuickFixRepository(IOptions settings) + : base(settings) { } + protected override string TableName { get; } = "QuickFixes"; + + protected override string SelectSql { get; } = @" +SELECT + [Id], + [DateTimeInserted], + [DateTimeUpdated], + [FeatureId], + [TagAssetId], + [SourceUrl], + [Name], + [Summary], + [Remarks], + [CanFixMultiple], + [CanFixProcedure], + [CanFixModule], + [CanFixProject], + [CanFixAll], + [Inspections], + [JsonExamples] +FROM [dbo].[QuickFixes]"; + + protected override string InsertSql { get; } = @" +INSERT INTO [dbo].[QuickFixes] ( + [DateTimeInserted], + [FeatureId], + [TagAssetId], + [SourceUrl], + [Name], + [Summary], + [Remarks], + [CanFixMultiple], + [CanFixProcedure], + [CanFixModule], + [CanFixProject], + [CanFixAll], + [Inspections], + [JsonExamples]) +VALUES ( + GETDATE(), + @featureId, + @tagAssetId, + @sourceUrl, + @name, + @summary, + @remarks, + @canFixMultiple, + @canFixProcedure, + @canFixModule, + @canFixProject, + @canFixAll, + @inspections, + @jsonExamples)"; + + protected override string UpdateSql { get; } = @" +UPDATE [dbo].[QuickFixes] +SET [DateTimeUpdated]=GETDATE(), + [TagAssetId]=@tagAssetId, + [Summary]=@summary, + [Remarks]=@remarks, + [CanFixMultiple]=@canFixMultiple, + [CanFixProcedure]=@canFixProcedure, + [CanFixModule]=@canFixModule, + [CanFixProject]=@canFixProject, + [CanFixAll]=@canFixAll, + [JsonExamples]=@jsonExamples +WHERE [Id]=@id"; + +} \ No newline at end of file diff --git a/rubberduckvba.Server/Data/Repository.cs b/rubberduckvba.Server/Data/Repository.cs new file mode 100644 index 0000000..c884b64 --- /dev/null +++ b/rubberduckvba.Server/Data/Repository.cs @@ -0,0 +1,95 @@ +using Dapper; +using Microsoft.Data.SqlClient; +using Microsoft.Extensions.Options; +using rubberduckvba.Server.Model.Entity; +using System.Data; + +namespace rubberduckvba.Server.Data; + +public interface IRepository where TEntity : Entity +{ + int GetId(string name); + TEntity GetById(int id); + IEnumerable GetAll(int? parentId = default); + TEntity Insert(TEntity entity); + IEnumerable Insert(IEnumerable entities); + void Update(TEntity entity); + void Update(IEnumerable entities); +} + +public abstract class Repository : IRepository where TEntity : Entity +{ + private readonly string _connectionString; + + protected Repository(IOptions settings) + { + _connectionString = settings.Value.RubberduckDb ?? throw new InvalidOperationException(); + } + + protected IEnumerable Query(Func> query) + { + using var db = new SqlConnection(_connectionString); + db.Open(); + return query(db); + } + + protected T Get(Func query) + { + using var db = new SqlConnection(_connectionString); + db.Open(); + return query(db); + } + + protected void Execute(Action action) + { + using var db = new SqlConnection(_connectionString); + db.Open(); + action(db); + } + + protected abstract string TableName { get; } + protected abstract string SelectSql { get; } + protected abstract string InsertSql { get; } + protected abstract string UpdateSql { get; } + + protected virtual string? ParentFKColumnName => null; + + public virtual int GetId(string name) => Get(db => db.QuerySingle($"SELECT [Id] FROM [dbo].[{TableName}] WHERE [Name]=@name", new { name })); + public virtual TEntity GetById(int id) => Get(db => db.QuerySingle(SelectSql + " WHERE [Id]=@id", new { id })); + public virtual IEnumerable GetAll(int? parentId = default) => + ParentFKColumnName is null || !parentId.HasValue + ? Query(db => db.Query(SelectSql)) + : Query(db => db.Query($"{SelectSql} WHERE [{ParentFKColumnName}]=@parentId", new { parentId })); + public virtual TEntity Insert(TEntity entity) => Insert([entity]).Single(); + public virtual IEnumerable Insert(IEnumerable entities) + { + using var db = new SqlConnection(_connectionString); + db.Open(); + + using var transaction = db.BeginTransaction(); + var inserts = new List(); + foreach (var entity in entities) + { + var id = db.ExecuteScalar(InsertSql + "; SELECT SCOPE_IDENTITY()", entity, transaction); + inserts.Add(entity with { Id = id }); + } + + transaction.Commit(); + return inserts; + } + + public virtual void Update(TEntity entity) => Update([entity]); + public virtual void Update(IEnumerable entities) + { + using var db = new SqlConnection(_connectionString); + db.Open(); + + using var transaction = db.BeginTransaction(); + foreach (var entity in entities) + { + db.Execute(UpdateSql, entity, transaction); + } + + transaction.Commit(); + } +} diff --git a/rubberduckvba.Server/Data/SynchronizationRequest.cs b/rubberduckvba.Server/Data/SynchronizationRequest.cs new file mode 100644 index 0000000..bd54cbd --- /dev/null +++ b/rubberduckvba.Server/Data/SynchronizationRequest.cs @@ -0,0 +1,32 @@ +namespace rubberduckvba.Server.Data; + + +public enum SyncStatus +{ + Received = 0, + Started = 1, + Success = 2, + Error = -1 +} + +public record class SynchronizationRequest +{ + public Guid RequestId { get; init; } = default!; + public string JobId { get; init; } = default!; + public DateTime UtcDateTimeStarted { get; init; } = default!; + public DateTime? UtcDateTimeEnded { get; init; } = default!; + public SyncStatus Status { get; init; } = default!; + public string Message { get; init; } = default!; +} + +public static class SynchronizationMessage +{ + public static string FromStatus(SyncStatus status) => status switch + { + SyncStatus.Received => "Synchronization queued.", + SyncStatus.Started => "Synchronization started.", + SyncStatus.Success => "Synchronization completed.", + SyncStatus.Error => "Synchronization failed.", + _ => "(unknown status)" + }; +} diff --git a/rubberduckvba.Server/Data/TagAssetsRepository.cs b/rubberduckvba.Server/Data/TagAssetsRepository.cs new file mode 100644 index 0000000..0e36264 --- /dev/null +++ b/rubberduckvba.Server/Data/TagAssetsRepository.cs @@ -0,0 +1,38 @@ +using Microsoft.Extensions.Options; +using rubberduckvba.Server; +using rubberduckvba.Server.Model.Entity; + +namespace rubberduckvba.Server.Data; + +public class TagAssetsRepository : Repository +{ + public TagAssetsRepository(IOptions settings) + : base(settings) { } + + protected override string TableName { get; } = "TagAssets"; + protected override string? ParentFKColumnName => "TagId"; + + protected override string SelectSql { get; } = @" +SELECT + [Id], + [DateTimeInserted], + [DateTimeUpdated], + [TagId], + [Name], + [DownloadUrl] +FROM [dbo].[TagAssets]"; + + protected override string InsertSql { get; } = @" +INSERT INTO [dbo].[TagAssets] ( + [DateTimeInserted], + [TagId], + [Name], + [DownloadUrl]) +VALUES ( + GETDATE(), + @tagId, + @name, + @downloadUrl);"; + + protected override string UpdateSql => throw new NotImplementedException(); +} \ No newline at end of file diff --git a/rubberduckvba.Server/Data/TagsRepository.cs b/rubberduckvba.Server/Data/TagsRepository.cs new file mode 100644 index 0000000..f847f96 --- /dev/null +++ b/rubberduckvba.Server/Data/TagsRepository.cs @@ -0,0 +1,50 @@ +using Microsoft.Extensions.Options; +using rubberduckvba.Server.Model.Entity; + +namespace rubberduckvba.Server.Data; + +public class TagsRepository : Repository +{ + public TagsRepository(IOptions settings) + : base(settings) { } + + protected override string TableName { get; } = "Tags"; + protected override string SelectSql { get; } = @" +SELECT + [Id], + [DateTimeInserted], + [DateTimeUpdated], + [RepositoryId], + [ReleaseId], + [Name], + [DateCreated], + [InstallerDownloadUrl], + [InstallerDownloads], + [IsPreRelease] +FROM [dbo].[Tags]"; + + protected override string InsertSql { get; } = @" +INSERT INTO [Tags] ( + [DateTimeInserted], + [RepositoryId], + [Name], + [DateCreated], + [InstallerDownloadUrl], + [InstallerDownloads], + [IsPreRelease]) +VALUES ( + GETDATE(), + @repositoryId, + @name, + @dateCreated, + @installerDownloadUrl, + @installerDownloads, + @isPreRelease)"; + + protected override string UpdateSql { get; } = @" +UPDATE [dbo].[Tags] +SET [DateTimeUpdated]=GETDATE(), + [InstallerDownloads]=@installerDownloads +WHERE [Id]=@id"; + +} diff --git a/rubberduckvba.Server/Data/XmlDocBranchIntersectComparer.cs b/rubberduckvba.Server/Data/XmlDocBranchIntersectComparer.cs new file mode 100644 index 0000000..fb2e877 --- /dev/null +++ b/rubberduckvba.Server/Data/XmlDocBranchIntersectComparer.cs @@ -0,0 +1,32 @@ +using rubberduckvba.Server.Model; +using System.Diagnostics.CodeAnalysis; + +namespace rubberduckvba.Server.Data; + +public class XmlDocBranchIntersectComparer : EqualityComparer where T : IFeature +{ + public override bool Equals(T? x, T? y) + { + if (x is null && y is null) + { + return true; + } + + if (x is null || y is null) + { + return false; + } + + if (ReferenceEquals(x, y)) + { + return true; + } + + return x.Name == y.Name; + } + + public override int GetHashCode([DisallowNull] T obj) + { + return HashCode.Combine(obj.Name); + } +} diff --git a/rubberduckvba.Server/GitHubAuthenticationHandler.cs b/rubberduckvba.Server/GitHubAuthenticationHandler.cs index 1c6aae0..5e0a85e 100644 --- a/rubberduckvba.Server/GitHubAuthenticationHandler.cs +++ b/rubberduckvba.Server/GitHubAuthenticationHandler.cs @@ -1,18 +1,18 @@  using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Options; -using rubberduckvba.com.Server.Services; +using rubberduckvba.Server.Services; using System.Security.Claims; using System.Text.Encodings.Web; -namespace rubberduckvba.com.Server; +namespace rubberduckvba.Server; public class GitHubAuthenticationHandler : AuthenticationHandler { private readonly IGitHubClientService _github; public GitHubAuthenticationHandler(IGitHubClientService github, - IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder) + IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder) : base(options, logger, encoder) { _github = github; @@ -23,7 +23,7 @@ protected async override Task HandleAuthenticateAsync() var token = Context.Request.Headers["X-ACCESS-TOKEN"].SingleOrDefault(); if (token is null) { - return AuthenticateResult.Fail("No token was provided"); + return AuthenticateResult.NoResult(); } var principal = await _github.ValidateTokenAsync(token); diff --git a/rubberduckvba.Server/GitHubSettings.cs b/rubberduckvba.Server/GitHubSettings.cs index 0c35240..fc941b6 100644 --- a/rubberduckvba.Server/GitHubSettings.cs +++ b/rubberduckvba.Server/GitHubSettings.cs @@ -1,6 +1,6 @@ using Hangfire; -namespace rubberduckvba.com.Server; +namespace rubberduckvba.Server; public record class ApiSettings { diff --git a/rubberduckvba.Server/Hangfire/DockerDashboardFilter.cs b/rubberduckvba.Server/Hangfire/DockerDashboardFilter.cs index b724224..3239f15 100644 --- a/rubberduckvba.Server/Hangfire/DockerDashboardFilter.cs +++ b/rubberduckvba.Server/Hangfire/DockerDashboardFilter.cs @@ -1,7 +1,7 @@ using Hangfire.Annotations; using Hangfire.Dashboard; -namespace rubberduckvba.com.Server.Hangfire; +namespace rubberduckvba.Server.Hangfire; public class DockerDashboardFilter : IDashboardAuthorizationFilter { diff --git a/rubberduckvba.Server/Hangfire/HangfireConstants.cs b/rubberduckvba.Server/Hangfire/HangfireConstants.cs index b07f62e..ac6c08b 100644 --- a/rubberduckvba.Server/Hangfire/HangfireConstants.cs +++ b/rubberduckvba.Server/Hangfire/HangfireConstants.cs @@ -1,4 +1,4 @@ -namespace rubberduckvba.com.Server.Hangfire; +namespace rubberduckvba.Server.Hangfire; public static class HangfireConstants { diff --git a/rubberduckvba.Server/Hangfire/QueuedUpdateOrchestrator.cs b/rubberduckvba.Server/Hangfire/QueuedUpdateOrchestrator.cs index 589e544..1e64a03 100644 --- a/rubberduckvba.Server/Hangfire/QueuedUpdateOrchestrator.cs +++ b/rubberduckvba.Server/Hangfire/QueuedUpdateOrchestrator.cs @@ -4,15 +4,19 @@ using NLog.Extensions.Logging; using NLog.Targets; using RubberduckServices; -using rubberduckvba.com.Server.ContentSynchronization; -using rubberduckvba.com.Server.ContentSynchronization.Pipeline; -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.ContentSynchronization.XmlDoc; -using rubberduckvba.com.Server.ContentSynchronization.XmlDoc.Abstract; -using rubberduckvba.com.Server.Services; +using rubberduckvba.Server.ContentSynchronization; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.ContentSynchronization.XmlDoc; +using rubberduckvba.Server.ContentSynchronization.XmlDoc.Abstract; +using rubberduckvba.Server.Data; +using rubberduckvba.Server.Model.Entity; +using rubberduckvba.Server.Services; +using rubberduckvba.Server.Services.rubberduckdb; using System.Diagnostics; +using System.Reflection; -namespace rubberduckvba.com.Server.Hangfire; +namespace rubberduckvba.Server.Hangfire; public static class QueuedUpdateOrchestrator { @@ -29,7 +33,7 @@ private static IServiceCollection Configure() var services = new ServiceCollection(); var configuration = new ConfigurationBuilder() .AddJsonFile("appsettings.json") - //.AddUserSecrets(Assembly.GetExecutingAssembly()) + .AddUserSecrets(Assembly.GetExecutingAssembly()) .Build(); @@ -97,11 +101,23 @@ private static void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton, TagsRepository>(); + services.AddSingleton, TagAssetsRepository>(); + services.AddSingleton, FeaturesRepository>(); + services.AddSingleton, InspectionsRepository>(); + services.AddSingleton, QuickFixRepository>(); + services.AddSingleton, AnnotationsRepository>(); + services.AddSingleton(); services.AddSingleton, SynchronizationPipelineFactory>(); services.AddSingleton(); services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); } private static async Task Run(IServiceCollection services, PerformContext context, TParameters parameters) diff --git a/rubberduckvba.Server/Model/Annotation.cs b/rubberduckvba.Server/Model/Annotation.cs new file mode 100644 index 0000000..97f91c7 --- /dev/null +++ b/rubberduckvba.Server/Model/Annotation.cs @@ -0,0 +1,61 @@ +using rubberduckvba.Server.Model.Entity; +using System.Text.Json; + +namespace rubberduckvba.Server.Model; + +public record class Annotation() : IFeature +{ + public Annotation(AnnotationEntity entity) : this() + { + Id = entity.Id; + DateTimeInserted = entity.DateTimeInserted; + DateTimeUpdated = entity.DateTimeUpdated; + FeatureId = entity.FeatureId; + TagAssetId = entity.TagAssetId; + SourceUrl = entity.SourceUrl; + Name = entity.Name; + Parameters = entity.JsonParameters is null ? [] : JsonSerializer.Deserialize(entity.JsonParameters) ?? []; + Examples = entity.JsonExamples is null ? [] : JsonSerializer.Deserialize(entity.JsonExamples) ?? []; + } + + public int Id { get; init; } + public DateTime DateTimeInserted { get; init; } + public DateTime? DateTimeUpdated { get; init; } + public int FeatureId { get; init; } + public int TagAssetId { get; init; } + public string SourceUrl { get; init; } = string.Empty; + public bool IsNew { get; init; } + public bool IsDiscontinued { get; init; } + public bool IsHidden { get; init; } + public string Name { get; init; } = string.Empty; + public string Summary { get; init; } = string.Empty; + public string Remarks { get; init; } = string.Empty; + public AnnotationParameter[] Parameters { get; init; } = []; + public AnnotationExample[] Examples { get; init; } = []; + + internal AnnotationEntity ToEntity() => new() + { + Id = Id, + DateTimeInserted = DateTimeInserted, + DateTimeUpdated = DateTimeUpdated, + FeatureId = FeatureId, + TagAssetId = TagAssetId, + SourceUrl = SourceUrl, + Name = Name, + Summary = Summary, + Remarks = Remarks, + JsonParameters = JsonSerializer.Serialize(Parameters), + JsonExamples = JsonSerializer.Serialize(Examples), + }; + + public int GetContentHash() + { + var hash = new HashCode(); + hash.Add(Name); + hash.Add(Summary); + hash.Add(Remarks); + hash.Add(JsonSerializer.Serialize(Parameters)); + hash.Add(JsonSerializer.Serialize(Examples)); + return hash.ToHashCode(); + } +} diff --git a/rubberduckvba.Server/Model/AnnotationParameter.cs b/rubberduckvba.Server/Model/AnnotationParameter.cs new file mode 100644 index 0000000..05c50eb --- /dev/null +++ b/rubberduckvba.Server/Model/AnnotationParameter.cs @@ -0,0 +1,9 @@ +namespace rubberduckvba.Server.Model; + +public record class AnnotationParameter +{ + public string Name { get; init; } = string.Empty; + public string Type { get; init; } = string.Empty; + public bool Required { get; init; } + public string Description { get; init; } = string.Empty; +} diff --git a/rubberduckvba.Server/Model/Entity/AnnotationEntity.cs b/rubberduckvba.Server/Model/Entity/AnnotationEntity.cs new file mode 100644 index 0000000..5c586ba --- /dev/null +++ b/rubberduckvba.Server/Model/Entity/AnnotationEntity.cs @@ -0,0 +1,12 @@ +namespace rubberduckvba.Server.Model.Entity; + +public record class AnnotationEntity : Entity +{ + public int FeatureId { get; init; } + public int TagAssetId { get; init; } + public string SourceUrl { get; init; } = string.Empty; + public string Summary { get; init; } = string.Empty; + public string Remarks { get; init; } = string.Empty; + public string? JsonParameters { get; init; } + public string? JsonExamples { get; init; } +} diff --git a/rubberduckvba.Server/Model/Entity/Entity.cs b/rubberduckvba.Server/Model/Entity/Entity.cs new file mode 100644 index 0000000..cbee48a --- /dev/null +++ b/rubberduckvba.Server/Model/Entity/Entity.cs @@ -0,0 +1,9 @@ +namespace rubberduckvba.Server.Model.Entity; + +public abstract record class Entity +{ + public int Id { get; init; } + public DateTime DateTimeInserted { get; init; } + public DateTime? DateTimeUpdated { get; init; } + public string Name { get; init; } = string.Empty; +} diff --git a/rubberduckvba.Server/Model/Entity/FeatureEntity.cs b/rubberduckvba.Server/Model/Entity/FeatureEntity.cs new file mode 100644 index 0000000..85db719 --- /dev/null +++ b/rubberduckvba.Server/Model/Entity/FeatureEntity.cs @@ -0,0 +1,13 @@ +namespace rubberduckvba.Server.Model.Entity; + +public record class FeatureEntity : Entity +{ + public int? ParentId { get; init; } + public int RepositoryId { get; init; } + public string Title { get; init; } = default!; + public string ShortDescription { get; init; } = default!; + public string Description { get; init; } = default!; + public bool IsHidden { get; init; } + public bool IsNew { get; init; } + public bool HasImage { get; init; } +} diff --git a/rubberduckvba.Server/Model/Entity/InspectionEntity.cs b/rubberduckvba.Server/Model/Entity/InspectionEntity.cs new file mode 100644 index 0000000..ae207dd --- /dev/null +++ b/rubberduckvba.Server/Model/Entity/InspectionEntity.cs @@ -0,0 +1,20 @@ +namespace rubberduckvba.Server.Model.Entity; + +public record class InspectionEntity : Entity +{ + public int FeatureId { get; init; } + public int TagAssetId { get; init; } + public string SourceUrl { get; init; } = string.Empty; + public string InspectionType { get; init; } = string.Empty; + public string DefaultSeverity { get; init; } = string.Empty; + public string Summary { get; init; } = string.Empty; + public string Reasoning { get; init; } = string.Empty; + public string? Remarks { get; init; } + public string? HostApp { get; init; } + public bool IsNew { get; init; } + public bool IsDiscontinued { get; init; } + public bool IsHidden { get; init; } + public string? References { get; init; } + public string? QuickFixes { get; init; } + public string? JsonExamples { get; init; } +} diff --git a/rubberduckvba.Server/Model/Entity/QuickFixEntity.cs b/rubberduckvba.Server/Model/Entity/QuickFixEntity.cs new file mode 100644 index 0000000..afc4d3e --- /dev/null +++ b/rubberduckvba.Server/Model/Entity/QuickFixEntity.cs @@ -0,0 +1,20 @@ +namespace rubberduckvba.Server.Model.Entity; + +public record class QuickFixEntity : Entity +{ + public int FeatureId { get; init; } + public int TagAssetId { get; init; } + public string SourceUrl { get; init; } = string.Empty; + public bool IsNew { get; init; } + public bool IsDiscontinued { get; init; } + public bool IsHidden { get; init; } + public string Summary { get; init; } = string.Empty; + public string? Remarks { get; init; } + public bool CanFixMultiple { get; init; } + public bool CanFixProcedure { get; init; } + public bool CanFixModule { get; init; } + public bool CanFixProject { get; init; } + public bool CanFixAll { get; init; } + public string Inspections { get; init; } = string.Empty; + public string? JsonExamples { get; init; } +} diff --git a/rubberduckvba.Server/Model/Entity/TagAssetEntity.cs b/rubberduckvba.Server/Model/Entity/TagAssetEntity.cs new file mode 100644 index 0000000..3d610f1 --- /dev/null +++ b/rubberduckvba.Server/Model/Entity/TagAssetEntity.cs @@ -0,0 +1,7 @@ +namespace rubberduckvba.Server.Model.Entity; + +public record class TagAssetEntity : Entity +{ + public int TagId { get; init; } + public string DownloadUrl { get; init; } = default!; +} diff --git a/rubberduckvba.Server/Model/Entity/TagEntity.cs b/rubberduckvba.Server/Model/Entity/TagEntity.cs new file mode 100644 index 0000000..d656e65 --- /dev/null +++ b/rubberduckvba.Server/Model/Entity/TagEntity.cs @@ -0,0 +1,10 @@ +namespace rubberduckvba.Server.Model.Entity; + +public record class TagEntity : Entity +{ + public long ReleaseId { get; init; } + public DateTime DateCreated { get; init; } + public string InstallerDownloadUrl { get; init; } = default!; + public int InstallerDownloads { get; init; } + public bool IsPreRelease { get; init; } +} diff --git a/rubberduckvba.Server/Model/Example.cs b/rubberduckvba.Server/Model/Example.cs new file mode 100644 index 0000000..72305df --- /dev/null +++ b/rubberduckvba.Server/Model/Example.cs @@ -0,0 +1,40 @@ +namespace rubberduckvba.Server.Model; + +public record class Example +{ + public string Description { get; init; } = default!; + public int SortOrder { get; init; } +} + +public record class InspectionExample : Example +{ + /// + /// True if the example depicts code a situation that would make an inspection issue a result. + /// + public bool HasResult { get; init; } + public ICollection Modules { get; init; } = []; +} + +public record class QuickFixExample : BeforeAndAfterExample +{ +} + +public record class AnnotationExample : BeforeAndAfterExample +{ + /// + /// The example modules when the annotation has no before/after examples. + /// + public ICollection Modules { get; init; } = []; +} + +public record class BeforeAndAfterExample : Example +{ + /// + /// The code modules before feature is used. + /// + public ICollection ModulesBefore { get; init; } = []; + /// + /// The code modules after the feature is used. + /// + public ICollection ModulesAfter { get; init; } = []; +} diff --git a/rubberduckvba.Server/Model/ExampleModule.cs b/rubberduckvba.Server/Model/ExampleModule.cs new file mode 100644 index 0000000..a6446ab --- /dev/null +++ b/rubberduckvba.Server/Model/ExampleModule.cs @@ -0,0 +1,26 @@ +using System.Reflection; + +namespace rubberduckvba.Server.Model; + +public record class ExampleModule +{ + private static readonly IDictionary ModuleTypes = + typeof(ExampleModuleType) + .GetMembers() + .Select(m => (m.Name, m.GetCustomAttributes().OfType().SingleOrDefault()?.Description)) + .Where(m => m.Description != null) + .ToDictionary(m => (ExampleModuleType)Enum.Parse(typeof(ExampleModuleType), m.Name, true), m => m.Description!); + + public static ExampleModule ParseError(string name) => new() + { + ModuleName = name, + HtmlContent = "(error parsing code example from source xmldoc)" + }; + + public string ModuleName { get; init; } = default!; + public ExampleModuleType ModuleType { get; init; } + public string ModuleTypeName => ModuleTypes[ModuleType]; + + public string HtmlContent { get; init; } = default!; + public string? Description { get; init; } = default!; +} diff --git a/rubberduckvba.Server/Model/ExampleModuleType.cs b/rubberduckvba.Server/Model/ExampleModuleType.cs new file mode 100644 index 0000000..9a78bf9 --- /dev/null +++ b/rubberduckvba.Server/Model/ExampleModuleType.cs @@ -0,0 +1,15 @@ +using System.ComponentModel; + +namespace rubberduckvba.Server.Model; + +public enum ExampleModuleType +{ + None = 0, + [Description("(Any)")] Any, + [Description("Class Module")] ClassModule, + [Description("Document Module")] DocumentModule, + [Description("Interface Module")] InterfaceModule, + [Description("Predeclared Class")] PredeclaredClass, + [Description("Standard Module")] StandardModule, + [Description("UserForm Module")] UserFormModule +} diff --git a/rubberduckvba.Server/Model/Feature.cs b/rubberduckvba.Server/Model/Feature.cs new file mode 100644 index 0000000..d1aef69 --- /dev/null +++ b/rubberduckvba.Server/Model/Feature.cs @@ -0,0 +1,82 @@ +using rubberduckvba.Server.Model.Entity; + +namespace rubberduckvba.Server.Model; + +public interface IFeature +{ + int Id { get; init; } + DateTime DateTimeInserted { get; init; } + DateTime? DateTimeUpdated { get; init; } + string Name { get; init; } + + bool IsNew { get; init; } + bool IsHidden { get; init; } + bool IsDiscontinued { get; init; } + + int GetContentHash(); +} + +public record class Feature() : IFeature +{ + public Feature(FeatureEntity entity) : this() + { + Id = entity.Id; + DateTimeInserted = entity.DateTimeInserted; + DateTimeUpdated = entity.DateTimeUpdated; + Name = entity.Name; + ParentId = entity.ParentId; + RepositoryId = (Services.RepositoryId)entity.RepositoryId; + Title = entity.Title; + ShortDescription = entity.ShortDescription; + Description = entity.Description; + IsHidden = entity.IsHidden; + IsNew = entity.IsNew; + HasImage = entity.HasImage; + } + + public int Id { get; init; } + public DateTime DateTimeInserted { get; init; } + public DateTime? DateTimeUpdated { get; init; } + public string Name { get; init; } = string.Empty; + + public int? ParentId { get; init; } + public Services.RepositoryId RepositoryId { get; init; } = Services.RepositoryId.Rubberduck; + public string Title { get; init; } = string.Empty; + public string ShortDescription { get; init; } = string.Empty; + public string Description { get; init; } = string.Empty; + public bool IsHidden { get; init; } + public bool IsNew { get; init; } + public bool IsDiscontinued { get; init; } + public bool HasImage { get; init; } + + public FeatureEntity ToEntity() => new() + { + Id = Id, + DateTimeInserted = DateTimeInserted, + DateTimeUpdated = DateTimeUpdated, + Description = Description, + HasImage = HasImage, + IsHidden = IsHidden, + IsNew = IsNew, + Name = Name, + ShortDescription = ShortDescription, + ParentId = ParentId, + RepositoryId = (int)Services.RepositoryId.Rubberduck, + Title = Title, + }; + + public int GetContentHash() => HashCode.Combine(Name, Title, ShortDescription, Description, HasImage, IsHidden, IsNew, IsDiscontinued); +} + +public record class FeatureGraph : Feature +{ + public FeatureGraph(FeatureEntity entity) : base(entity) { } + + public string? ParentName { get; init; } + public string? ParentTitle { get; init; } + + public IEnumerable Features { get; init; } = []; + public IEnumerable Inspections { get; init; } = []; + public IEnumerable QuickFixes { get; init; } = []; + public IEnumerable Annotations { get; init; } = []; +} \ No newline at end of file diff --git a/rubberduckvba.Server/Model/Inspection.cs b/rubberduckvba.Server/Model/Inspection.cs new file mode 100644 index 0000000..817faaf --- /dev/null +++ b/rubberduckvba.Server/Model/Inspection.cs @@ -0,0 +1,93 @@ +using rubberduckvba.Server.Model.Entity; +using System.Text.Json; + +namespace rubberduckvba.Server.Model; + +public record class Inspection() : IFeature +{ + public Inspection(InspectionEntity entity) : this() + { + Id = entity.Id; + DateTimeInserted = entity.DateTimeInserted; + DateTimeUpdated = entity.DateTimeUpdated; + FeatureId = entity.FeatureId; + TagAssetId = entity.TagAssetId; + SourceUrl = entity.SourceUrl; + + IsNew = entity.IsNew; + IsDiscontinued = entity.IsDiscontinued; + IsHidden = entity.IsHidden; + + Name = entity.Name; + InspectionType = entity.InspectionType; + DefaultSeverity = entity.DefaultSeverity; + Summary = entity.Summary; + Reasoning = entity.Reasoning; + Remarks = entity.Remarks; + HostApp = entity.HostApp; + References = entity.References?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) ?? []; + QuickFixes = entity.QuickFixes?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) ?? []; + Examples = entity.JsonExamples is null ? [] : JsonSerializer.Deserialize(entity.JsonExamples) ?? []; + } + + public int Id { get; init; } + public DateTime DateTimeInserted { get; init; } + public DateTime? DateTimeUpdated { get; init; } + public int FeatureId { get; init; } + public int TagAssetId { get; init; } + public string SourceUrl { get; init; } = string.Empty; + + public bool IsNew { get; init; } + public bool IsDiscontinued { get; init; } + public bool IsHidden { get; init; } + + public string Name { get; init; } = string.Empty; + public string InspectionType { get; init; } = string.Empty; + public string DefaultSeverity { get; init; } = string.Empty; + public string Summary { get; init; } = string.Empty; + public string Reasoning { get; init; } = string.Empty; + public string? Remarks { get; init; } + public string? HostApp { get; init; } + public string[] References { get; init; } = []; + public string[] QuickFixes { get; init; } = []; + public InspectionExample[] Examples { get; init; } = []; + + public InspectionEntity ToEntity() => new() + { + Id = Id, + FeatureId = FeatureId, + DateTimeInserted = DateTimeInserted, + DateTimeUpdated = DateTimeUpdated, + TagAssetId = TagAssetId, + SourceUrl = SourceUrl, + IsNew = IsNew, + IsDiscontinued = IsDiscontinued, + IsHidden = IsHidden, + Name = Name, + InspectionType = InspectionType, + DefaultSeverity = DefaultSeverity, + Summary = Summary, + Reasoning = Reasoning, + Remarks = Remarks, + HostApp = HostApp, + JsonExamples = JsonSerializer.Serialize(Examples), + QuickFixes = string.Join(',', QuickFixes), + References = string.Join(',', References), + }; + + public int GetContentHash() + { + var hash = new HashCode(); + hash.Add(DefaultSeverity); + hash.Add(Summary); + hash.Add(Reasoning); + hash.Add(Remarks); + hash.Add(HostApp); + hash.Add(Name); + hash.Add(InspectionType); + hash.Add(JsonSerializer.Serialize(Examples)); + hash.Add(string.Join(',', QuickFixes)); + hash.Add(string.Join(',', References)); + return hash.ToHashCode(); + } +} diff --git a/rubberduckvba.Server/Model/QuickFix.cs b/rubberduckvba.Server/Model/QuickFix.cs new file mode 100644 index 0000000..08b9631 --- /dev/null +++ b/rubberduckvba.Server/Model/QuickFix.cs @@ -0,0 +1,89 @@ +using rubberduckvba.Server.Model.Entity; +using System.Text.Json; + +namespace rubberduckvba.Server.Model; + +public record class QuickFix() : IFeature +{ + public QuickFix(QuickFixEntity entity) : this() + { + Id = entity.Id; + DateTimeInserted = entity.DateTimeInserted; + DateTimeUpdated = entity.DateTimeUpdated; + FeatureId = entity.FeatureId; + TagAssetId = entity.TagAssetId; + SourceUrl = entity.SourceUrl; + IsNew = entity.IsNew; + IsDiscontinued = entity.IsDiscontinued; + IsHidden = entity.IsHidden; + Name = entity.Name; + Summary = entity.Summary; + Remarks = entity.Remarks; + CanFixMultiple = entity.CanFixMultiple; + CanFixProcedure = entity.CanFixProcedure; + CanFixModule = entity.CanFixModule; + CanFixProject = entity.CanFixProject; + CanFixAll = entity.CanFixAll; + Inspections = entity.Inspections.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) ?? []; + Examples = entity.JsonExamples is null ? [] : JsonSerializer.Deserialize(entity.JsonExamples) ?? []; + } + + public int Id { get; init; } + public DateTime DateTimeInserted { get; init; } + public DateTime? DateTimeUpdated { get; init; } + public int FeatureId { get; init; } + public int TagAssetId { get; init; } + public string SourceUrl { get; init; } = string.Empty; + public bool IsNew { get; init; } + public bool IsDiscontinued { get; init; } + public bool IsHidden { get; init; } + public string Name { get; init; } = string.Empty; + public string Summary { get; init; } = string.Empty; + public string? Remarks { get; init; } + public bool CanFixMultiple { get; init; } + public bool CanFixProcedure { get; init; } + public bool CanFixModule { get; init; } + public bool CanFixProject { get; init; } + public bool CanFixAll { get; init; } + public string[] Inspections { get; init; } = []; + public QuickFixExample[] Examples { get; init; } = []; + + public QuickFixEntity ToEntity() => new() + { + Id = Id, + DateTimeInserted = DateTimeInserted, + DateTimeUpdated = DateTimeUpdated, + FeatureId = FeatureId, + SourceUrl = SourceUrl, + IsNew = IsNew, + IsDiscontinued = IsDiscontinued, + IsHidden = IsHidden, + Name = Name, + Summary = Summary, + Remarks = Remarks, + CanFixMultiple = CanFixMultiple, + CanFixProcedure = CanFixProcedure, + CanFixModule = CanFixModule, + CanFixProject = CanFixProject, + CanFixAll = CanFixAll, + TagAssetId = TagAssetId, + JsonExamples = JsonSerializer.Serialize(Examples), + Inspections = string.Join(',', Inspections), + }; + + public int GetContentHash() + { + var hash = new HashCode(); + hash.Add(Summary); + hash.Add(Remarks); + hash.Add(Name); + hash.Add(CanFixAll); + hash.Add(CanFixModule); + hash.Add(CanFixMultiple); + hash.Add(CanFixProcedure); + hash.Add(CanFixProject); + hash.Add(JsonSerializer.Serialize(Examples)); + hash.Add(string.Join(',', Inspections)); + return hash.ToHashCode(); + } +} \ No newline at end of file diff --git a/rubberduckvba.Server/Model/Tag.cs b/rubberduckvba.Server/Model/Tag.cs new file mode 100644 index 0000000..6394100 --- /dev/null +++ b/rubberduckvba.Server/Model/Tag.cs @@ -0,0 +1,58 @@ +using rubberduckvba.Server.Model.Entity; + +namespace rubberduckvba.Server.Model; + +public record class Tag +{ + public Tag() + { + } + + public Tag(TagEntity entity) + { + Id = entity.Id; + DateTimeInserted = entity.DateTimeInserted; + DateTimeUpdated = entity.DateTimeUpdated; + Name = entity.Name; + ReleaseId = entity.ReleaseId; + DateCreated = entity.DateCreated; + InstallerDownloadUrl = entity.InstallerDownloadUrl; + InstallerDownloads = entity.InstallerDownloads; + IsPreRelease = entity.IsPreRelease; + } + + public int Id { get; init; } + public DateTime DateTimeInserted { get; init; } + public DateTime? DateTimeUpdated { get; init; } + public string Name { get; init; } = string.Empty; + public long ReleaseId { get; init; } + public DateTime DateCreated { get; init; } + public string InstallerDownloadUrl { get; init; } = string.Empty; + public int InstallerDownloads { get; init; } + public bool IsPreRelease { get; init; } + + public TagEntity ToEntity() => new() + { + Id = Id, + DateTimeInserted = DateTimeInserted, + DateTimeUpdated = DateTimeUpdated, + Name = Name, + ReleaseId = ReleaseId, + DateCreated = DateCreated, + InstallerDownloadUrl = InstallerDownloadUrl, + InstallerDownloads = InstallerDownloads, + IsPreRelease = IsPreRelease, + }; +} + +public record class TagGraph : Tag +{ + public TagGraph() : base() { } + public TagGraph(TagEntity tag, IEnumerable assets) + : base(tag) + { + Assets = assets.Select(e => new TagAsset(e)); + } + + public IEnumerable Assets { get; init; } = []; +} \ No newline at end of file diff --git a/rubberduckvba.Server/Model/TagAsset.cs b/rubberduckvba.Server/Model/TagAsset.cs new file mode 100644 index 0000000..95890cf --- /dev/null +++ b/rubberduckvba.Server/Model/TagAsset.cs @@ -0,0 +1,33 @@ +using rubberduckvba.Server.Model.Entity; + +namespace rubberduckvba.Server.Model; + +public record class TagAsset() +{ + public TagAsset(TagAssetEntity entity) : this() + { + Id = entity.Id; + DateTimeInserted = entity.DateTimeInserted; + DateTimeUpdated = entity.DateTimeUpdated; + Name = entity.Name; + TagId = entity.TagId; + DownloadUrl = entity.DownloadUrl; + } + + public int Id { get; init; } + public DateTime DateTimeInserted { get; init; } + public DateTime? DateTimeUpdated { get; init; } + public string Name { get; init; } = string.Empty; + public int TagId { get; init; } + public string DownloadUrl { get; init; } = string.Empty; + + public TagAssetEntity ToEntity() => new() + { + Id = Id, + DateTimeInserted = DateTimeInserted, + DateTimeUpdated = DateTimeUpdated, + Name = Name, + DownloadUrl = DownloadUrl, + TagId = TagId + }; +} \ No newline at end of file diff --git a/rubberduckvba.Server/Program.cs b/rubberduckvba.Server/Program.cs index 9b3e7f7..fd06fbe 100644 --- a/rubberduckvba.Server/Program.cs +++ b/rubberduckvba.Server/Program.cs @@ -8,21 +8,25 @@ using NLog.Extensions.Logging; using NLog.Targets; using RubberduckServices; -using rubberduckvba.com.Server.Api.Admin; -using rubberduckvba.com.Server.ContentSynchronization; -using rubberduckvba.com.Server.ContentSynchronization.Pipeline; -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.ContentSynchronization.XmlDoc; -using rubberduckvba.com.Server.ContentSynchronization.XmlDoc.Abstract; -using rubberduckvba.com.Server.Hangfire; -using rubberduckvba.com.Server.Services; +using rubberduckvba.Server.Api.Admin; +using rubberduckvba.Server.ContentSynchronization; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.ContentSynchronization.XmlDoc; +using rubberduckvba.Server.ContentSynchronization.XmlDoc.Abstract; +using rubberduckvba.Server.Data; +using rubberduckvba.Server.Hangfire; +using rubberduckvba.Server.Model.Entity; +using rubberduckvba.Server.Services; +using rubberduckvba.Server.Services.rubberduckdb; +using System.Diagnostics; using System.Reflection; -namespace rubberduckvba.com.Server; +namespace rubberduckvba.Server; public class HangfireAuthenticationFilter : IDashboardAuthorizationFilter { - public bool Authorize([NotNull] DashboardContext context) => context.Request.RemoteIpAddress == "20.220.30.154"; + public bool Authorize([NotNull] DashboardContext context) => Debugger.IsAttached || context.Request.RemoteIpAddress == "20.220.30.154"; } public class Program @@ -31,10 +35,6 @@ public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); builder.Configuration.AddJsonFile("appsettings.json"); - builder.Configuration.AddEnvironmentVariables("ConnectionStrings"); - builder.Configuration.AddEnvironmentVariables("GitHub"); - builder.Configuration.AddEnvironmentVariables("Api"); - builder.Configuration.AddUserSecrets(Assembly.GetExecutingAssembly()); builder.Services.Configure(options => builder.Configuration.GetSection("ConnectionStrings").Bind(options)); @@ -42,6 +42,7 @@ public static void Main(string[] args) builder.Services.Configure(options => builder.Configuration.GetSection("Api").Bind(options)); builder.Services.Configure(options => builder.Configuration.GetSection("Hangfire").Bind(options)); + builder.Services.AddAuthentication(options => { options.RequireAuthenticatedSignIn = false; @@ -148,13 +149,25 @@ private static void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton, TagsRepository>(); + services.AddSingleton, TagAssetsRepository>(); + services.AddSingleton, FeaturesRepository>(); + services.AddSingleton, InspectionsRepository>(); + services.AddSingleton, QuickFixRepository>(); + services.AddSingleton, AnnotationsRepository>(); + + //services.AddSingleton(); services.AddSingleton(); services.AddSingleton, InstallerDownloadStatsOrchestrator>(); services.AddSingleton, XmldocContentOrchestrator>(); services.AddSingleton, SynchronizationPipelineFactory>(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -175,7 +188,7 @@ private static void ConfigureLogging(IServiceCollection services) var config = new LoggingConfiguration(); (NLog.LogLevel MinLevel, string, Target Target)[] targets = [ - (NLog.LogLevel.Trace, "*", new DebuggerTarget("DebuggerLog")), + (NLog.LogLevel.Trace, "rubberduckvba.*", new DebuggerTarget("DebuggerLog")), (NLog.LogLevel.Info, "rubberduckvba.*", new FileTarget("FileLog") { FileName = "logs/rubberduckvba.Server.log", diff --git a/rubberduckvba.Server/Services/ContentCacheService.cs b/rubberduckvba.Server/Services/ContentCacheService.cs index 003f939..084e49a 100644 --- a/rubberduckvba.Server/Services/ContentCacheService.cs +++ b/rubberduckvba.Server/Services/ContentCacheService.cs @@ -2,7 +2,7 @@ using System.Collections.Concurrent; using System.Diagnostics; -namespace rubberduckvba.com.Server.Services; +namespace rubberduckvba.Server.Services; public interface IContentCacheService : IDisposable { diff --git a/rubberduckvba.Server/Services/DateTimeExtensions.cs b/rubberduckvba.Server/Services/DateTimeExtensions.cs index ce9f630..e24c612 100644 --- a/rubberduckvba.Server/Services/DateTimeExtensions.cs +++ b/rubberduckvba.Server/Services/DateTimeExtensions.cs @@ -1,4 +1,4 @@ -namespace rubberduckvba.com.Server.Services; +namespace rubberduckvba.Server.Services; public static class DateTimeExtensions { diff --git a/rubberduckvba.Server/Services/DistributedCacheService.cs b/rubberduckvba.Server/Services/DistributedCacheService.cs index fea248d..b1c80c1 100644 --- a/rubberduckvba.Server/Services/DistributedCacheService.cs +++ b/rubberduckvba.Server/Services/DistributedCacheService.cs @@ -3,7 +3,7 @@ using System.Text; using System.Text.Json; -namespace rubberduckvba.com.Server.Services; +namespace rubberduckvba.Server.Services; public interface ICacheService { diff --git a/rubberduckvba.Server/Services/GitHubClientService.cs b/rubberduckvba.Server/Services/GitHubClientService.cs index cf42165..80e6f49 100644 --- a/rubberduckvba.Server/Services/GitHubClientService.cs +++ b/rubberduckvba.Server/Services/GitHubClientService.cs @@ -1,29 +1,35 @@ using Microsoft.Extensions.Options; using Octokit; using Octokit.Internal; -using rubberduckvba.com.Server.ContentSynchronization; -using rubberduckvba.com.Server.ContentSynchronization.XmlDoc.Schema; -using rubberduckvba.com.Server.Data; +using rubberduckvba.Server; +using rubberduckvba.Server.ContentSynchronization; +using rubberduckvba.Server.ContentSynchronization.XmlDoc.Schema; +using rubberduckvba.Server.Model; using System.Collections.Immutable; using System.Security.Claims; using System.Text; using System.Web; using System.Xml.Linq; -namespace rubberduckvba.com.Server.Services; +namespace rubberduckvba.Server.Services; public interface IGitHubClientService { Task ValidateTokenAsync(string token); Task> GetAllTagsAsync(); - Task GetTagAsync(string token, string name); + Task GetTagAsync(string? token, string name); Task> GetCodeAnalysisDefaultsConfigAsync(); } public class GitHubClientService(IOptions configuration, ILogger logger) : IGitHubClientService { - public async Task ValidateTokenAsync(string token) + public async Task ValidateTokenAsync(string? token) { + if (token is null) + { + return null; + } + var config = configuration.Value; var credentials = new Credentials(token); var client = new GitHubClient(new ProductHeaderValue(config.UserAgent), new InMemoryCredentialStore(credentials)); @@ -55,29 +61,29 @@ public async Task> GetAllTagsAsync() var releases = await client.Repository.Release.GetAll(config.OwnerOrg, config.Rubberduck, new ApiOptions { PageCount = 1, PageSize = 10 }); return (from release in releases - let installer = release.Assets.SingleOrDefault(asset => asset.Name.EndsWith(".exe") && asset.Name.StartsWith("Rubberduck.Setup")) - select new TagGraph - { - ReleaseId = release.Id, - Name = release.TagName, - DateCreated = release.CreatedAt.Date, - IsPreRelease = release.Prerelease, - InstallerDownloads = installer?.DownloadCount ?? 0, - InstallerDownloadUrl = installer?.BrowserDownloadUrl ?? string.Empty, - Assets = (from asset in release.Assets - where asset.Name.EndsWith(".xml") - select new TagAsset - { - Name = asset.Name, - DownloadUrl = asset.BrowserDownloadUrl - }).ToImmutableArray() - }).ToImmutableArray(); + let installer = release.Assets.SingleOrDefault(asset => asset.Name.EndsWith(".exe") && asset.Name.StartsWith("Rubberduck.Setup")) + select new TagGraph + { + ReleaseId = release.Id, + Name = release.TagName, + DateCreated = release.CreatedAt.Date, + IsPreRelease = release.Prerelease, + InstallerDownloads = installer?.DownloadCount ?? 0, + InstallerDownloadUrl = installer?.BrowserDownloadUrl ?? string.Empty, + Assets = (from asset in release.Assets + where asset.Name.EndsWith(".xml") + select new TagAsset + { + Name = asset.Name, + DownloadUrl = asset.BrowserDownloadUrl + }).ToImmutableArray() + }).ToImmutableArray(); } - public async Task GetTagAsync(string token, string name) + public async Task GetTagAsync(string? token, string name) { var config = configuration.Value; - var credentials = new Credentials(config.OrgToken); + var credentials = new Credentials(token ?? config.OrgToken); var client = new GitHubClient(new ProductHeaderValue(config.UserAgent), new InMemoryCredentialStore(credentials)); var release = await client.Repository.Release.Get(config.OwnerOrg, config.Rubberduck, name); @@ -164,7 +170,7 @@ private XDocument ParseCodeInspectionSettings(byte[] rawContent) var length = endIndex - startIndex + encodedTagClose.Length; var encodedDocument = encoded.Substring(startIndex, length); var decoded = HttpUtility.HtmlDecode(encodedDocument); - + var document = XDocument.Parse(decoded); return document; } diff --git a/rubberduckvba.Server/Services/IContentOrchestrator.cs b/rubberduckvba.Server/Services/IContentOrchestrator.cs index 291700f..e9ce9e5 100644 --- a/rubberduckvba.Server/Services/IContentOrchestrator.cs +++ b/rubberduckvba.Server/Services/IContentOrchestrator.cs @@ -1,6 +1,6 @@ -using rubberduckvba.com.Server.ContentSynchronization; +using rubberduckvba.Server.ContentSynchronization; -namespace rubberduckvba.com.Server.Services; +namespace rubberduckvba.Server.Services; public interface IContentOrchestrator where TRequest : SyncRequestParameters { diff --git a/rubberduckvba.Server/Services/InstallerDownloadStatsOrchestrator.cs b/rubberduckvba.Server/Services/InstallerDownloadStatsOrchestrator.cs index a68264c..50b2adf 100644 --- a/rubberduckvba.Server/Services/InstallerDownloadStatsOrchestrator.cs +++ b/rubberduckvba.Server/Services/InstallerDownloadStatsOrchestrator.cs @@ -1,8 +1,8 @@ -using rubberduckvba.com.Server.ContentSynchronization; -using rubberduckvba.com.Server.ContentSynchronization.Pipeline; -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; -namespace rubberduckvba.com.Server.Services; +namespace rubberduckvba.Server.Services; public class InstallerDownloadStatsOrchestrator(ISynchronizationPipelineFactory factory) : IContentOrchestrator { @@ -13,14 +13,14 @@ public async Task UpdateContentAsync(TagSyncRequestParameters request, Cancellat { await pipeline.ExecuteAsync(request, tokenSource); } - catch (TaskCanceledException) + catch (TaskCanceledException e) { var exceptions = pipeline.Exceptions.ToList(); if (exceptions.Count > 0) { if (exceptions.Count == 1) { - throw exceptions[0]; + throw new OperationCanceledException(e.Message, exceptions[0]); } else { diff --git a/rubberduckvba.Server/Services/MarkdownFormattingService.cs b/rubberduckvba.Server/Services/MarkdownFormattingService.cs index 248c387..00a6171 100644 --- a/rubberduckvba.Server/Services/MarkdownFormattingService.cs +++ b/rubberduckvba.Server/Services/MarkdownFormattingService.cs @@ -2,35 +2,71 @@ using MarkdownSharp; using RubberduckServices; -namespace rubberduckvba.com.Server.Services; +namespace rubberduckvba.Server.Services; public interface IMarkdownFormattingService { - Task FormatMarkdownDocument(string content, bool withSyntaxHighlighting = false); + string FormatMarkdownDocument(string content, bool withSyntaxHighlighting = false); } public class MarkdownFormattingService(ISyntaxHighlighterService service) : IMarkdownFormattingService { - public async Task FormatMarkdownDocument(string content, bool withSyntaxHighlighting = false) + private static readonly Markdown _service = new(); + + public string FormatMarkdownDocument(string content, bool withSyntaxHighlighting = false) { if (string.IsNullOrWhiteSpace(content)) { return string.Empty; } - var markdown = content.Replace("\n","\r\n"); + var markdown = content.Replace("\n", "\r\n"); + var html = string.Empty; + if (withSyntaxHighlighting) { - markdown = await PreProcessMarkdownString(markdown); + // HTML tags makes the markdown formatter silently fail, + // so we pull out any blocks and format everything between them. + + markdown = PreProcessMarkdownString(markdown); + + var lastSectionStart = markdown.LastIndexOf(""); + if (lastSectionStart > 0) + { + var sectionStart = 0; + while (sectionStart < lastSectionStart) + { + var sectionEnd = markdown.IndexOf("", sectionEnd) > 0 + ? markdown.IndexOf("", sectionEnd) + 8 + : lastSectionStart; + + var block = markdown.Substring(sectionEnd, sectionStart - sectionEnd).Replace("", ""); + html += section + block; + } + } + else + { + html = _service.Transform(markdown); + } + } + else + { + html = _service.Transform(markdown); } - var html = new Markdown().Transform(markdown); - var result = await PostProcessHtml(html); - - return await Task.FromResult(result); + return PostProcessHtml(html); } - private async Task PreProcessMarkdownString(string content) + private string PreProcessMarkdownString(string content) { var document = new HtmlDocument(); document.LoadHtml($"
{content}
"); @@ -38,10 +74,10 @@ private async Task PreProcessMarkdownString(string content) var codeNodes = document.DocumentNode.Descendants("code").ToList(); foreach (var node in codeNodes) { - var code = await service.FormatAsync(node.InnerText); + var code = service.Format(node.InnerText); - node.Name = "div"; - node.EndNode.Name = "div"; + //node.Name = "div"; + //node.EndNode.Name = "div"; node.AddClass("vbe-mock-debugger"); node.InnerHtml = string.Empty; @@ -63,10 +99,10 @@ private async Task PreProcessMarkdownString(string content) node.AppendChild(codeAreaDiv); } - return document.DocumentNode.FirstChild.InnerHtml; + return document.DocumentNode.InnerHtml; } - private async Task PostProcessHtml(string html) + private string PostProcessHtml(string html) { var document = new HtmlDocument(); document.LoadHtml($"
{html}
"); @@ -76,6 +112,6 @@ private async Task PostProcessHtml(string html) node.AddClass("document-img"); } - return await Task.FromResult(document.DocumentNode.FirstChild.InnerHtml); + return document.DocumentNode.FirstChild.InnerHtml; } } diff --git a/rubberduckvba.Server/Services/RubberduckDbService.cs b/rubberduckvba.Server/Services/RubberduckDbService.cs index ec24661..458c5cd 100644 --- a/rubberduckvba.Server/Services/RubberduckDbService.cs +++ b/rubberduckvba.Server/Services/RubberduckDbService.cs @@ -1,41 +1,52 @@ -using Dapper; -using Microsoft.Data.SqlClient; +using Microsoft.Data.SqlClient; using Microsoft.Extensions.Options; -using rubberduckvba.com.Server.ContentSynchronization.Pipeline; -using rubberduckvba.com.Server.Data; -using System.Collections.Immutable; -using System.Diagnostics; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; +using rubberduckvba.Server.Model; +using rubberduckvba.Server.Services.rubberduckdb; -namespace rubberduckvba.com.Server.Services; +namespace rubberduckvba.Server.Services; public interface IStagingServices { Task StageAsync(StagingContext staging, CancellationToken token); } -public class StagingServices(IRubberduckDbService service) : IStagingServices +public class StagingServices(TagServices tagService, FeatureServices featureServices) : IStagingServices { public async Task StageAsync(StagingContext context, CancellationToken token) { token.ThrowIfCancellationRequested(); - var updatedTags = context.ProcessedTags.Where(e => e.Id != default); - await service.UpdateAsync(updatedTags); + var updatedTags = context.Tags.Where(e => e.Id != default); + tagService.Update(updatedTags); token.ThrowIfCancellationRequested(); - var newTags = context.ProcessedTags.Where(e => e.Id == default); - await service.CreateAsync(newTags, context.Parameters.RepositoryId); + var newTags = context.Tags.Where(e => e.Id == default); + tagService.Create(newTags); token.ThrowIfCancellationRequested(); - await service.UpdateAsync(context.UpdatedFeatureItems); + var updatedInspections = context.Inspections.Where(e => e.Id != default); + featureServices.Update(updatedInspections); token.ThrowIfCancellationRequested(); - await service.CreateAsync(context.NewFeatureItems); - } -} + var newInspections = context.Inspections.Where(e => e.Id == default); + featureServices.Insert(newInspections); -public interface ISynchronizationService -{ - Task LogAsync(SynchronizationRequest synchronization); + token.ThrowIfCancellationRequested(); + var updatedQuickFixes = context.QuickFixes.Where(e => e.Id != default); + featureServices.Update(updatedQuickFixes); + + token.ThrowIfCancellationRequested(); + var newQuickFixes = context.QuickFixes.Where(e => e.Id == default); + featureServices.Insert(newQuickFixes); + + token.ThrowIfCancellationRequested(); + var updatedAnnotations = context.Annotations.Where(e => e.Id != default); + featureServices.Update(updatedAnnotations); + + token.ThrowIfCancellationRequested(); + var newAnnotations = context.Annotations.Where(e => e.Id == default); + featureServices.Insert(newAnnotations); + } } public enum RepositoryId @@ -47,85 +58,31 @@ public enum RepositoryId public interface IRubberduckDbService { Task> GetAllTagsAsync(); - Task> GetLatestTagsAsync(RepositoryId repositoryId); Task GetLatestTagAsync(RepositoryId repositoryId, bool includePreRelease); - Task UpdateAsync(IEnumerable tags); - Task UpdateAsync(IEnumerable featureItems); - Task CreateAsync(IEnumerable tags, RepositoryId repositoryId); - Task CreateAsync(IEnumerable featureItems); Task> GetTopLevelFeatures(RepositoryId? repositoryId = default); Task ResolveFeature(RepositoryId repositoryId, string name); Task GetFeatureId(RepositoryId repositoryId, string name); - Task> GetXmlDocFeaturesAsync(RepositoryId repositoryId); Task SaveFeature(Feature feature); } -public class SynchronizationDbService : ISynchronizationService -{ - private readonly string _connectionString; - - public SynchronizationDbService(IOptions settings, ILogger logger) - { - _connectionString = settings.Value.RubberduckDb ?? throw new InvalidOperationException(); - Logger = logger; - } - - private ILogger Logger { get; } - - public async Task LogAsync(SynchronizationRequest synchronization) - { - const string insertSql = @" -INSERT INTO [SynchronizationRequests] ([DateTimeInserted],[RequestId],[Status],[Message]) -VALUES (@ts, @requestId, @status, @message);"; - const string updateSql = @" -UPDATE [SynchronizationRequests] SET - [DateTimeUpdated]=@ts, - [UtcDateTimeStarted]=@utcStart, - [UtcDateTimeEnded]=@utcEnd, - [Status]=@status, - [Message]=@message -WHERE [RequestId]=@requestId; -"; - using var db = new SqlConnection(_connectionString); - await db.OpenAsync(); - - using var transaction = await db.BeginTransactionAsync(); - - if (synchronization.Status == default) - { - await db.ExecuteAsync(insertSql, new - { - ts = TimeProvider.System.GetUtcNow().ToTimestampString(), - requestId = synchronization.RequestId, - status = (int)synchronization.Status, - message = synchronization.Message, - }, transaction); - } - else - { - await db.ExecuteAsync(updateSql, new - { - ts = TimeProvider.System.GetUtcNow().ToTimestampString(), - requestId = synchronization.RequestId, - }, transaction); - } - - await transaction.CommitAsync(); - } -} - public class RubberduckDbService : IRubberduckDbService { private readonly string _connectionString; + private readonly TagServices _tagServices; + private readonly FeatureServices _featureServices; - public RubberduckDbService(IOptions settings, ILogger logger) + public RubberduckDbService(IOptions settings, ILogger logger, + TagServices tagServices, FeatureServices featureServices) { _connectionString = settings.Value.RubberduckDb ?? throw new InvalidOperationException("ConnectionString 'RubberduckDb' could not be retrieved."); Logger = logger; + + _tagServices = tagServices; + _featureServices = featureServices; } private ILogger Logger { get; } @@ -140,501 +97,234 @@ private async Task GetDbConnection() public async Task> GetAllTagsAsync() { - const string sql = @" -SELECT - [Id], - [DateTimeInserted], - [DateTimeUpdated], - [RepositoryId], - [ReleaseId], - [Name], - [DateCreated], - [InstallerDownloadUrl], - [InstallerDownloads], - [IsPreRelease] -FROM [Tags] -"; - using var db = await GetDbConnection(); - - var sw = Stopwatch.StartNew(); - var result = (await db.QueryAsync(sql)).ToArray(); - sw.Stop(); - - Logger.LogInformation(nameof(GetAllTagsAsync) + " | SELECT operation completed ({results}) | ⏱️ {elapsed}", result.Length, sw.Elapsed); - return result; + return _tagServices.GetAllTags(); + // const string sql = @" + //SELECT + // [Id], + // [DateTimeInserted], + // [DateTimeUpdated], + // [RepositoryId], + // [ReleaseId], + // [Name], + // [DateCreated], + // [InstallerDownloadUrl], + // [InstallerDownloads], + // [IsPreRelease] + //FROM [Tags] + //"; + // using var db = await GetDbConnection(); + + // var sw = Stopwatch.StartNew(); + // var result = (await db.QueryAsync(sql)).ToArray(); + // sw.Stop(); + + // Logger.LogInformation(nameof(GetAllTagsAsync) + " | SELECT operation completed ({results}) | ⏱️ {elapsed}", result.Length, sw.Elapsed); + // return result; } public async Task> GetTopLevelFeatures(RepositoryId? repositoryId = default) { - const string sql = @" -SELECT - [Id], - [DateTimeInserted], - [DateTimeUpdated], - [RepositoryId], - [Name], - [Title], - [ShortDescription], - [IsNew], - [HasImage] -FROM [Features] -WHERE [RepositoryId]=ISNULL(@repositoryId,[RepositoryId]) -AND [ParentId] IS NULL -AND [IsHidden]=0; -"; - using var db = await GetDbConnection(); - var parameters = new { repositoryId = (int)(repositoryId ?? RepositoryId.Rubberduck) }; - - var sw = Stopwatch.StartNew(); - var result = (await db.QueryAsync(sql, parameters)).ToArray(); - sw.Stop(); - - Logger.LogInformation(nameof(GetTopLevelFeatures) + " | SELECT operation completed ({results}) | ⏱️ {elapsed}", result.Length, sw.Elapsed); - return result; + return _featureServices.Get(topLevelOnly: true); + // const string sql = @" + //SELECT + // [Id], + // [DateTimeInserted], + // [DateTimeUpdated], + // [RepositoryId], + // [Name], + // [Title], + // [ShortDescription], + // [IsNew], + // [HasImage] + //FROM [Features] + //WHERE [RepositoryId]=ISNULL(@repositoryId,[RepositoryId]) + //AND [ParentId] IS NULL + //AND [IsHidden]=0; + //"; + // using var db = await GetDbConnection(); + // var parameters = new { repositoryId = (int)(repositoryId ?? RepositoryId.Rubberduck) }; + + // var sw = Stopwatch.StartNew(); + // var result = (await db.QueryAsync(sql, parameters)).ToArray(); + // sw.Stop(); + + // Logger.LogInformation(nameof(GetTopLevelFeatures) + " | SELECT operation completed ({results}) | ⏱️ {elapsed}", result.Length, sw.Elapsed); + // return result; } public async Task ResolveFeature(RepositoryId repositoryId, string name) { - const string featureSql = @" -WITH feature AS ( - SELECT [Id] - FROM [Features] - WHERE [RepositoryId]=@repositoryId AND LOWER([Name])=LOWER(@name) -) -SELECT - src.[Id], - src.[ParentId], - src.[DateTimeInserted], - src.[DateTimeUpdated], - src.[Name], - src.[Title], - src.[ShortDescription], - src.[Description], - src.[IsNew], - src.[HasImage] -FROM [Features] src -INNER JOIN feature ON src.[Id]=feature.[Id] OR src.[ParentId]=feature.[Id]; -"; - const string itemSql = @" -WITH feature AS ( - SELECT [Id] - FROM [Features] - WHERE [RepositoryId]=@repositoryId AND LOWER([Name])=LOWER(@name) -) -SELECT - src.[Id], - src.[DateTimeInserted], - src.[DateTimeUpdated], - src.[FeatureId], - f.[Name] AS [FeatureName], - f.[Title] AS [FeatureTitle], - src.[Name], - src.[Title], - src.[Description] AS [Summary], - src.[IsNew], - src.[IsDiscontinued], - src.[IsHidden], - src.[TagAssetId], - t.[Id] AS [TagId], - src.[SourceUrl], - src.[Serialized], - t.[Name] AS [TagName] -FROM [FeatureXmlDoc] src -INNER JOIN feature ON src.[FeatureId]=feature.[Id] -INNER JOIN [Features] f ON feature.[Id]=f.[Id] -INNER JOIN [TagAssets] a ON src.[TagAssetId]=a.[Id] -INNER JOIN [Tags] t ON a.[TagId]=t.[Id] -ORDER BY src.[IsNew] DESC, src.[IsDiscontinued] DESC, src.[Name]; -"; - - using var db = await GetDbConnection(); - var sw = Stopwatch.StartNew(); - - var parameters = new { repositoryId, name }; - var features = await db.QueryAsync(featureSql, parameters); - var materialized = features.ToArray(); - - var items = await db.QueryAsync(itemSql, parameters); - - var graph = materialized.Where(e => e.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)) - .Select(e => FeatureGraph.FromFeature(e) with - { - Features = materialized.Where(f => f.ParentId == e.Id).ToImmutableArray(), - Items = items.ToImmutableArray() - }).Single(); - sw.Stop(); - - Logger.LogInformation(nameof(ResolveFeature) + " | All SELECT operations completed | ⏱️ {elapsed}", sw.Elapsed); - return graph; + var features = _featureServices.Get(topLevelOnly: false).ToList(); + var feature = features.Single(e => string.Equals(e.Name, name, StringComparison.OrdinalIgnoreCase)); + var children = features.Where(e => e.ParentId == feature.Id); + return new FeatureGraph(feature.ToEntity()) + { + Features = children.ToArray() + }; + // const string featureSql = @" + //WITH feature AS ( + // SELECT [Id] + // FROM [Features] + // WHERE [RepositoryId]=@repositoryId AND LOWER([Name])=LOWER(@name) + //) + //SELECT + // src.[Id], + // src.[ParentId], + // src.[DateTimeInserted], + // src.[DateTimeUpdated], + // src.[Name], + // src.[Title], + // src.[ShortDescription], + // src.[Description], + // src.[IsNew], + // src.[HasImage] + //FROM [Features] src + //INNER JOIN feature ON src.[Id]=feature.[Id] OR src.[ParentId]=feature.[Id]; + //"; + // const string itemSql = @" + //WITH feature AS ( + // SELECT [Id] + // FROM [Features] + // WHERE [RepositoryId]=@repositoryId AND LOWER([Name])=LOWER(@name) + //) + //SELECT + // src.[Id], + // src.[DateTimeInserted], + // src.[DateTimeUpdated], + // src.[FeatureId], + // f.[Name] AS [FeatureName], + // f.[Title] AS [FeatureTitle], + // src.[Name], + // src.[Title], + // src.[Description] AS [Summary], + // src.[IsNew], + // src.[IsDiscontinued], + // src.[IsHidden], + // src.[TagAssetId], + // t.[Id] AS [TagId], + // src.[SourceUrl], + // src.[Serialized], + // t.[Name] AS [TagName] + //FROM [FeatureXmlDoc] src + //INNER JOIN feature ON src.[FeatureId]=feature.[Id] + //INNER JOIN [Features] f ON feature.[Id]=f.[Id] + //INNER JOIN [TagAssets] a ON src.[TagAssetId]=a.[Id] + //INNER JOIN [Tags] t ON a.[TagId]=t.[Id] + //ORDER BY src.[IsNew] DESC, src.[IsDiscontinued] DESC, src.[Name]; + //"; + + // using var db = await GetDbConnection(); + // var sw = Stopwatch.StartNew(); + + // var parameters = new { repositoryId, name }; + // var features = await db.QueryAsync(featureSql, parameters); + // var materialized = features.ToArray(); + + // var items = await db.QueryAsync(itemSql, parameters); + + // var graph = materialized.Where(e => e.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)) + // .Select(e => new FeatureGraph(e.ToEntity()) with + // { + // Features = materialized.Where(f => f.ParentId == e.Id).ToImmutableArray(), + // Items = items.ToImmutableArray() + // }).Single(); + // sw.Stop(); + + // Logger.LogInformation(nameof(ResolveFeature) + " | All SELECT operations completed | ⏱️ {elapsed}", sw.Elapsed); + // return graph; } public async Task SaveFeature(Feature feature) { - const string insertSql = @" -INSERT INTO [Features] ([DateTimeInserted],[RepositoryId],[ParentId],[Name],[Title],[ShortDescription],[Description],[IsHidden],[IsNew],[HasImage]) -VALUES (@ts,@repositoryId,@parentId,@name,@title,@shortDescription,@description,@isHidden,@isNew,@hasImage) -RETURNING [Id]; -"; - const string updateSql = @" -UPDATE [Features] SET - [DateTimeUpdated]=@ts, - [RepositoryId]=@repositoryId, - [ParentId]=@parentId, - [Name]=@name, - [Title]=@title, - [ShortDescription]=@shortDescription, - [Description]=@description, - [IsHidden]=@isHidden, - [IsNew]=@isNew, - [HasImage]=@hasImage -WHERE [Id]=@id; -"; - Feature result; - - using var db = await GetDbConnection(); - using var transaction = await db.BeginTransactionAsync(); - if (feature.Id == default) { - var parameters = new - { - ts = TimeProvider.System.GetUtcNow().ToTimestampString(), - repositoryId = feature.RepositoryId, - parentId = feature.ParentId, - name = feature.Name, - shortDescription = feature.ShortDescription, - description = feature.Description, - isHidden = feature.IsHidden, - isNew = feature.IsNew, - hasImage = feature.HasImage, - }; - var id = await db.ExecuteAsync(insertSql, parameters, transaction); - result = feature with { Id = id }; + _featureServices.Insert(new FeatureGraph(feature.ToEntity())); } else { - var parameters = new - { - ts = TimeProvider.System.GetUtcNow().ToTimestampString(), - repositoryId = feature.RepositoryId, - parentId = feature.ParentId, - name = feature.Name, - shortDescription = feature.ShortDescription, - description = feature.Description, - isHidden = feature.IsHidden, - isNew = feature.IsNew, - hasImage = feature.HasImage, - id = feature.Id - }; - await db.ExecuteAsync(updateSql, parameters, transaction); - result = feature; + _featureServices.Update(new FeatureGraph(feature.ToEntity())); } - - var trx = Stopwatch.StartNew(); - await transaction.CommitAsync(); - trx.Stop(); - Logger.LogInformation(nameof(SaveFeature) + " | Transaction committed | ⏱️ {elapsed}", trx.Elapsed); - return result; + // TODO return with id + return feature; + // const string insertSql = @" + //INSERT INTO [Features] ([DateTimeInserted],[RepositoryId],[ParentId],[Name],[Title],[ShortDescription],[Description],[IsHidden],[IsNew],[HasImage]) + //VALUES (@ts,@repositoryId,@parentId,@name,@title,@shortDescription,@description,@isHidden,@isNew,@hasImage) + //RETURNING [Id]; + //"; + // const string updateSql = @" + //UPDATE [Features] SET + // [DateTimeUpdated]=@ts, + // [RepositoryId]=@repositoryId, + // [ParentId]=@parentId, + // [Name]=@name, + // [Title]=@title, + // [ShortDescription]=@shortDescription, + // [Description]=@description, + // [IsHidden]=@isHidden, + // [IsNew]=@isNew, + // [HasImage]=@hasImage + //WHERE [Id]=@id; + //"; + // Feature result; + + // using var db = await GetDbConnection(); + // using var transaction = await db.BeginTransactionAsync(); + + // if (feature.Id == default) + // { + // var parameters = new + // { + // ts = TimeProvider.System.GetUtcNow().ToTimestampString(), + // repositoryId = feature.RepositoryId, + // parentId = feature.ParentId, + // name = feature.Name, + // shortDescription = feature.ShortDescription, + // description = feature.Description, + // isHidden = feature.IsHidden, + // isNew = feature.IsNew, + // hasImage = feature.HasImage, + // }; + // var id = await db.ExecuteAsync(insertSql, parameters, transaction); + // result = feature with { Id = id }; + // } + // else + // { + // var parameters = new + // { + // ts = TimeProvider.System.GetUtcNow().ToTimestampString(), + // repositoryId = feature.RepositoryId, + // parentId = feature.ParentId, + // name = feature.Name, + // shortDescription = feature.ShortDescription, + // description = feature.Description, + // isHidden = feature.IsHidden, + // isNew = feature.IsNew, + // hasImage = feature.HasImage, + // id = feature.Id + // }; + // await db.ExecuteAsync(updateSql, parameters, transaction); + // result = feature; + // } + + // var trx = Stopwatch.StartNew(); + // await transaction.CommitAsync(); + // trx.Stop(); + // Logger.LogInformation(nameof(SaveFeature) + " | Transaction committed | ⏱️ {elapsed}", trx.Elapsed); + // return result; } public async Task CreateAsync(IEnumerable tags, RepositoryId repositoryId) - { - if (!tags.Any()) - { - return; - } - - const string tagSql = @" -INSERT INTO [Tags] ([DateTimeInserted],[RepositoryId],[Name],[DateCreated],[InstallerDownloadUrl],[InstallerDownloads],[IsPreRelease]) -VALUES (@ts, @repositoryId, @name, @dateCreated, @installerDownloadUrl, @installerDownloads, @isPreRelease) -RETURNING [Id];"; - - const string assetSql = @" -INSERT INTO [TagAssets] ([DateTimeInserted],[TagId],[Name],[DownloadUrl]) -VALUES (@ts, @tagId, @name, @downloadUrl); -"; - - using var db = await GetDbConnection(); - using var transaction = db.BeginTransaction(); - - foreach (var tag in tags) - { - var tagInfo = new - { - ts = TimeProvider.System.GetUtcNow().ToTimestampString(), - repositoryId = (int)repositoryId, - name = tag.Name, - dateCreated = tag.DateCreated, - installerDownloadUrl = tag.InstallerDownloadUrl, - installerDownloads = tag.InstallerDownloads, - isPreRelease = tag.IsPreRelease - }; - await db.ExecuteAsync(tagSql, tagInfo, transaction); - var id = await db.ExecuteScalarAsync("SELECT [Id] FROM [Tags] WHERE [Name]=@name;", new { name = tag.Name }); - - foreach (var asset in tag.Assets) - { - var assetInfo = new - { - ts = TimeProvider.System.GetUtcNow().ToTimestampString(), - tagId = id, - name = asset.Name, - downloadUrl = asset.DownloadUrl - }; - await db.ExecuteAsync(assetSql, assetInfo, transaction); - } - } - - var trx = Stopwatch.StartNew(); - await transaction.CommitAsync(); - trx.Stop(); - Logger.LogInformation(nameof(CreateAsync) + " (" + nameof(TagGraph) + ") | Transaction committed | ⏱️ {elapsed}", trx.Elapsed); - } + => _tagServices.Create(tags); public async Task> GetLatestTagsAsync(RepositoryId repositoryId) - { - const string sql = @" -WITH t AS ( - SELECT [Id], RANK() OVER (PARTITION BY src.[IsPreRelease] ORDER BY src.[DateCreated] DESC) AS [Rank] - FROM [Tags] src - WHERE [RepositoryId]=@repositoryId -) -SELECT - src.[Id], - src.[DateTimeInserted], - src.[DateTimeUpdated], - src.[Name], - src.[DateCreated], - src.[InstallerDownloadUrl], - src.[InstallerDownloads], - src.[IsPreRelease] -FROM [Tags] src -INNER JOIN t ON src.[Id]=t.[Id] -WHERE t.[Rank]=1;"; - - using var db = await GetDbConnection(); - var sw = Stopwatch.StartNew(); - var results = await db.QueryAsync(sql, new { repositoryId = (int)repositoryId }); - - Logger.LogInformation(nameof(GetLatestTagsAsync) + " | SELECT operation completed | ⏱️ {elapsed}", sw.Elapsed); - return results; - } + => _tagServices.GetLatestTags(); public async Task GetLatestTagAsync(RepositoryId repositoryId, bool preRelease) - { - const string tagSql = @" -WITH t AS ( - SELECT [Id], RANK() OVER (PARTITION BY src.[IsPreRelease] ORDER BY src.[DateCreated] DESC) AS [Rank] - FROM [Tags] src - WHERE [RepositoryId]=@repositoryId -) -SELECT - src.[Id], - src.[DateTimeInserted], - src.[DateTimeUpdated], - src.[Name], - src.[DateCreated], - src.[InstallerDownloadUrl], - src.[InstallerDownloads], - src.[IsPreRelease] -FROM [Tags] src -INNER JOIN t ON src.[Id]=t.[Id] -WHERE t.[Rank]=1 AND src.[IsPreRelease]=@pre -"; - const string assetsSql = @" -SELECT - src.[Id], - src.[DateTimeInserted], - src.[DateTimeUpdated], - src.[TagId], - src.[Name], - src.[DownloadUrl] -FROM [TagAssets] src -WHERE src.[TagId]=@id -"; - - using var db = await GetDbConnection(); - var sw = Stopwatch.StartNew(); - - var tag = await db.QuerySingleAsync(tagSql, new { repositoryId = (int)repositoryId, pre = preRelease }); - var assets = await db.QueryAsync(assetsSql, new { id = tag.Id }); - - Logger.LogInformation(nameof(GetLatestTagAsync) + " | All SELECT operations completed | ⏱️ {elapsed}", sw.Elapsed); - - return new TagGraph - { - Id = tag.Id, - DateTimeInserted = tag.DateTimeInserted, - DateTimeUpdated = tag.DateTimeUpdated, - DateCreated = tag.DateCreated, - ReleaseId = tag.ReleaseId, - IsPreRelease = tag.IsPreRelease, - Name = tag.Name, - InstallerDownloadUrl = tag.InstallerDownloadUrl, - InstallerDownloads = tag.InstallerDownloads, - Assets = assets - }; - } + => _tagServices.GetLatestTag(preRelease); public async Task UpdateAsync(IEnumerable tags) - { - if (!tags.Any()) - { - return; - } - - const string sql = @" -UPDATE [Tags] SET - [DateTimeUpdated]=@ts, - [InstallerDownloads]=@installerDownloads -WHERE [Id]=@id;"; - - using var db = await GetDbConnection(); - var sw = Stopwatch.StartNew(); - using var transaction = await db.BeginTransactionAsync(); - - foreach (var tag in tags) - { - var args = new - { - ts = TimeProvider.System.GetUtcNow().ToTimestampString(), - id = tag.Id, - installerDownloads = tag.InstallerDownloads - }; - await db.ExecuteAsync(sql, args, transaction); - } - - await transaction.CommitAsync(); - sw.Stop(); - - Logger.LogInformation(nameof(UpdateAsync) + "(" + nameof(Tag) + ") | Transaction committed; all UPDATE operations completed | ⏱️ {elapsed}", sw.Elapsed); - } + => _tagServices.Update(tags); public async Task GetFeatureId(RepositoryId repositoryId, string name) - { - const string sql = "SELECT [Id] FROM [Features] WHERE [RepositoryId]=@repositoryId AND [Name]=@name;"; - - using var db = await GetDbConnection(); - var sw = Stopwatch.StartNew(); - - var id = await db.ExecuteScalarAsync(sql, new { repositoryId, name }); - Logger.LogInformation(nameof(GetFeatureId) + " | SELECT operation completed | ⏱️ {elapsed}", sw.Elapsed); - - return id; - } - - public async Task> GetXmlDocFeaturesAsync(RepositoryId repositoryId) - { - const string sql = @" -SELECT - x.[Id], - x.[DateTimeInserted], - x.[DateTimeUpdated], - x.[FeatureId], - x.[Name], - x.[Title], - x.[Description], - x.[IsNew], - x.[IsDiscontinued], - x.[IsHidden], - x.[TagAssetId], - x.[SourceUrl], - x.[Serialized] -FROM [FeatureXmlDoc] AS x -INNER JOIN [Features] AS f ON x.[FeatureId]=f.[Id] -WHERE f.[RepositoryId]=@repositoryId"; - - using var db = await GetDbConnection(); - var sw = Stopwatch.StartNew(); - - var result = await db.QueryAsync(sql, new { repositoryId }); - Logger.LogInformation(nameof(GetXmlDocFeaturesAsync) + " | SELECT operation completed | ⏱️ {elapsed}", sw.Elapsed); - - return result; - } - - public async Task UpdateAsync(IEnumerable featureItems) - { - const string updateSql = @" -UPDATE [FeatureXmlDoc] -SET [DateTimeUpdated]=@ts, - [Title]=@title, - [Description]=@description, - [IsNew]=@isNew, - [IsDiscontinued]=@isDiscontinued, - [IsHidden]=@isHidden, - [TagAssetId]=@tagAssetId, - [SourceUrl]=@sourceUrl, - [Serialized]=@serialized -WHERE [Id]=@id"; - - if (!featureItems.Any()) - { - return; - } - - using var db = await GetDbConnection(); - using var transaction = db.BeginTransaction(); - var sw = Stopwatch.StartNew(); - - var timestamp = TimeProvider.System.GetUtcNow().ToTimestampString(); - foreach (var update in featureItems) - { - await db.ExecuteAsync(updateSql, new - { - ts = timestamp, - title = update.Title, - description = update.Summary, - isNew = update.IsNew, - isDiscontinued = update.IsDiscontinued, - isHidden = update.IsHidden, - tagAssetId = update.TagAssetId, - sourceUrl = update.SourceUrl, - serialized = update.Serialized, - id = update.Id, - }, transaction); - } - - await transaction.CommitAsync(); - Logger.LogInformation(nameof(UpdateAsync) + "(" + nameof(FeatureXmlDoc) + ") | Transaction committed; all UPDATE operations completed | ⏱️ {elapsed}", sw.Elapsed); - } - - public async Task CreateAsync(IEnumerable featureItems) - { - const string insertSql = @" -INSERT INTO [FeatureXmlDoc] ([DateTimeInserted],[FeatureId],[Name],[Title],[Description],[IsNew],[IsDiscontinued],[IsHidden],[TagAssetId],[SourceUrl],[Serialized]) -VALUES (@ts,@featureId,@name,@title,@description,@isNew,@isDiscontinued,@isHidden,@tagAssetId,@sourceUrl,@serialized); -"; - - if (!featureItems.Any()) - { - return; - } - - using var db = await GetDbConnection(); - using var transaction = db.BeginTransaction(); - var sw = Stopwatch.StartNew(); - - var timestamp = TimeProvider.System.GetUtcNow().ToTimestampString(); - foreach (var insert in featureItems) - { - await db.ExecuteAsync(insertSql, new - { - ts = timestamp, - featureId = insert.FeatureId, - name = insert.Name, - title = insert.Title, - description = insert.Summary, - isNew = insert.IsNew, - isDiscontinued = insert.IsDiscontinued, - isHidden = insert.IsHidden, - tagAssetId = insert.TagAssetId, - sourceUrl = insert.SourceUrl, - serialized = insert.Serialized - }, transaction); - } - - await transaction.CommitAsync(); - Logger.LogInformation(nameof(CreateAsync) + "(" + nameof(FeatureXmlDoc) + ") | Transaction committed; all INSERT operations completed | ⏱️ {elapsed}", sw.Elapsed); - } + => _featureServices.GetId(name); } \ No newline at end of file diff --git a/rubberduckvba.Server/Services/ServiceLogger.cs b/rubberduckvba.Server/Services/ServiceLogger.cs index 52bfc87..0aa5884 100644 --- a/rubberduckvba.Server/Services/ServiceLogger.cs +++ b/rubberduckvba.Server/Services/ServiceLogger.cs @@ -1,3 +1,3 @@ -namespace rubberduckvba.com.Server.Services; +namespace rubberduckvba.Server.Services; public class ServiceLogger { } \ No newline at end of file diff --git a/rubberduckvba.Server/Services/XmldocContentOrchestrator.cs b/rubberduckvba.Server/Services/XmldocContentOrchestrator.cs index 15b16a4..8c16049 100644 --- a/rubberduckvba.Server/Services/XmldocContentOrchestrator.cs +++ b/rubberduckvba.Server/Services/XmldocContentOrchestrator.cs @@ -1,8 +1,8 @@ -using rubberduckvba.com.Server.ContentSynchronization; -using rubberduckvba.com.Server.ContentSynchronization.Pipeline; -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Abstract; +using rubberduckvba.Server.ContentSynchronization.Pipeline.Sections.Context; -namespace rubberduckvba.com.Server.Services; +namespace rubberduckvba.Server.Services; public class XmldocContentOrchestrator(ISynchronizationPipelineFactory factory) : IContentOrchestrator { diff --git a/rubberduckvba.Server/Services/rubberduckdb/FeatureServices.cs b/rubberduckvba.Server/Services/rubberduckdb/FeatureServices.cs new file mode 100644 index 0000000..44402a4 --- /dev/null +++ b/rubberduckvba.Server/Services/rubberduckdb/FeatureServices.cs @@ -0,0 +1,64 @@ +using rubberduckvba.Server.Data; +using rubberduckvba.Server.Model; +using rubberduckvba.Server.Model.Entity; + +namespace rubberduckvba.Server.Services.rubberduckdb; + +public class FeatureServices( + IRepository featureRepository, + IRepository inspectionRepository, + IRepository quickfixRepository, + IRepository annotationRepository) +{ + public int GetId(string name) => featureRepository.GetId(name); + public IEnumerable Get(bool topLevelOnly = true) + { + return featureRepository.GetAll() + .Where(e => !topLevelOnly || e.ParentId is null) + .Select(e => new Feature(e)); + } + + public FeatureGraph Get(string name) + { + var id = featureRepository.GetId(name); + var feature = featureRepository.GetById(id); + var children = featureRepository.GetAll(parentId: id).Select(e => new Feature(e)); + + IEnumerable inspections = []; + IEnumerable quickfixes = []; + IEnumerable annotations = []; + + if (string.Equals(name, "inspections", StringComparison.InvariantCultureIgnoreCase)) + { + inspections = inspectionRepository.GetAll().Select(e => new Inspection(e)).ToList(); + } + else if (string.Equals(name, "quickfixes", StringComparison.InvariantCultureIgnoreCase)) + { + quickfixes = quickfixRepository.GetAll().Select(e => new QuickFix(e)).ToList(); + } + else if (string.Equals(name, "annotations", StringComparison.InvariantCultureIgnoreCase)) + { + annotations = annotationRepository.GetAll().Select(e => new Annotation(e)).ToList(); + } + + return new FeatureGraph(feature) + { + Features = children, + Annotations = annotations, + QuickFixes = quickfixes, + Inspections = inspections, + }; + } + + public void Update(IEnumerable features) => featureRepository.Update(features.Select(feature => feature.ToEntity())); + public void Update(FeatureGraph feature) => Update([feature]); + public void Update(IEnumerable inspections) => inspectionRepository.Update(inspections.Select(inspection => inspection.ToEntity())); + public void Update(IEnumerable quickFixes) => quickfixRepository.Update(quickFixes.Select(quickfix => quickfix.ToEntity())); + public void Update(IEnumerable annotations) => annotationRepository.Update(annotations.Select(annotation => annotation.ToEntity())); + + public void Insert(IEnumerable features) => featureRepository.Insert(features.Select(feature => feature.ToEntity())); + public void Insert(FeatureGraph feature) => Insert([feature]); + public void Insert(IEnumerable inspections) => inspectionRepository.Insert(inspections.Select(inspection => inspection.ToEntity())); + public void Insert(IEnumerable quickFixes) => quickfixRepository.Insert(quickFixes.Select(quickfix => quickfix.ToEntity())); + public void Insert(IEnumerable annotations) => annotationRepository.Insert(annotations.Select(annotation => annotation.ToEntity())); +} diff --git a/rubberduckvba.Server/Services/rubberduckdb/TagServices.cs b/rubberduckvba.Server/Services/rubberduckdb/TagServices.cs new file mode 100644 index 0000000..8d8abcf --- /dev/null +++ b/rubberduckvba.Server/Services/rubberduckdb/TagServices.cs @@ -0,0 +1,91 @@ +using rubberduckvba.Server.Data; +using rubberduckvba.Server.Model; +using rubberduckvba.Server.Model.Entity; + +namespace rubberduckvba.Server.Services.rubberduckdb; + +public class TagServices(IRepository tagsRepository, IRepository tagAssetsRepository) +{ + private IEnumerable _allAssets = []; + private IEnumerable _allTags = []; + private IEnumerable _latestTags = []; + private TagGraph? _main; + private TagGraph? _next; + private bool _mustInvalidate = true; + + public IEnumerable GetAllTags() + { + if (_mustInvalidate || !_allTags.Any()) + { + _allTags = tagsRepository.GetAll().ToList(); + _latestTags = _allTags + .GroupBy(tag => tag.IsPreRelease) + .Select(tags => tags.OrderByDescending(tag => tag.DateCreated)) + .SelectMany(tags => tags.Take(1)) + .ToList(); + _mustInvalidate = false; + } + + return _allTags.Select(e => new Tag(e)); + } + + public IEnumerable GetLatestTags() + { + if (_mustInvalidate || !_latestTags.Any()) + { + _ = GetAllTags(); + } + + return _latestTags.Select(e => new Tag(e)); + } + + public TagGraph GetLatestTag(bool isPreRelease) + { + var mustInvalidate = _mustInvalidate; + if (mustInvalidate || !_latestTags.Any()) + { + _ = GetAllTags(); // _mustInvalidate => false + } + + if (!mustInvalidate && !isPreRelease && _main != null) + { + return _main; + } + if (!mustInvalidate && isPreRelease && _next != null) + { + return _next; + } + + var mainTag = _latestTags.First(e => !e.IsPreRelease); + var mainAssets = tagAssetsRepository.GetAll(mainTag.Id); + _main = new TagGraph(mainTag, mainAssets); + + var nextTag = _latestTags.First(e => e.IsPreRelease); + var nextAssets = tagAssetsRepository.GetAll(nextTag.Id); + _next = new TagGraph(nextTag, nextAssets); + + return isPreRelease ? _next : _main; + } + + public void Create(IEnumerable tags) + { + var tagsByName = tags.ToDictionary(tag => tag.Name); + var tagEntities = tagsRepository.Insert(tags.Select(tag => tag.ToEntity())); + + var assets = new List(); + foreach (var tagEntity in tagEntities) + { + var tag = tagsByName[tagEntity.Name]; + assets.AddRange(tag.Assets.Select(asset => (asset with { TagId = tag.Id }).ToEntity())); + } + + _ = tagAssetsRepository.Insert(assets); + _mustInvalidate = true; + } + + public void Update(IEnumerable tags) + { + tagsRepository.Update(tags.Select(tag => tag.ToEntity())); + _mustInvalidate = true; + } +} \ No newline at end of file diff --git a/rubberduckvba.Server/appsettings.json b/rubberduckvba.Server/appsettings.json deleted file mode 100644 index 0a58fec..0000000 --- a/rubberduckvba.Server/appsettings.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "ConnectionStrings": { - "RubberduckDb": "Data Source=(localdb)\\MSSQLLocalDB;Integrated Security=True;Trust Server Certificate=True;", - "HangfireDb": "Data Source=(localdb)\\MSSQLLocalDB;Integrated Security=True;Trust Server Certificate=True;" - }, - "GitHub": { - "CodeInspectionDefaultSettingsUri": "/Rubberduck.CodeAnalysis/Properties/CodeInspectionDefaults.settings", - "CodeInspectionInfoResxUri": "https://github.com/rubberduck-vba/Rubberduck/blob/next/Rubberduck.Resources/Inspections/InspectionInfo.resx" - }, - "Hangfire": { - "QueuePollIntervalSeconds": 30, - "AutoRetryAttempts": 2, - "AutoRetryDelaySeconds": [ 2, 5, 10, 30, 60 ], - "HeartbeatIntervalMinutes": 1, - "CancellationCheckIntervalSeconds": 300, - "ConfigureHangfireServerOnStartup": true, - "CreateUpdateInstallerDownloadsJob": true, - "CreateUpdateXmldocContentJob": true, - "UpdateInstallerDownloadsSchedule": "0 0 * * *", // daily - "UpdateXmldocContentSchedule": "0 0 31 2 *" // never - }, - "AllowedHosts": "*" -} diff --git a/rubberduckvba.client/src/app/app.module.ts b/rubberduckvba.client/src/app/app.module.ts index 8c9cb30..ab024be 100644 --- a/rubberduckvba.client/src/app/app.module.ts +++ b/rubberduckvba.client/src/app/app.module.ts @@ -21,6 +21,7 @@ import { FeatureItemExampleComponent } from './components/quickfix-example.modal import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { BrowserModule } from '@angular/platform-browser'; import { RouterModule } from '@angular/router'; +import { LoadingContentComponent } from './components/loading-content/loading-content.component'; //import { httpInterceptorProviders } from './services/HttpRequestInterceptor'; @@ -38,6 +39,7 @@ import { RouterModule } from '@angular/router'; FeatureItemBoxComponent, ExampleBoxComponent, FeatureItemExampleComponent, + LoadingContentComponent ], bootstrap: [AppComponent], imports: [ diff --git a/rubberduckvba.client/src/app/components/example-box/example-box.component.html b/rubberduckvba.client/src/app/components/example-box/example-box.component.html index 5665c2d..44a5952 100644 --- a/rubberduckvba.client/src/app/components/example-box/example-box.component.html +++ b/rubberduckvba.client/src/app/components/example-box/example-box.component.html @@ -1,9 +1,6 @@
-
-

Loading...

-