Skip to content

Commit 3dd72f6

Browse files
authored
[PM-22450] Bump Collection.RevisionDate on edits and access changes (#7380)
* Fix UpdateCollectionCommand to set RevisionDate using TimeProvider and update corresponding tests. Adjust tests to verify correct RevisionDate assignment during collection updates. * Enhance BulkAddCollectionAccessCommand to include revision date in access updates. Update ICollectionRepository and its implementations to accept revision date parameter. Modify stored procedure to update collection revision dates accordingly. Add tests to verify correct behavior of access creation and revision date updates. * Update GroupRepository and stored procedures to bump RevisionDate for affected collections during group creation and updates. Enhance integration tests to verify that collection revision dates are correctly updated when groups are created or modified. * Implement revision date updates for affected collections in OrganizationUserRepository and related stored procedures. Add integration tests to ensure revision dates are correctly bumped during organization user creation and updates. * Update database migration script * Update migration script summary * Refactor OrganizationUserReplaceTests to create collection first * Refactor stored procedures to use Common Table Expressions (CTEs) for updating RevisionDate of affected collections. This change improves readability and maintainability by consolidating the logic for identifying affected collections in Group_UpdateWithCollections and OrganizationUser_UpdateWithCollections procedures. * Enhance OrganizationUser_CreateManyWithCollectionsAndGroups stored procedure to accept RevisionDate parameter for updating affected collections. Update OrganizationUserRepository to utilize the provided RevisionDate when available, ensuring accurate revision date management during organization user operations. * Refactor OrganizationUser_CreateManyWithCollectionsGroups and migration script to utilize temporary table for CollectionUser data insertion. This change improves performance and maintains consistency in updating RevisionDate for affected collections. * Refactor OrganizationUserRepository to consistently use RevisionDate from created OrganizationUsers when updating affected collections. This change enhances the accuracy of revision date management across the repository. * Refactor tests to ensure consistent handling of RevisionDate across Group and Collection repositories. Update assertions to compare RevisionDate directly, improving accuracy in revision date management during tests. * Restore BOM in Group_UpdateWithCollections and OrganizationUser_UpdateWithCollections * Refactor GroupRepository and OrganizationUserRepository to improve handling of RevisionDate. Updated collection filtering logic to use HashSet for efficiency and ensured that affected collections are filtered by OrganizationId, enhancing accuracy in revision date management. * Bump migration script date * Remove internal set from RevisionDate on Group and OrganizationUser The Dapper repositories use a System.Text.Json serialize/deserialize round-trip to build *WithCollections objects. System.Text.Json silently skips properties with non-public setters, so RevisionDate was reverting to DateTime.UtcNow instead of preserving the value set in C#. * Refactor OrganizationUser_CreateManyWithCollectionsGroups and migration script to improve the logic for updating RevisionDate. The update now uses INNER JOINs to ensure accurate filtering of collections based on OrganizationId and CollectionUser data, enhancing the precision of revision date management. * Fix sprocs styling * Added early return to OrganizationUserRepository.CreateManyAsync if the supplied parameter is empty
1 parent 5dbfa77 commit 3dd72f6

File tree

24 files changed

+1091
-39
lines changed

24 files changed

+1091
-39
lines changed

src/Core/AdminConsole/Entities/Group.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public class Group : ITableObject<Guid>, IExternal
1616
[MaxLength(300)]
1717
public string? ExternalId { get; set; }
1818
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;
19-
public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow;
19+
public DateTime RevisionDate { get; set; } = DateTime.UtcNow;
2020

2121
public void SetNewId()
2222
{

src/Core/AdminConsole/Entities/OrganizationUser.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public class OrganizationUser : ITableObject<Guid>, IExternal, IOrganizationUser
6565
/// <summary>
6666
/// The last date the OrganizationUser entry was updated.
6767
/// </summary>
68-
public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow;
68+
public DateTime RevisionDate { get; set; } = DateTime.UtcNow;
6969
/// <summary>
7070
/// A json blob representing the <see cref="Bit.Core.Models.Data.Permissions"/> of the OrganizationUser if they
7171
/// are a Custom user role (i.e. the <see cref="OrganizationUserType"/> is Custom). MAY be NULL if they are not

src/Core/OrganizationFeatures/OrganizationCollections/BulkAddCollectionAccessCommand.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,20 @@ public class BulkAddCollectionAccessCommand : IBulkAddCollectionAccessCommand
1515
private readonly IOrganizationUserRepository _organizationUserRepository;
1616
private readonly IGroupRepository _groupRepository;
1717
private readonly IEventService _eventService;
18+
private readonly TimeProvider _timeProvider;
1819

1920
public BulkAddCollectionAccessCommand(
2021
ICollectionRepository collectionRepository,
2122
IOrganizationUserRepository organizationUserRepository,
2223
IGroupRepository groupRepository,
23-
IEventService eventService)
24+
IEventService eventService,
25+
TimeProvider timeProvider)
2426
{
2527
_collectionRepository = collectionRepository;
2628
_organizationUserRepository = organizationUserRepository;
2729
_groupRepository = groupRepository;
2830
_eventService = eventService;
31+
_timeProvider = timeProvider;
2932
}
3033

3134
public async Task AddAccessAsync(ICollection<Collection> collections,
@@ -34,15 +37,18 @@ public async Task AddAccessAsync(ICollection<Collection> collections,
3437
{
3538
await ValidateRequestAsync(collections, users, groups);
3639

40+
var revisionDate = _timeProvider.GetUtcNow().UtcDateTime;
41+
3742
await _collectionRepository.CreateOrUpdateAccessForManyAsync(
3843
collections.First().OrganizationId,
3944
collections.Select(c => c.Id),
4045
users,
41-
groups
46+
groups,
47+
revisionDate
4248
);
4349

4450
await _eventService.LogCollectionEventsAsync(collections.Select(c =>
45-
(c, EventType.Collection_Updated, (DateTime?)DateTime.UtcNow)));
51+
(c, EventType.Collection_Updated, (DateTime?)revisionDate)));
4652
}
4753

4854
private async Task ValidateRequestAsync(ICollection<Collection> collections, ICollection<CollectionAccessSelection> usersAccess, ICollection<CollectionAccessSelection> groupsAccess)

src/Core/OrganizationFeatures/OrganizationCollections/UpdateCollectionCommand.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,18 @@ public class UpdateCollectionCommand : IUpdateCollectionCommand
1616
private readonly IEventService _eventService;
1717
private readonly IOrganizationRepository _organizationRepository;
1818
private readonly ICollectionRepository _collectionRepository;
19+
private readonly TimeProvider _timeProvider;
1920

2021
public UpdateCollectionCommand(
2122
IEventService eventService,
2223
IOrganizationRepository organizationRepository,
23-
ICollectionRepository collectionRepository)
24+
ICollectionRepository collectionRepository,
25+
TimeProvider timeProvider)
2426
{
2527
_eventService = eventService;
2628
_organizationRepository = organizationRepository;
2729
_collectionRepository = collectionRepository;
30+
_timeProvider = timeProvider;
2831
}
2932

3033
public async Task<Collection> UpdateAsync(Collection collection, IEnumerable<CollectionAccessSelection> groups = null,
@@ -75,6 +78,7 @@ public async Task<Collection> UpdateAsync(Collection collection, IEnumerable<Col
7578
}
7679
}
7780

81+
collection.RevisionDate = _timeProvider.GetUtcNow().UtcDateTime;
7882
await _collectionRepository.ReplaceAsync(collection, org.UseGroups ? groupsList : null, usersList);
7983
await _eventService.LogCollectionEventAsync(collection, Enums.EventType.Collection_Updated);
8084

src/Core/Repositories/ICollectionRepository.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,18 @@ public interface ICollectionRepository : IRepository<Collection, Guid>
6060
Task UpdateUsersAsync(Guid id, IEnumerable<CollectionAccessSelection> users);
6161
Task<ICollection<CollectionAccessSelection>> GetManyUsersByIdAsync(Guid id);
6262
Task DeleteManyAsync(IEnumerable<Guid> collectionIds);
63+
64+
/// <summary>
65+
/// Creates or updates the access for many collections.
66+
/// </summary>
67+
/// <param name="organizationId">The Organization ID.</param>
68+
/// <param name="collectionIds">The Collection IDs to create or update access for.</param>
69+
/// <param name="users">The users to grant access to.</param>
70+
/// <param name="groups">The groups to grant access to.</param>
71+
/// <param name="revisionDate">The revision date to use for the collections.</param>
6372
Task CreateOrUpdateAccessForManyAsync(Guid organizationId, IEnumerable<Guid> collectionIds,
64-
IEnumerable<CollectionAccessSelection> users, IEnumerable<CollectionAccessSelection> groups);
73+
IEnumerable<CollectionAccessSelection> users, IEnumerable<CollectionAccessSelection> groups,
74+
DateTime revisionDate);
6575

6676
/// <summary>
6777
/// Creates default user collections for the specified organization users.

src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationUserRepository.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,10 @@ public async Task CreateManyAsync(IEnumerable<CreateOrganizationUser> organizati
652652
await using var connection = new SqlConnection(_marsConnectionString);
653653

654654
var organizationUsersList = organizationUserCollection.ToList();
655+
if (organizationUsersList.Count == 0)
656+
{
657+
return;
658+
}
655659

656660
await connection.ExecuteAsync(
657661
$"[{Schema}].[OrganizationUser_CreateManyWithCollectionsAndGroups]",
@@ -672,7 +676,9 @@ await connection.ExecuteAsync(
672676
{
673677
GroupId = group,
674678
OrganizationUserId = user.OrganizationUser.Id
675-
}))
679+
})),
680+
// Use the same RevisionDate as the created OrganizationUsers
681+
RevisionDate = organizationUsersList.First().OrganizationUser.RevisionDate
676682
},
677683
commandType: CommandType.StoredProcedure);
678684
}

src/Infrastructure.Dapper/Repositories/CollectionRepository.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,8 @@ await connection.ExecuteAsync("[dbo].[Collection_DeleteByIds]",
301301
}
302302

303303
public async Task CreateOrUpdateAccessForManyAsync(Guid organizationId, IEnumerable<Guid> collectionIds,
304-
IEnumerable<CollectionAccessSelection> users, IEnumerable<CollectionAccessSelection> groups)
304+
IEnumerable<CollectionAccessSelection> users, IEnumerable<CollectionAccessSelection> groups,
305+
DateTime revisionDate)
305306
{
306307
using (var connection = new SqlConnection(ConnectionString))
307308
{
@@ -310,7 +311,14 @@ public async Task CreateOrUpdateAccessForManyAsync(Guid organizationId, IEnumera
310311

311312
var results = await connection.ExecuteAsync(
312313
$"[{Schema}].[Collection_CreateOrUpdateAccessForMany]",
313-
new { OrganizationId = organizationId, CollectionIds = collectionIds.ToGuidIdArrayTVP(), Users = usersArray, Groups = groupsArray },
314+
new
315+
{
316+
OrganizationId = organizationId,
317+
CollectionIds = collectionIds.ToGuidIdArrayTVP(),
318+
Users = usersArray,
319+
Groups = groupsArray,
320+
RevisionDate = revisionDate
321+
},
314322
commandType: CommandType.StoredProcedure);
315323
}
316324
}

src/Infrastructure.EntityFramework/AdminConsole/Repositories/GroupRepository.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ from c in dbContext.Collections
3838
Manage = y.Manage,
3939
});
4040
await dbContext.CollectionGroups.AddRangeAsync(collectionGroups);
41+
// Bump RevisionDate on all affected collections
42+
var filteredCollectionIds = filteredCollections.Select(fc => fc.Id).ToHashSet();
43+
foreach (var c in availableCollections.Where(a => filteredCollectionIds.Contains(a.Id)))
44+
{
45+
c.RevisionDate = grp.RevisionDate;
46+
}
4147
await dbContext.SaveChangesAsync();
4248
}
4349
}
@@ -227,6 +233,20 @@ public async Task ReplaceAsync(AdminConsoleEntities.Group group, IEnumerable<Col
227233
dbContext.CollectionGroups.RemoveRange(
228234
existingCollectionGroups.Where(cg => !requestedCollectionIds.Contains(cg.CollectionId)));
229235

236+
// Bump the revision date on all affected collections
237+
var allAffectedCollectionIds = existingCollectionGroups.Select(cg => cg.CollectionId)
238+
.Union(requestedCollections.Select(rc => rc.Id))
239+
.Distinct()
240+
.ToList();
241+
var affectedCollections = await dbContext.Collections
242+
.Where(c => c.OrganizationId == group.OrganizationId
243+
&& allAffectedCollectionIds.Contains(c.Id))
244+
.ToListAsync();
245+
foreach (var c in affectedCollections)
246+
{
247+
c.RevisionDate = group.RevisionDate;
248+
}
249+
230250
await dbContext.UserBumpAccountRevisionDateByOrganizationIdAsync(group.OrganizationId);
231251
await dbContext.SaveChangesAsync();
232252
}

src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationUserRepository.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ from c in dbContext.Collections
4646
Manage = y.Manage
4747
});
4848
await dbContext.CollectionUsers.AddRangeAsync(collectionUsers);
49+
// Bump RevisionDate on all affected collections
50+
var filteredCollectionIds = filteredCollections.Select(fc => fc.Id).ToHashSet();
51+
foreach (var c in availableCollections.Where(a => filteredCollectionIds.Contains(a.Id)))
52+
{
53+
c.RevisionDate = organizationUser.RevisionDate;
54+
}
4955
await dbContext.SaveChangesAsync();
5056
}
5157

@@ -651,6 +657,21 @@ join c in dbContext.Collections on cu.CollectionId equals c.Id
651657
// Remove all existing ones that are no longer requested
652658
var requestedCollectionIds = requestedCollections.Select(c => c.Id).ToList();
653659
dbContext.CollectionUsers.RemoveRange(existingCollectionUsers.Where(cu => !requestedCollectionIds.Contains(cu.CollectionId)));
660+
661+
// Bump the revision date on all affected collections
662+
var allAffectedCollectionIds = existingCollectionUsers.Select(cu => cu.CollectionId)
663+
.Union(requestedCollections.Select(rc => rc.Id))
664+
.Distinct()
665+
.ToList();
666+
var affectedCollections = await dbContext.Collections
667+
.Where(c => c.OrganizationId == obj.OrganizationId
668+
&& allAffectedCollectionIds.Contains(c.Id))
669+
.ToListAsync();
670+
foreach (var c in affectedCollections)
671+
{
672+
c.RevisionDate = obj.RevisionDate;
673+
}
674+
654675
await dbContext.SaveChangesAsync();
655676
}
656677
}
@@ -923,6 +944,11 @@ on ou.UserId equals u.Id
923944

924945
public async Task CreateManyAsync(IEnumerable<CreateOrganizationUser> organizationUserCollection)
925946
{
947+
if (!organizationUserCollection.Any())
948+
{
949+
return;
950+
}
951+
926952
using var scope = ServiceScopeFactory.CreateScope();
927953

928954
await using var dbContext = GetDatabaseContext(scope);
@@ -942,6 +968,27 @@ public async Task CreateManyAsync(IEnumerable<CreateOrganizationUser> organizati
942968
OrganizationUserId = user.OrganizationUser.Id
943969
}));
944970

971+
// Bump RevisionDate on all affected collections
972+
var affectedCollectionIds = organizationUserCollection
973+
.SelectMany(x => x.Collections)
974+
.Select(c => c.Id)
975+
.Distinct()
976+
.ToList();
977+
if (affectedCollectionIds.Count > 0)
978+
{
979+
var organizationId = organizationUserCollection.First().OrganizationUser.OrganizationId;
980+
var affectedCollections = await dbContext.Collections
981+
.Where(c => c.OrganizationId == organizationId
982+
&& affectedCollectionIds.Contains(c.Id))
983+
.ToListAsync();
984+
// Use the same RevisionDate as the created OrganizationUsers
985+
var revisionDate = organizationUserCollection.First().OrganizationUser.RevisionDate;
986+
foreach (var c in affectedCollections)
987+
{
988+
c.RevisionDate = revisionDate;
989+
}
990+
}
991+
945992
await dbContext.SaveChangesAsync();
946993
}
947994

src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -630,7 +630,8 @@ public async Task DeleteManyAsync(IEnumerable<Guid> collectionIds)
630630
}
631631

632632
public async Task CreateOrUpdateAccessForManyAsync(Guid organizationId, IEnumerable<Guid> collectionIds,
633-
IEnumerable<CollectionAccessSelection> users, IEnumerable<CollectionAccessSelection> groups)
633+
IEnumerable<CollectionAccessSelection> users, IEnumerable<CollectionAccessSelection> groups,
634+
DateTime revisionDate)
634635
{
635636
using (var scope = ServiceScopeFactory.CreateScope())
636637
{
@@ -715,6 +716,16 @@ public async Task CreateOrUpdateAccessForManyAsync(Guid organizationId, IEnumera
715716
}
716717
// Need to save the new collection users/groups before running the bump revision code
717718
await dbContext.SaveChangesAsync();
719+
720+
// Bump the revision date on all affected collections
721+
var collections = await dbContext.Collections
722+
.Where(c => collectionIdsList.Contains(c.Id))
723+
.ToListAsync();
724+
foreach (var c in collections)
725+
{
726+
c.RevisionDate = revisionDate;
727+
}
728+
718729
await dbContext.UserBumpAccountRevisionDateByCollectionIdsAsync(collectionIdsList, organizationId);
719730
await dbContext.SaveChangesAsync();
720731
}

0 commit comments

Comments
 (0)