Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix entry sync with complex forms #1264

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace FwDataMiniLcmBridge.Api.UpdateProxy;

public class UpdateEntryProxy : Entry
public record UpdateEntryProxy : Entry
{
private readonly ILexEntry _lcmEntry;
private readonly FwDataMiniLcmApi _lexboxLcmApi;
Expand Down Expand Up @@ -50,7 +50,7 @@
get =>
new UpdateListProxy<Sense>(
sense => _lexboxLcmApi.CreateSense(_lcmEntry, sense),
sense => _lexboxLcmApi.DeleteSense(Id, sense.Id),

Check warning on line 53 in backend/FwLite/FwDataMiniLcmBridge/Api/UpdateProxy/UpdateEntryProxy.cs

View workflow job for this annotation

GitHub Actions / Build FW Lite and run tests

Observe the awaitable result of this method call by awaiting it, assigning to a variable, or passing it to another method (https://github.com/Microsoft/vs-threading/blob/main/doc/analyzers/VSTHRD110.md)

Check warning on line 53 in backend/FwLite/FwDataMiniLcmBridge/Api/UpdateProxy/UpdateEntryProxy.cs

View workflow job for this annotation

GitHub Actions / Build FwHeadless / publish-fw-headless

Observe the awaitable result of this method call by awaiting it, assigning to a variable, or passing it to another method (https://github.com/Microsoft/vs-threading/blob/main/doc/analyzers/VSTHRD110.md)
i => new UpdateSenseProxy(_lcmEntry.SensesOS[i], _lexboxLcmApi),
_lcmEntry.SensesOS.Count
);
Expand Down
23 changes: 23 additions & 0 deletions backend/FwLite/FwLiteProjectSync.Tests/EntrySyncTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,27 @@
actual.Should().NotBeNull();
actual.Should().BeEquivalentTo(after, options => options);
}

[Fact]
public async Task CanCreateAComplexFormAndItsComponentInOneSync()
{
//ensure they are synced so a real sync will happen when we want it to
await _fixture.SyncService.Sync(_fixture.CrdtApi, _fixture.FwDataApi);

var complexFormEntry = await _fixture.CrdtApi.CreateEntry(new() { LexemeForm = { { "en", "complexForm" } } });
var componentEntry = await _fixture.CrdtApi.CreateEntry(new()
{
LexemeForm = { { "en", "component" } },
ComplexForms =
[
new ComplexFormComponent()
{
ComplexFormEntryId = complexFormEntry.Id, ComponentEntryId = Guid.Empty
}
]
});

//one of the entries will be created first, it will try to create the reference to the other but it won't exist yet
await _fixture.SyncService.Sync(_fixture.CrdtApi, _fixture.FwDataApi);

Check failure on line 129 in backend/FwLite/FwLiteProjectSync.Tests/EntrySyncTests.cs

View workflow job for this annotation

GitHub Actions / Build FW Lite and run tests

FwLiteProjectSync.Tests.EntrySyncTests.CanCreateAComplexFormAndItsComponentInOneSync

System.InvalidOperationException : Sequence contains no matching element
}
}
7 changes: 6 additions & 1 deletion backend/FwLite/MiniLcm/Models/Entry.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace MiniLcm.Models;

public class Entry : IObjectWithId<Entry>
public record Entry : IObjectWithId<Entry>
{
public Guid Id { get; set; }
public DateTimeOffset? DeletedAt { get; set; }
Expand Down Expand Up @@ -68,6 +68,11 @@ public Guid[] GetReferences()
public void RemoveReference(Guid id, DateTimeOffset time)
{
}

public Entry WithoutEntryRefs()
{
return this with { Components = [], ComplexForms = [] };
}
}

public class Variants
Expand Down
68 changes: 68 additions & 0 deletions backend/FwLite/MiniLcm/SyncHelpers/DiffCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,73 @@ namespace MiniLcm.SyncHelpers;

public static class DiffCollection
{
/// <summary>
/// Diffs a list, for new items calls add, it will then call update for the item returned from the add, using that as the before item for the replace call
/// </summary>
/// <param name="api"></param>
/// <param name="before"></param>
/// <param name="after"></param>
/// <param name="identity"></param>
/// <param name="add">api, value, return value to be used as the before item for the replace call</param>
/// <param name="remove"></param>
/// <param name="replace">api, before, after is the parameter order</param>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TId"></typeparam>
/// <returns></returns>
public static async Task<int> DiffAddThenUpdate<T, TId>(
IMiniLcmApi api,
IList<T> before,
IList<T> after,
Func<T, TId> identity,
Func<IMiniLcmApi, T, Task<T>> add,
Func<IMiniLcmApi, T, Task<int>> remove,
Func<IMiniLcmApi, T, T, Task<int>> replace) where TId : notnull
{
var changes = 0;
var afterEntriesDict = after.ToDictionary(identity);

foreach (var beforeEntry in before)
{
if (afterEntriesDict.TryGetValue(identity(beforeEntry), out var afterEntry))
{
changes += await replace(api, beforeEntry, afterEntry);
}
else
{
changes += await remove(api, beforeEntry);
}

afterEntriesDict.Remove(identity(beforeEntry));
}

var postAddUpdates = new List<(T created, T after)>(afterEntriesDict.Values.Count);
foreach (var value in afterEntriesDict.Values)
{
changes++;
postAddUpdates.Add((await add(api, value), value));
}
foreach ((T createdItem, T afterItem) in postAddUpdates)
{
//todo this may do a lot more work than it needs to, eg sense will be created during add, but they will be checked again here when we know they didn't change
await replace(api, createdItem, afterItem);
}

return changes;
}

/// <summary>
///
/// </summary>
/// <param name="api"></param>
/// <param name="before"></param>
/// <param name="after"></param>
/// <param name="identity"></param>
/// <param name="add"></param>
/// <param name="remove"></param>
/// <param name="replace">api, before, after is the parameter order</param>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TId"></typeparam>
/// <returns></returns>
public static async Task<int> Diff<T, TId>(
IMiniLcmApi api,
IList<T> before,
Expand Down Expand Up @@ -36,6 +103,7 @@ public static async Task<int> Diff<T, TId>(

return changes;
}

public static async Task<int> Diff<T>(
IMiniLcmApi api,
IList<T> before,
Expand Down
11 changes: 7 additions & 4 deletions backend/FwLite/MiniLcm/SyncHelpers/EntrySync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,21 @@ public static async Task<int> Sync(Entry[] afterEntries,
Entry[] beforeEntries,
IMiniLcmApi api)
{
Func<IMiniLcmApi, Entry, Task<int>> add = static async (api, afterEntry) =>
Func<IMiniLcmApi, Entry, Task<Entry>> add = static async (api, afterEntry) =>
{
await api.CreateEntry(afterEntry);
return 1;
//create each entry without components.
//After each entry is created, then replace will be called to create those components
var entryWithoutEntryRefs = afterEntry.WithoutEntryRefs();
await api.CreateEntry(entryWithoutEntryRefs);
return entryWithoutEntryRefs;
};
Func<IMiniLcmApi, Entry, Task<int>> remove = static async (api, beforeEntry) =>
{
await api.DeleteEntry(beforeEntry.Id);
return 1;
};
Func<IMiniLcmApi, Entry, Entry, Task<int>> replace = static async (api, beforeEntry, afterEntry) => await Sync(afterEntry, beforeEntry, api);
return await DiffCollection.Diff(api, beforeEntries, afterEntries, add, remove, replace);
return await DiffCollection.DiffAddThenUpdate(api, beforeEntries, afterEntries, entry => entry.Id, add, remove, replace);
}

public static async Task<int> Sync(Entry afterEntry, Entry beforeEntry, IMiniLcmApi api)
Expand Down
Loading