Skip to content
1 change: 1 addition & 0 deletions samples/Misc/RemoteAppendExample.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public Task<IEnumerable<Guid>> ListAsync(int maxResults, DateTime? start = null,
public Task SaveAsync(MiniProfiler profiler) => Wrapped.SaveAsync(profiler);
public Task SetUnviewedAsync(string user, Guid id) => Wrapped.SetUnviewedAsync(user, id);
public Task SetViewedAsync(string user, Guid id) => Wrapped.SetViewedAsync(user, id);
public Task SetViewedAsync(string user, IEnumerable<Guid> ids) => Wrapped.SetViewedAsync(user, ids);
public Task<List<Guid>> GetUnviewedIdsAsync(string user) => Wrapped.GetUnviewedIdsAsync(user);

/// <summary>
Expand Down
13 changes: 13 additions & 0 deletions src/MiniProfiler.AspNetCore/Storage/MemoryCacheStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -249,5 +249,18 @@ public Task SetViewedAsync(string user, Guid id)
SetViewed(user, id);
return Task.CompletedTask;
}

/// <summary>
/// Asynchronously sets the provided profiler sessions to "viewed"
/// </summary>
/// <param name="user">The user to set this profiler ID as viewed for.</param>
/// <param name="ids">The profiler IDs to set viewed.</param>
public async Task SetViewedAsync(string user, IEnumerable<Guid> ids)
{
foreach (var id in ids)
{
await this.SetViewedAsync(user, id).ConfigureAwait(false);
}
}
}
}
13 changes: 13 additions & 0 deletions src/MiniProfiler.Providers.MongoDB/MongoDbStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,19 @@ public async Task SetViewedAsync(string user, Guid id)
var set = Builders<MiniProfiler>.Update.Set(profiler => profiler.HasUserViewed, true);
await _collection.UpdateOneAsync(p => p.Id == id, set).ConfigureAwait(false);
}

/// <summary>
/// Asynchronously sets the provided profiler sessions to "viewed"
/// </summary>
/// <param name="user">The user to set this profiler ID as viewed for.</param>
/// <param name="ids">The profiler IDs to set viewed.</param>
public async Task SetViewedAsync(string user, IEnumerable<Guid> ids)
{
foreach (var id in ids)
{
await this.SetViewedAsync(user, id).ConfigureAwait(false);
}
}

/// <summary>
/// Returns the underlying client.
Expand Down
24 changes: 18 additions & 6 deletions src/MiniProfiler.Providers.PostgreSql/PostgreSqlStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -268,28 +268,40 @@ public override async Task<MiniProfiler> LoadAsync(Guid id)
/// <param name="user">The user to set this profiler ID as viewed for.</param>
/// <param name="id">The profiler ID to set viewed.</param>
public override Task SetViewedAsync(string user, Guid id) => ToggleViewedAsync(user, id, true);

/// <summary>
/// Asynchronously sets the provided profiler sessions to "viewed"
/// </summary>
/// <param name="user">The user to set this profiler ID as viewed for.</param>
/// <param name="ids">The profiler IDs to set viewed.</param>
public override Task SetViewedAsync(string user, IEnumerable<Guid> ids) => ToggleViewedAsync(user, ids, true);

private string _toggleViewedSql;

private string ToggleViewedSql => _toggleViewedSql ??= $@"
Update {MiniProfilersTable}
Set HasUserViewed = @hasUserVeiwed
Where Id = @id
Set HasUserViewed = @hasUserViewed
Where Id = ANY(@ids)
And ""User"" = @user";

private void ToggleViewed(string user, Guid id, bool hasUserVeiwed)
private void ToggleViewed(string user, Guid id, bool hasUserViewed)
{
using (var conn = GetConnection())
{
conn.Execute(ToggleViewedSql, new { id, user, hasUserVeiwed });
conn.Execute(ToggleViewedSql, new { ids = new [] { id }, user, hasUserViewed });
}
}

private async Task ToggleViewedAsync(string user, Guid id, bool hasUserVeiwed)
private Task ToggleViewedAsync(string user, Guid id, bool hasUserViewed)
{
return ToggleViewedAsync(user, new [] { id }, hasUserViewed);
}

private async Task ToggleViewedAsync(string user, IEnumerable<Guid> ids, bool hasUserViewed)
{
using (var conn = GetConnection())
{
await conn.ExecuteAsync(ToggleViewedSql, new { id, user, hasUserVeiwed }).ConfigureAwait(false);
await conn.ExecuteAsync(ToggleViewedSql, new { ids = ids.ToArray(), user, hasUserViewed }).ConfigureAwait(false);
}
}

Expand Down
13 changes: 13 additions & 0 deletions src/MiniProfiler.Providers.RavenDB/RavenDbStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,19 @@ public async Task SetViewedAsync(string user, Guid id)
profile.HasUserViewed = true;
await session.SaveChangesAsync().ConfigureAwait(false);
}

/// <summary>
/// Asynchronously sets the provided profiler sessions to "viewed"
/// </summary>
/// <param name="user">The user to set this profiler ID as viewed for.</param>
/// <param name="ids">The profiler IDs to set viewed.</param>
public async Task SetViewedAsync(string user, IEnumerable<Guid> ids)
{
foreach (var id in ids)
{
await this.SetViewedAsync(user, id).ConfigureAwait(false);
}
}

/// <summary>
/// Asynchronously returns a list of <see cref="MiniProfiler.Id"/>s that haven't been seen by <paramref name="user"/>.
Expand Down
13 changes: 13 additions & 0 deletions src/MiniProfiler.Providers.Redis/RedisStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,19 @@ public Task SetViewedAsync(string user, Guid id)
RedisValue value = id.ToString();
return _database.SetRemoveAsync(key, value);
}

/// <summary>
/// Asynchronously sets the provided profiler sessions to "viewed"
/// </summary>
/// <param name="user">The user to set this profiler ID as viewed for.</param>
/// <param name="ids">The profiler IDs to set viewed.</param>
public async Task SetViewedAsync(string user, IEnumerable<Guid> ids)
{
foreach (var id in ids)
{
await this.SetViewedAsync(user, id).ConfigureAwait(false);
}
}

/// <summary>
/// Asynchronously returns a list of <see cref="MiniProfiler.Id"/>s that haven't been seen by <paramref name="user"/>.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace StackExchange.Profiling.Internal
Expand All @@ -23,7 +24,7 @@ public static List<Guid> ExpireAndGetUnviewed(this MiniProfilerBaseOptions optio
{
for (var i = 0; i < ids.Count - options.MaxUnviewedProfiles; i++)
{
options.Storage.SetViewedAsync(user, ids[i]);
options.Storage.SetViewed(user, ids[i]);
}
}
return ids;
Expand All @@ -45,10 +46,8 @@ public static async Task<List<Guid>> ExpireAndGetUnviewedAsync(this MiniProfiler
var ids = await options.Storage.GetUnviewedIdsAsync(user).ConfigureAwait(false);
if (ids?.Count > options.MaxUnviewedProfiles)
{
for (var i = 0; i < ids.Count - options.MaxUnviewedProfiles; i++)
{
await options.Storage.SetViewedAsync(user, ids[i]).ConfigureAwait(false);
}
var idsToSetViewed = ids.Take(ids.Count - options.MaxUnviewedProfiles);
await options.Storage.SetViewedAsync(user, idsToSetViewed).ConfigureAwait(false);
}
return ids;
}
Expand Down
13 changes: 13 additions & 0 deletions src/MiniProfiler.Shared/Storage/DatabaseStorageBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,19 @@ protected DatabaseStorageBase(string connectionString, string profilersTable, st
/// <param name="id">The profiler ID to set viewed.</param>
public abstract Task SetViewedAsync(string user, Guid id);

/// <summary>
/// Asynchronously sets the provided profiler sessions to "viewed"
/// </summary>
/// <param name="user">The user to set this profiler ID as viewed for.</param>
/// <param name="ids">The profiler IDs to set viewed.</param>
public virtual async Task SetViewedAsync(string user, IEnumerable<Guid> ids)
{
foreach (var id in ids)
{
await this.SetViewedAsync(user, id).ConfigureAwait(false);
}
}

/// <summary>
/// Returns a list of <see cref="MiniProfiler.Id"/>s that haven't been seen by <paramref name="user"/>.
/// </summary>
Expand Down
7 changes: 7 additions & 0 deletions src/MiniProfiler.Shared/Storage/IAsyncStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,13 @@ Task<IEnumerable<Guid>> ListAsync(
/// <param name="user">The user to set this profiler ID as viewed for.</param>
/// <param name="id">The profiler ID to set viewed.</param>
Task SetViewedAsync(string user, Guid id);

/// <summary>
/// Asynchronously sets the provided profiler sessions to "viewed"
/// </summary>
/// <param name="user">The user to set this profiler ID as viewed for.</param>
/// <param name="ids">The profiler IDs to set viewed.</param>
Task SetViewedAsync(string user, IEnumerable<Guid> ids);

/// <summary>
/// Asynchronously returns a list of <see cref="MiniProfiler.Id"/>s that haven't been seen by <paramref name="user"/>.
Expand Down
14 changes: 14 additions & 0 deletions src/MiniProfiler.Shared/Storage/MultiStorageProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,20 @@ public Task SetViewedAsync(string user, Guid id)

return Task.WhenAll(Stores.Select(s => s.SetViewedAsync(user, id)));
}

/// <summary>
/// Asynchronously sets the provided profiler sessions to "viewed"
/// </summary>
/// <param name="user">The user to set this profiler ID as viewed for.</param>
/// <param name="ids">The profiler IDs to set viewed.</param>
public Task SetViewedAsync(string user, IEnumerable<Guid> ids)
{
if (Stores == null) return Task.CompletedTask;

var idsArray = ids.ToArray(); // Prevent multiple enumerations of ids.

return Task.WhenAll(Stores.Select(s => s.SetViewedAsync(user, idsArray)));
}

/// <summary>
/// Runs <see cref="IAsyncStorage.GetUnviewedIds"/> on each <see cref="IAsyncStorage"/> object in <see cref="Stores"/> and returns the Union of results.
Expand Down
7 changes: 7 additions & 0 deletions src/MiniProfiler.Shared/Storage/NullStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,13 @@ public void SetViewed(string user, Guid id) { /* no-op */ }
/// <param name="id">No one cares.</param>
public Task SetViewedAsync(string user, Guid id) => Task.CompletedTask;

/// <summary>
/// Sets nothing.
/// </summary>
/// <param name="user">No one cares.</param>
/// <param name="ids">No one cares.</param>
public Task SetViewedAsync(string user, IEnumerable<Guid> ids) => Task.CompletedTask;

/// <summary>
/// Gets nothing.
/// </summary>
Expand Down
13 changes: 13 additions & 0 deletions src/MiniProfiler/Storage/MemoryCacheStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -243,5 +243,18 @@ public Task SetViewedAsync(string user, Guid id)
SetViewed(user, id);
return Task.CompletedTask;
}

/// <summary>
/// Asynchronously sets the provided profiler sessions to "viewed"
/// </summary>
/// <param name="user">The user to set this profiler ID as viewed for.</param>
/// <param name="ids">The profiler IDs to set viewed.</param>
public async Task SetViewedAsync(string user, IEnumerable<Guid> ids)
{
foreach (var id in ids)
{
await this.SetViewedAsync(user, id).ConfigureAwait(false);
}
}
}
}
1 change: 1 addition & 0 deletions tests/MiniProfiler.Tests/InternalErrorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public class KaboomStorage : IAsyncStorage
public Task SetUnviewedAsync(string user, Guid id) => throw new BoomBoom();
public void SetViewed(string user, Guid id) => throw new BoomBoom();
public Task SetViewedAsync(string user, Guid id) => throw new BoomBoom();
public Task SetViewedAsync(string user, IEnumerable<Guid> ids) => throw new BoomBoom();

public class BoomBoom : Exception { }
}
Expand Down
32 changes: 32 additions & 0 deletions tests/MiniProfiler.Tests/Storage/StorageBaseTest.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Dapper;
using StackExchange.Profiling.Internal;
using StackExchange.Profiling.Storage;
using Xunit;
using Xunit.Abstractions;
Expand Down Expand Up @@ -146,6 +149,35 @@ public async Task SetViewedAsync()
var unviewedIds2 = await Storage.GetUnviewedIdsAsync(mp.User).ConfigureAwait(false);
Assert.DoesNotContain(mp.Id, unviewedIds2);
}

[Fact]
public async Task ExpireAndGetUnviewedAsync()
{
Options.Storage = Storage;
var user = "TestUser";
var mps = Enumerable.Range(0, 500)
.Select(i => GetMiniProfiler(user: user))
.ToList();

foreach (var mp in mps)
{
Assert.False(mp.HasUserViewed);
await Storage.SaveAsync(mp).ConfigureAwait(false);
Assert.False(mp.HasUserViewed);
}

var unviewedIds = await Storage.GetUnviewedIdsAsync(user).ConfigureAwait(false);
Assert.All(mps, mp => Assert.Contains(mp.Id, unviewedIds));

var sw = Stopwatch.StartNew();
await Options.ExpireAndGetUnviewedAsync(user);
sw.Stop();
Output.WriteLine($"{nameof(MiniProfilerBaseOptionsExtensions.ExpireAndGetUnviewedAsync)} completed in {sw.ElapsedMilliseconds}ms");

var unviewedIds2 = await Storage.GetUnviewedIdsAsync(user).ConfigureAwait(false);
Assert.InRange(unviewedIds2.Count, 0, Options.MaxUnviewedProfiles);
Assert.Subset(new HashSet<Guid>(unviewedIds), new HashSet<Guid>(unviewedIds2));
}

[Fact]
public void SetUnviewed()
Expand Down