Skip to content

Commit

Permalink
Improve ServerSide PErformance (#7807)
Browse files Browse the repository at this point in the history
  • Loading branch information
chidozieononiwu authored Mar 4, 2024
1 parent 0489973 commit caa77fb
Show file tree
Hide file tree
Showing 11 changed files with 287 additions and 33 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using System;
using APIViewWeb;
using Microsoft.Azure.Cosmos;
using Microsoft.Extensions.Configuration;
using Xunit;
using APIViewWeb.LeanModels;
using APIViewWeb.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;

namespace APIViewIntegrationTests.RepositoryTests
{
public class CosmosPullRequestRepositoryTestsBaseFixture : IDisposable
{
private IConfigurationRoot _config;
private readonly CosmosClient _cosmosClient;
private readonly string _cosmosDBname;
public CosmosPullRequestsRepository PullRequestRepositopry { get; private set; }
public CosmosReviewRepository ReviewRepository { get; private set; }

public CosmosPullRequestRepositoryTestsBaseFixture()
{
var _config = new ConfigurationBuilder()
.AddEnvironmentVariables(prefix: "APIVIEW_")
.AddUserSecrets(typeof(TestsBaseFixture).Assembly)
.Build();

_cosmosDBname = "CosmosPullRequestRepositoryTestsDB";
_config["CosmosDBName"] = _cosmosDBname;

_cosmosClient = new CosmosClient(_config["Cosmos:ConnectionString"]);
var dataBaseResponse = _cosmosClient.CreateDatabaseIfNotExistsAsync(_config["CosmosDBName"]).Result;
dataBaseResponse.Database.CreateContainerIfNotExistsAsync("Reviews", "/id").Wait();
dataBaseResponse.Database.CreateContainerIfNotExistsAsync("PullRequests", "/ReviewId").Wait();

ReviewRepository = new CosmosReviewRepository(_config, _cosmosClient);
PullRequestRepositopry = new CosmosPullRequestsRepository(_config, ReviewRepository, _cosmosClient);
PopulateDBWithDummyPullRequestData().Wait();
PopulateDBWithDummyReviewData().Wait();
}

private async Task PopulateDBWithDummyPullRequestData()
{
List<PullRequestModel> testPullRequests = new List<PullRequestModel>
{
new PullRequestModel { Id = "1", ReviewId = "1", IsDeleted = false},
new PullRequestModel { Id = "2", ReviewId = "1", IsDeleted = false},
new PullRequestModel { Id = "3", ReviewId = "2", IsDeleted = false},
new PullRequestModel { Id = "4", ReviewId = "3", IsDeleted = true},
};
foreach (var pr in testPullRequests)
{
await PullRequestRepositopry.UpsertPullRequestAsync(pr);
}
}

private async Task PopulateDBWithDummyReviewData()
{
List<ReviewListItemModel> testReviews = new List<ReviewListItemModel>
{
new ReviewListItemModel { Id = "1", IsClosed = false},
new ReviewListItemModel { Id = "2", IsClosed = true},
new ReviewListItemModel { Id = "3", IsClosed = false}
};
foreach (var review in testReviews)
{
await ReviewRepository.UpsertReviewAsync(review);
}
}

public void Dispose()
{
_cosmosClient.GetDatabase(_cosmosDBname).DeleteAsync().Wait();
_cosmosClient.Dispose();
}
}

[CollectionDefinition("CosmosPullRequestRepositoryTestsCollection")]
public class CosmosPullRequestRepositoryTestsCollection : ICollectionFixture<CosmosPullRequestRepositoryTestsBaseFixture>
{
// This class has no code, and is never created. Its purpose is simply
// to be the place to apply [CollectionDefinition] and all the
// ICollectionFixture<> interfaces.
}

[Collection("CosmosPullRequestRepositoryTestsCollection")]
public class CosmosPullRequestRepositoryTests
{
private readonly CosmosPullRequestRepositoryTestsBaseFixture _fixture;

public CosmosPullRequestRepositoryTests(CosmosPullRequestRepositoryTestsBaseFixture fixture)
{
_fixture = fixture;
}

[Fact]
public async Task GetPullRequestAsync_By_ReviewId_ReturnsCorrectNumberOfPullRequests()
{
var pullRequests = await _fixture.PullRequestRepositopry.GetPullRequestsAsync(reviewId: "1");
Assert.Equal(2, pullRequests.Count());

pullRequests = await _fixture.PullRequestRepositopry.GetPullRequestsAsync(reviewId: "2");
Assert.False(pullRequests.Any());

pullRequests = await _fixture.PullRequestRepositopry.GetPullRequestsAsync(reviewId: "3");
Assert.False(pullRequests.Any());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using System;
using APIViewWeb;
using Microsoft.Azure.Cosmos;
using Microsoft.Extensions.Configuration;
using Xunit;
using APIViewWeb.LeanModels;
using APIViewWeb.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;

namespace APIViewIntegrationTests.RepositoryTests
{
public class CosmosReviewRepositoryTestsBaseFixture : IDisposable
{
private IConfigurationRoot _config;
private readonly CosmosClient _cosmosClient;
private readonly string _cosmosDBname;
public CosmosReviewRepository ReviewRepository { get; private set; }

public CosmosReviewRepositoryTestsBaseFixture()
{
var _config = new ConfigurationBuilder()
.AddEnvironmentVariables(prefix: "APIVIEW_")
.AddUserSecrets(typeof(TestsBaseFixture).Assembly)
.Build();

_cosmosDBname = "CosmosReviewRepositoryTestsDB";
_config["CosmosDBName"] = _cosmosDBname;

_cosmosClient = new CosmosClient(_config["Cosmos:ConnectionString"]);
var dataBaseResponse = _cosmosClient.CreateDatabaseIfNotExistsAsync(_config["CosmosDBName"]).Result;
dataBaseResponse.Database.CreateContainerIfNotExistsAsync("Reviews", "/id").Wait();

ReviewRepository = new CosmosReviewRepository(_config, _cosmosClient);
PopulateDBWithDummyReviewData().Wait();
}

private async Task PopulateDBWithDummyReviewData()
{
List<ReviewListItemModel> testReviews = new List<ReviewListItemModel>
{
new ReviewListItemModel { Id = "1", IsClosed = false},
new ReviewListItemModel { Id = "2", IsClosed = true},
new ReviewListItemModel { Id = "3", IsClosed = false},
new ReviewListItemModel { Id = "4", IsClosed = false},
new ReviewListItemModel { Id = "5", IsClosed = false},
};
foreach (var review in testReviews)
{
await ReviewRepository.UpsertReviewAsync(review);
}
}

public void Dispose()
{
_cosmosClient.GetDatabase(_cosmosDBname).DeleteAsync().Wait();
_cosmosClient.Dispose();
}
}

[CollectionDefinition("CosmosReviewRepositoryTestsCollection")]
public class CosmosReviewRepositoryTestsCollection : ICollectionFixture<CosmosReviewRepositoryTestsBaseFixture>
{
// This class has no code, and is never created. Its purpose is simply
// to be the place to apply [CollectionDefinition] and all the
// ICollectionFixture<> interfaces.
}

[Collection("CosmosReviewRepositoryTestsCollection")]
public class CosmosReviewRepositoryTests
{
private readonly CosmosReviewRepositoryTestsBaseFixture _fixture;

public CosmosReviewRepositoryTests(CosmosReviewRepositoryTestsBaseFixture fixture)
{
_fixture = fixture;
}

[Fact]
public async Task GetPullRequestAsync_By_ReviewId_ReturnsCorrectNumberOfPullRequests()
{
var reviews = await _fixture.ReviewRepository.GetReviewsAsync(reviewIds: new List<string> { "1", "2", "3", "4" });
Assert.Equal(4, reviews.Count());

reviews = await _fixture.ReviewRepository.GetReviewsAsync(reviewIds: new List<string> { "1", "2", "3", "4", "5" }, isClosed: true);
Assert.Single(reviews);

reviews = await _fixture.ReviewRepository.GetReviewsAsync(reviewIds: new List<string> { "1", "2", "3", "4", "5" }, isClosed: false);
Assert.Equal(4, reviews.Count());
}
}
}
39 changes: 21 additions & 18 deletions src/dotnet/APIView/APIViewIntegrationTests/TestsBaseFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
using APIViewWeb.Managers;
using APIViewWeb.Hubs;
using Microsoft.AspNetCore.SignalR;
using APIViewWeb.Managers.Interfaces;
using Microsoft.CodeAnalysis.Host;
using Microsoft.Extensions.Options;
using Microsoft.ApplicationInsights;

Expand All @@ -30,6 +28,8 @@ public class TestsBaseFixture : IDisposable
private readonly CosmosClient _cosmosClient;
private readonly BlobContainerClient _blobCodeFileContainerClient;
private readonly BlobContainerClient _blobOriginalContainerClient;
private IConfigurationRoot _config;
private readonly string _cosmosDBname = "ManagerTestsDB";

public PackageNameManager PackageNameManager { get; private set; }
public ReviewManager ReviewManager { get; private set; }
Expand All @@ -45,13 +45,15 @@ public class TestsBaseFixture : IDisposable

public TestsBaseFixture()
{
var config = new ConfigurationBuilder()
_config = new ConfigurationBuilder()
.AddEnvironmentVariables(prefix: "APIVIEW_")
.AddUserSecrets(typeof(TestsBaseFixture).Assembly)
.Build();

_config["CosmosDBName"] = _cosmosDBname;

var services = new ServiceCollection();
services.AddSingleton<IConfiguration>(config);
services.AddSingleton<IConfiguration>(_config);
services.AddMemoryCache();
services.AddSingleton<PackageNameManager>();
services.AddSingleton<LanguageService, JsonLanguageService>();
Expand All @@ -72,32 +74,33 @@ public TestsBaseFixture()
PackageNameManager = serviceProvider.GetService<PackageNameManager>();
User = TestUser.GetTestuser();

_cosmosClient = new CosmosClient(config["Cosmos:ConnectionString"]);
var dataBaseResponse = _cosmosClient.CreateDatabaseIfNotExistsAsync("APIViewV2").Result;
_cosmosClient = new CosmosClient(_config["Cosmos:ConnectionString"]);
var dataBaseResponse = _cosmosClient.CreateDatabaseIfNotExistsAsync(_config["CosmosDBName"]).Result;
dataBaseResponse.Database.CreateContainerIfNotExistsAsync("Reviews", "/id").Wait();
dataBaseResponse.Database.CreateContainerIfNotExistsAsync("APIRevisions", "/ReviewId").Wait();
dataBaseResponse.Database.CreateContainerIfNotExistsAsync("Comments", "/ReviewId").Wait();
dataBaseResponse.Database.CreateContainerIfNotExistsAsync("Profiles", "/id").Wait();
ReviewRepository = new CosmosReviewRepository(config, _cosmosClient);
APIRevisionRepository = new CosmosAPIRevisionsRepository(config, _cosmosClient);
CommentRepository = new CosmosCommentsRepository(config, _cosmosClient);
var cosmosUserProfileRepository = new CosmosUserProfileRepository(config, _cosmosClient);

_blobCodeFileContainerClient = new BlobContainerClient(config["Blob:ConnectionString"], "codefiles");
_blobOriginalContainerClient = new BlobContainerClient(config["Blob:ConnectionString"], "originals");
dataBaseResponse.Database.CreateContainerIfNotExistsAsync("PullRe", "/id").Wait();
ReviewRepository = new CosmosReviewRepository(_config, _cosmosClient);
APIRevisionRepository = new CosmosAPIRevisionsRepository(_config, _cosmosClient);
CommentRepository = new CosmosCommentsRepository(_config, _cosmosClient);
var cosmosUserProfileRepository = new CosmosUserProfileRepository(_config, _cosmosClient);

_blobCodeFileContainerClient = new BlobContainerClient(_config["Blob:ConnectionString"], "codefiles");
_blobOriginalContainerClient = new BlobContainerClient(_config["Blob:ConnectionString"], "originals");
_ = _blobCodeFileContainerClient.CreateIfNotExistsAsync(PublicAccessType.BlobContainer);
_ = _blobOriginalContainerClient.CreateIfNotExistsAsync(PublicAccessType.BlobContainer);

BlobCodeFileRepository = new BlobCodeFileRepository(config, memoryCache);
var blobOriginalsRepository = new BlobOriginalsRepository(config);
BlobCodeFileRepository = new BlobCodeFileRepository(_config, memoryCache);
var blobOriginalsRepository = new BlobOriginalsRepository(_config);

var authorizationServiceMoq = new Mock<IAuthorizationService>();
authorizationServiceMoq.Setup(_ => _.AuthorizeAsync(It.IsAny<ClaimsPrincipal>(), It.IsAny<Object>(), It.IsAny<IEnumerable<IAuthorizationRequirement>>()))
.ReturnsAsync(AuthorizationResult.Success);

var telemetryClient = new Mock<TelemetryClient>();

var notificationManager = new NotificationManager(config, ReviewRepository, APIRevisionRepository, cosmosUserProfileRepository, telemetryClient.Object);
var notificationManager = new NotificationManager(_config, ReviewRepository, APIRevisionRepository, cosmosUserProfileRepository, telemetryClient.Object);

var devopsArtifactRepositoryMoq = new Mock<IDevopsArtifactRepository>();
devopsArtifactRepositoryMoq.Setup(_ => _.DownloadPackageArtifact(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
Expand Down Expand Up @@ -131,12 +134,12 @@ public TestsBaseFixture()
commentsRepository: CommentRepository, languageServices: languageService, signalRHubContext: signalRHubContextMoq.Object,
telemetryClient: telemetryClient.Object);

TestDataPath = config["TestPkgPath"];
TestDataPath = _config["TestPkgPath"];
}

public void Dispose()
{
_cosmosClient.GetDatabase("APIView").DeleteAsync().Wait();
_cosmosClient.GetDatabase(_cosmosDBname).DeleteAsync().Wait();
_cosmosClient.Dispose();

_blobCodeFileContainerClient.DeleteIfExists();
Expand Down
1 change: 1 addition & 0 deletions src/dotnet/APIView/APIViewWeb/APIViewWeb.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Octokit" Version="9.1.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="WebMarkupMin.AspNetCore7" Version="2.16.0" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class CosmosAPIRevisionsRepository : ICosmosAPIRevisionsRepository

public CosmosAPIRevisionsRepository(IConfiguration configuration, CosmosClient cosmosClient)
{
_apiRevisionContainer = cosmosClient.GetContainer("APIViewV2", "APIRevisions");
_apiRevisionContainer = cosmosClient.GetContainer(configuration["CosmosDBName"], "APIRevisions");
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class CosmosCommentsRepository : ICosmosCommentsRepository

public CosmosCommentsRepository(IConfiguration configuration, CosmosClient cosmosClient)
{
_commentsContainer = cosmosClient.GetContainer("APIViewV2", "Comments");
_commentsContainer = cosmosClient.GetContainer(configuration["CosmosDBName"], "Comments");
}

public async Task<IEnumerable<CommentItemModel>> GetCommentsAsync(string reviewId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using APIViewWeb.Models;
Expand All @@ -18,7 +19,7 @@ public class CosmosPullRequestsRepository : ICosmosPullRequestsRepository

public CosmosPullRequestsRepository(IConfiguration configuration, ICosmosReviewRepository reviewsRepository, CosmosClient cosmosClient)
{
_pullRequestsContainer = cosmosClient.GetContainer("APIViewV2", "PullRequests");
_pullRequestsContainer = cosmosClient.GetContainer(configuration["CosmosDBName"], "PullRequests");
_reviewsRepository = reviewsRepository;
}

Expand Down Expand Up @@ -73,19 +74,36 @@ private async Task<List<PullRequestModel>> GetPullRequestFromQueryAsync(string q

// Cosmos doesn't allow cross join of two containers so we need to filter closed API reviews
var filtered = new List<PullRequestModel>();
foreach(var pr in allRequests)
Dictionary<string, List<PullRequestModel>> kvp = new Dictionary<string, List<PullRequestModel>>();
foreach (var pr in allRequests)
{
if(!string.IsNullOrEmpty(pr.ReviewId) && !await IsApiReviewClosed(pr.ReviewId))
filtered.Add(pr);
if (!string.IsNullOrEmpty(pr.ReviewId))
{
if (kvp.ContainsKey(pr.ReviewId))
{
kvp[pr.ReviewId].Add(pr);
}
else
{
kvp.Add(pr.ReviewId, new List<PullRequestModel> { pr });
}
}
}


if (kvp.Any())
{
var reviews = await _reviewsRepository.GetReviewsAsync(reviewIds: new List<string>(kvp.Keys), isClosed: false);
var reviewIds = reviews.Select(r => r.Id).ToList();

foreach (var kv in kvp)
{
if (reviewIds.Contains(kv.Key))
{
filtered.AddRange(kv.Value);
}
}
}
return filtered;
}

private async Task<bool> IsApiReviewClosed(string reviewId)
{
var review = await _reviewsRepository.GetReviewAsync(reviewId);
return review?.IsClosed ?? true;
}
}
}
Loading

0 comments on commit caa77fb

Please sign in to comment.