Skip to content

Commit c99debf

Browse files
authored
Merge pull request #1887 from exceptionless/bugfix/api-improvements
Fixed some repository caching bugs and improved code gen
2 parents f1cb8cd + 1e63d40 commit c99debf

File tree

5 files changed

+56
-24
lines changed

5 files changed

+56
-24
lines changed

src/Exceptionless.Core/Repositories/ProjectRepository.cs

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using FluentValidation;
66
using Foundatio.Repositories;
77
using Foundatio.Repositories.Models;
8+
using Foundatio.Repositories.Options;
89
using Nest;
910

1011
namespace Exceptionless.Core.Repositories;
@@ -36,13 +37,13 @@ private void OnDocumentsChanging(object sender, DocumentsChangeEventArgs<Project
3637
if (configCacheValue.HasValue)
3738
return configCacheValue.Value;
3839

39-
var project = await FindOneAsync(q => q.Id(projectId).Include(p => p.Configuration, p => p.OrganizationId));
40-
if (project?.Document is null)
40+
var project = await GetByIdAsync(projectId, o => o.ReadCache().Include(p => p.Configuration, p => p.OrganizationId));
41+
if (project is null)
4142
return null;
4243

43-
await Cache.AddAsync(cacheKey, project.Document);
44-
45-
return project.Document;
44+
// NOTE: We might read from cache, but we want to save a limited subset of data.
45+
await Cache.AddAsync(cacheKey, ToCachedProjectConfig(project));
46+
return project;
4647
}
4748

4849
public Task<CountResult> GetCountByOrganizationIdAsync(string organizationId)
@@ -88,17 +89,42 @@ public async Task IncrementNextSummaryEndOfDayTicksAsync(IReadOnlyCollection<Pro
8889
await InvalidateCacheAsync(projects);
8990
}
9091

92+
protected override async Task AddDocumentsToCacheAsync(ICollection<FindHit<Project>> findHits, ICommandOptions options, bool isDirtyRead)
93+
{
94+
await base.AddDocumentsToCacheAsync(findHits, options, isDirtyRead);
95+
96+
var cacheEntries = new Dictionary<string, Project>();
97+
foreach (var project in findHits.Select(hit => hit.Document).Where(d => !String.IsNullOrEmpty(d?.Id)))
98+
cacheEntries.Add(ConfigCacheKey(project.Id), ToCachedProjectConfig(project));
99+
100+
// NOTE: We call SetAllAsync instead of AddDocumentsToCacheWithKeyAsync due to our repo method gets the value directly from cache.
101+
if (cacheEntries.Count > 0)
102+
await Cache.SetAllAsync(cacheEntries, options.GetExpiresIn());
103+
}
104+
91105
protected override async Task InvalidateCacheAsync(IReadOnlyCollection<ModifiedDocument<Project>> documents, ChangeType? changeType = null)
92106
{
93-
var organizations = documents.Select(d => d.Value.OrganizationId).Distinct().Where(id => !String.IsNullOrEmpty(id));
94-
await Cache.RemoveAllAsync(organizations.Select(id => $"count:{OrganizationCacheKey(id)}"));
107+
var originalAndModifiedDocuments = documents.UnionOriginalAndModified();
108+
109+
// Invalidate GetCountByOrganizationIdAsync
110+
var organizationIds = originalAndModifiedDocuments.Select(d => d.OrganizationId).Distinct().Where(id => !String.IsNullOrEmpty(id));
111+
var countByOrganizationKeysToRemove = organizationIds.Select(id => $"count:{OrganizationCacheKey(id)}");
112+
await Cache.RemoveAllAsync(countByOrganizationKeysToRemove);
95113

96-
var configCacheKeys = documents.Select(d => ConfigCacheKey(d.Value.Id));
97-
await Cache.RemoveAllAsync(configCacheKeys);
114+
var configKeysToRemove = originalAndModifiedDocuments.Select(d => ConfigCacheKey(d.Id)).Distinct();
115+
await Cache.RemoveAllAsync(configKeysToRemove);
98116

99117
await base.InvalidateCacheAsync(documents, changeType);
100118
}
101119

120+
private static Project ToCachedProjectConfig(Project project)
121+
{
122+
return new Project
123+
{
124+
Id = project.Id, OrganizationId = project.OrganizationId, Configuration = project.Configuration
125+
};
126+
}
127+
102128
private static string ConfigCacheKey(string projectId) => String.Concat("config:", projectId);
103129
private static string OrganizationCacheKey(string organizationId) => String.Concat("Organization:", organizationId);
104130
}

src/Exceptionless.Core/Repositories/StackRepository.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,8 @@ protected override async Task AddDocumentsToCacheAsync(ICollection<FindHit<Stack
142142

143143
protected override async Task InvalidateCacheAsync(IReadOnlyCollection<ModifiedDocument<Stack>> documents, ChangeType? changeType = null)
144144
{
145-
var keys = documents.UnionOriginalAndModified().Select(GetStackSignatureCacheKey).Distinct();
146-
await Cache.RemoveAllAsync(keys);
145+
var keysToRemove = documents.UnionOriginalAndModified().Select(GetStackSignatureCacheKey).Distinct();
146+
await Cache.RemoveAllAsync(keysToRemove);
147147
await base.InvalidateCacheAsync(documents, changeType);
148148
}
149149

src/Exceptionless.Core/Repositories/WebHookRepository.cs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Exceptionless.Core.Models;
1+
using Exceptionless.Core.Extensions;
2+
using Exceptionless.Core.Models;
23
using Exceptionless.Core.Repositories.Configuration;
34
using FluentValidation;
45
using Foundatio.Repositories;
@@ -9,8 +10,11 @@ namespace Exceptionless.Core.Repositories;
910

1011
public sealed class WebHookRepository : RepositoryOwnedByOrganizationAndProject<WebHook>, IWebHookRepository
1112
{
12-
public WebHookRepository(ExceptionlessElasticConfiguration configuration, IValidator<WebHook> validator, AppOptions options)
13-
: base(configuration.WebHooks, validator, options) { }
13+
public WebHookRepository(ExceptionlessElasticConfiguration configuration, IValidator<WebHook> validator,
14+
AppOptions options)
15+
: base(configuration.WebHooks, validator, options)
16+
{
17+
}
1418

1519
public Task<FindResults<WebHook>> GetByUrlAsync(string targetUrl)
1620
{
@@ -19,9 +23,10 @@ public Task<FindResults<WebHook>> GetByUrlAsync(string targetUrl)
1923

2024
public Task<FindResults<WebHook>> GetByOrganizationIdOrProjectIdAsync(string organizationId, string projectId)
2125
{
22-
var filter = (Query<WebHook>.Term(e => e.OrganizationId, organizationId) && !Query<WebHook>.Exists(e => e.Field(f => f.ProjectId))) || Query<WebHook>.Term(e => e.ProjectId, projectId);
26+
ArgumentException.ThrowIfNullOrEmpty(organizationId);
27+
ArgumentException.ThrowIfNullOrEmpty(projectId);
2328

24-
// TODO: This cache key may not always be cleared out if the web hook doesn't have both a org and project id.
29+
var filter = (Query<WebHook>.Term(e => e.OrganizationId, organizationId) && !Query<WebHook>.Exists(e => e.Field(f => f.ProjectId))) || Query<WebHook>.Term(e => e.ProjectId, projectId);
2530
return FindAsync(q => q.ElasticFilter(filter).Sort(f => f.CreatedUtc), o => o.Cache(PagedCacheKey(organizationId, projectId)));
2631
}
2732

@@ -42,19 +47,19 @@ public async Task MarkDisabledAsync(string id)
4247
await SaveAsync(webHook, o => o.Cache());
4348
}
4449

45-
4650
protected override async Task InvalidateCacheAsync(IReadOnlyCollection<ModifiedDocument<WebHook>> documents, ChangeType? changeType = null)
4751
{
48-
var keysToRemove = documents.Select(d => d.Value).Select(CacheKey).Distinct();
52+
var originalAndModifiedDocuments = documents.UnionOriginalAndModified();
53+
var keysToRemove = originalAndModifiedDocuments.Select(CacheKey).Distinct();
4954
await Cache.RemoveAllAsync(keysToRemove);
5055

51-
var pagedKeysToRemove = documents.Select(d => PagedCacheKey(d.Value.OrganizationId, d.Value.ProjectId)).Distinct();
56+
var pagedKeysToRemove = originalAndModifiedDocuments.Select(d => PagedCacheKey(d.OrganizationId, d.ProjectId)).Distinct();
5257
foreach (string key in pagedKeysToRemove)
5358
await Cache.RemoveByPrefixAsync(key);
5459

5560
await base.InvalidateCacheAsync(documents, changeType);
5661
}
5762

58-
private string CacheKey(WebHook webHook) => String.Concat("Organization:", webHook.OrganizationId, ":Project:", webHook.ProjectId);
63+
private static string CacheKey(WebHook webHook) => String.Concat("Organization:", webHook.OrganizationId, ":Project:", webHook.ProjectId);
5964
private static string PagedCacheKey(string organizationId, string projectId) => String.Concat("paged:Organization:", organizationId, ":Project:", projectId);
6065
}

src/Exceptionless.Web/Controllers/ProjectController.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ public async Task<ActionResult<ClientConfiguration>> GetConfigAsync(string? id =
247247
if (project is null)
248248
return NotFound();
249249

250-
if (_isOwnedByOrganization && !CanAccessOrganization(project.OrganizationId))
250+
if (!CanAccessOrganization(project.OrganizationId))
251251
return NotFound();
252252

253253
if (v.HasValue && v == project.Configuration.Version)
@@ -314,6 +314,7 @@ public async Task<IActionResult> DeleteConfigAsync(string id, string key)
314314
/// Reset project data
315315
/// </summary>
316316
/// <param name="id">The identifier of the project.</param>
317+
/// <response code="202">Accepted</response>
317318
/// <response code="404">The project could not be found.</response>
318319
[HttpGet("{id:objectid}/reset-data")]
319320
[Authorize(Policy = AuthorizationRoles.UserPolicy)]
@@ -428,7 +429,7 @@ public async Task<IActionResult> SetNotificationSettingsAsync(string id, string
428429
[HttpPost("{id:objectid}/{integration:minlength(1)}/notifications")]
429430
[Consumes("application/json")]
430431
[Authorize(Policy = AuthorizationRoles.UserPolicy)]
431-
public async Task<IActionResult> SetIntegrationNotificationSettingsAsync(string id, string integration, NotificationSettings settings)
432+
public async Task<IActionResult> SetIntegrationNotificationSettingsAsync(string id, string integration, NotificationSettings? settings)
432433
{
433434
if (!String.Equals(Project.NotificationIntegrations.Slack, integration))
434435
return NotFound();
@@ -442,7 +443,7 @@ public async Task<IActionResult> SetIntegrationNotificationSettingsAsync(string
442443
return NotFound();
443444

444445
if (!organization.HasPremiumFeatures)
445-
return PlanLimitReached($"Please upgrade your plan to enable {integration.TrimStart('@')} integration.");
446+
return PlanLimitReached($"Please upgrade your plan to enable {integration} integration.");
446447

447448
if (settings is null)
448449
project.NotificationSettings.Remove(integration);

src/Exceptionless.Web/Controllers/WebHookController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public async Task<ActionResult<IReadOnlyCollection<WebHook>>> GetByProjectAsync(
4949
page = GetPage(page);
5050
limit = GetLimit(limit);
5151
var results = await _repository.GetByProjectIdAsync(projectId, o => o.PageNumber(page).PageLimit(limit));
52-
return OkWithResourceLinks(results.Documents.ToArray(), results.HasMore && !NextPageExceedsSkipLimit(page, limit), page);
52+
return OkWithResourceLinks(results.Documents.ToArray(), results.HasMore && !NextPageExceedsSkipLimit(page, limit), page, results.Total);
5353
}
5454

5555
/// <summary>

0 commit comments

Comments
 (0)