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

Support custom semantic domains ending in 0 #3081

Merged
merged 59 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
a639607
[Backend] Support importing custom sem doms ending in 0
imnasnainaec Apr 30, 2024
f59a20a
Allow multilingual, full custom project sem doms
imnasnainaec Apr 30, 2024
61503c7
Add stub settings tab for custom domains
imnasnainaec Apr 30, 2024
a4d70a4
[ProjectSettings] Add control for adding custom domain
imnasnainaec May 1, 2024
2817fec
Merge branch 'master' into sem-dom-custom
imnasnainaec Jun 6, 2024
bf2823d
Merge branch 'master' into sem-dom-custom
imnasnainaec Jun 7, 2024
8114204
Add shim
imnasnainaec Jun 7, 2024
32cebd3
Add guids to new sem doms
imnasnainaec Jun 7, 2024
c47131e
Continue developing project domains UI
imnasnainaec Jun 7, 2024
64f6961
Make custom domains project setting fully functional
imnasnainaec Jun 10, 2024
5656c43
Remove unused import
imnasnainaec Jun 10, 2024
11c7afc
Remove debugging console.info
imnasnainaec Jun 10, 2024
571c0cc
Fix navigation with custom domains
imnasnainaec Jun 10, 2024
7616b7f
Fix boolean error
imnasnainaec Jun 10, 2024
04fe7a5
Retract change implemented in branch theme-colors
imnasnainaec Jun 10, 2024
1d2ea58
[TreeViewActions] Comment new helper functions
imnasnainaec Jun 10, 2024
1c33053
[ProjectSettings] Label new setting
imnasnainaec Jun 10, 2024
2d3e398
Merge branch 'master' into sem-dom-custom
imnasnainaec Jun 12, 2024
0784a88
[ProjectSettings] Add sem dom tests
imnasnainaec Jun 12, 2024
0fde9bb
Try to split SemanticDomain models into base and DB versions
imnasnainaec Jun 12, 2024
2571e3e
Merge branch 'master' into sem-dom-custom
imnasnainaec Jun 12, 2024
1ee914a
Merge branch 'sem-dom-custom' of https://github.com/sillsdev/TheCombi…
imnasnainaec Jun 12, 2024
55ec405
Update tests with new sem dom models
imnasnainaec Jun 12, 2024
f7d8fa3
Revert sem dom model split and make MongoId optional
imnasnainaec Jun 14, 2024
e9120d7
Update tree navigation functions
imnasnainaec Jun 14, 2024
803b68d
Restore wrongfully deleted lines
imnasnainaec Jun 14, 2024
21aadd1
Merge branch 'master' into sem-dom-custom
imnasnainaec Jun 14, 2024
66fb47c
Add missing parenteses
imnasnainaec Jun 14, 2024
3d9bac5
Fix lift range writing
imnasnainaec Jun 14, 2024
d2f9e88
Simplify constructor
imnasnainaec Jun 14, 2024
b658e35
Support import/export of custom domain description
imnasnainaec Jun 14, 2024
ba575ee
Add explicit check for empty id
imnasnainaec Jun 18, 2024
f69ff90
Add comments to ProjectDomains
imnasnainaec Jun 18, 2024
d5bcb3e
Revert variable change
imnasnainaec Jun 18, 2024
4a7c62b
Mark type imports
imnasnainaec Jun 18, 2024
35ff63c
Clean up utility comments
imnasnainaec Jun 18, 2024
7ab5b33
Add utilities tests and fix bug
imnasnainaec Jun 18, 2024
0fd32c3
Fix nav-to-root bug; Add parent import/export support
imnasnainaec Jun 18, 2024
f0cb32b
Fix tests
imnasnainaec Jun 18, 2024
dfc2815
Export questions
imnasnainaec Jun 19, 2024
1faf6c1
Use implicit new()
imnasnainaec Jun 19, 2024
6a0ecb4
Change parent handling
imnasnainaec Jun 19, 2024
83fa684
[ProjectDomain] Expand testing a little bit
imnasnainaec Jun 21, 2024
60a8906
Test trimDomain
imnasnainaec Jun 21, 2024
dc8298e
Simplify text element writing
imnasnainaec Jun 21, 2024
2eb52e0
Merge branch 'master' into sem-dom-custom
imnasnainaec Jun 24, 2024
d133a36
Don't export questions till we consider LIFT standard & FLEx import
imnasnainaec Jun 24, 2024
6c1b7f2
Remove unused helper function
imnasnainaec Jun 24, 2024
0d4d23f
Add missing parentheses
imnasnainaec Jun 24, 2024
a46d1d0
Merge branch 'master' into sem-dom-custom
imnasnainaec Jun 26, 2024
0839301
Add precautionary comments
imnasnainaec Jun 26, 2024
bd17d2a
Fix test name
imnasnainaec Jun 27, 2024
7b97637
Merge branch 'master' into sem-dom-custom
imnasnainaec Jun 28, 2024
5ec9512
Merge branch 'master' into sem-dom-custom
imnasnainaec Jun 28, 2024
8b230fd
Start updating User Guide
imnasnainaec Jul 1, 2024
a35a076
Rename _semDoms to _customSemDoms
imnasnainaec Jul 1, 2024
48b5b07
Merge branch 'master' into sem-dom-custom
imnasnainaec Jul 1, 2024
f3ac259
Refactor TreeView utility tests
imnasnainaec Jul 1, 2024
2727088
Merge branch 'sem-dom-custom' of https://github.com/sillsdev/TheCombi…
imnasnainaec Jul 1, 2024
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
6 changes: 0 additions & 6 deletions Backend.Tests/Controllers/LiftControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -587,12 +587,6 @@ public void TestRoundtrip(RoundTripObj roundTripObj)
}
}

// Assert that the first SemanticDomain doesn't have an empty MongoId.
if (allWords[0].Senses.Count > 0 && allWords[0].Senses[0].SemanticDomains.Count > 0)
{
Assert.That(allWords[0].Senses[0].SemanticDomains[0].MongoId, Is.Not.Empty);
}

// Export.
var exportedFilePath = _liftController.CreateLiftExport(proj1.Id).Result;
var exportedDirectory = FileOperations.ExtractZipFile(exportedFilePath, null);
Expand Down
4 changes: 2 additions & 2 deletions Backend.Tests/Controllers/SemanticDomainControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ protected virtual void Dispose(bool disposing)
private const string Id = "1";
private const string Lang = "en";
private const string Name = "Universe";
private readonly SemanticDomainFull _semDom = new() { Id = Id, Lang = Lang, Name = Name };
private readonly SemanticDomain _semDom = new() { Id = Id, Lang = Lang, Name = Name };

[SetUp]
public void Setup()
Expand Down Expand Up @@ -60,7 +60,7 @@ public void GetAllSemanticDomainNamesNotFound()
[Test]
public void GetSemanticDomainFullDomainFound()
{
((SemanticDomainRepositoryMock)_semDomRepository).SetNextResponse(_semDom);
((SemanticDomainRepositoryMock)_semDomRepository).SetNextResponse(new SemanticDomainFull(_semDom));
var domain = (SemanticDomainFull?)(
(ObjectResult)_semDomController.GetSemanticDomainFull(Id, Lang).Result).Value;
Assert.That(domain?.Id, Is.EqualTo(Id));
Expand Down
37 changes: 24 additions & 13 deletions Backend.Tests/Models/ProjectTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ public void TestNotEquals()
Assert.That(project.Equals(project2), Is.False);

project2 = project.Clone();
project2.AnalysisWritingSystems.Add(new WritingSystem());
project2.AnalysisWritingSystems.Add(new());
Assert.That(project.Equals(project2), Is.False);

project2 = project.Clone();
project2.SemanticDomains.Add(new SemanticDomain());
project2.SemanticDomains.Add(new());
Assert.That(project.Equals(project2), Is.False);

project2 = project.Clone();
Expand All @@ -74,7 +74,7 @@ public void TestNotEquals()
Assert.That(project.Equals(project2), Is.False);

project2 = project.Clone();
project2.CustomFields.Add(new CustomField());
project2.CustomFields.Add(new());
Assert.That(project.Equals(project2), Is.False);

project2 = project.Clone();
Expand All @@ -99,16 +99,27 @@ public void TestNotEquals()
[Test]
public void TestClone()
{
var system = new WritingSystem("en", "WritingSystemName", "calibri");
var project = new Project { Name = "ProjectName", VernacularWritingSystem = system };
var domain = new SemanticDomain { Name = "SemanticDomainName", Id = "1" };
project.SemanticDomains.Add(domain);

var customField = new CustomField { Name = "CustomFieldName", Type = "type" };
project.CustomFields.Add(customField);

var emailInvite = new EmailInvite(10, "[email protected]", Role.Harvester);
project.InviteTokens.Add(emailInvite);
var project = new Project
{
Id = "ProjectId",
Name = "ProjectName",
IsActive = true,
LiftImported = true,
DefinitionsEnabled = true,
GrammaticalInfoEnabled = true,
AutocompleteSetting = AutocompleteSetting.On,
SemDomWritingSystem = new("fr", "Français"),
VernacularWritingSystem = new("en", "English", "Calibri"),
AnalysisWritingSystems = new() { new("es", "Español") },
SemanticDomains = new() { new() { Name = "SemanticDomainName", Id = "1" } },
ValidCharacters = new() { "a", "b", "c" },
RejectedCharacters = new() { "X", "Y", "Z" },
CustomFields = new() { new() { Name = "CustomFieldName", Type = "type" } },
WordFields = new() { "some field string" },
PartsOfSpeech = new() { "noun", "verb" },
InviteTokens = new() { new(10, "[email protected]", Role.Harvester) },
WorkshopSchedule = new() { new(2222, 2, 22), },
};

var project2 = project.Clone();
Assert.That(project, Is.EqualTo(project2));
Expand Down
48 changes: 48 additions & 0 deletions Backend.Tests/Models/SemanticDomainTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,54 @@ public void TestHashCode()
Is.Not.EqualTo(new SemanticDomain { Name = Guid.NewGuid().ToString() }.GetHashCode())
);
}

private static readonly List<string> _invalidIds = new()
{
"",
"a",
"123",
"1.42.9",
".1.3.6",
"9.9.r",
"1.2.3..4"
};

[TestCaseSource(nameof(_invalidIds))]
public void TestIsValidIdInvalid(string id)
{
Assert.That(SemanticDomain.IsValidId(id), Is.False);
Assert.That(SemanticDomain.IsValidId(id, true), Is.False);
}

private static readonly List<string> _validIds = new()
{
"6",
"3.7",
"1.2.9",
"9.9.9.1",
};

[TestCaseSource(nameof(_validIds))]
public void TestIsValidIdValid(string id)
{
Assert.That(SemanticDomain.IsValidId(id), Is.True);
Assert.That(SemanticDomain.IsValidId(id, true), Is.True);
}

private static readonly List<string> _customIds = new()
{
"0",
"3.0",
"1.2.0",
"9.9.9.0",
};

[TestCaseSource(nameof(_customIds))]
public void TestIsValidIdCustom(string id)
{
Assert.That(SemanticDomain.IsValidId(id), Is.False);
Assert.That(SemanticDomain.IsValidId(id, true), Is.True);
}
}

public class SemanticDomainFullTests
Expand Down
10 changes: 5 additions & 5 deletions Backend.Tests/Util.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,20 +110,20 @@ public static Project RandomProject()
{
Name = RandString(),
VernacularWritingSystem = RandomWritingSystem(),
AnalysisWritingSystems = new List<WritingSystem> { RandomWritingSystem() },
SemanticDomains = new List<SemanticDomain>()
AnalysisWritingSystems = new() { RandomWritingSystem() },
SemanticDomains = new()
};

const int numSemanticDomains = 3;
foreach (var i in Range(1, numSemanticDomains))
{
project.SemanticDomains.Add(RandomSemanticDomain($"{i}"));
project.SemanticDomains.Add(new(RandomSemanticDomain($"{i}")));
foreach (var j in Range(1, numSemanticDomains))
{
project.SemanticDomains.Add(RandomSemanticDomain($"{i}.{j}"));
project.SemanticDomains.Add(new(RandomSemanticDomain($"{i}.{j}")));
foreach (var k in Range(1, numSemanticDomains))
{
project.SemanticDomains.Add(RandomSemanticDomain($"{i}.{j}.{k}"));
project.SemanticDomains.Add(new(RandomSemanticDomain($"{i}.{j}.{k}")));
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions Backend/Controllers/LiftController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,15 @@ private async Task<IActionResult> AddImportToProject(string liftStoragePath, str
project.DefinitionsEnabled = doesImportHaveDefinitions;
project.GrammaticalInfoEnabled = doesImportHaveGrammaticalInfo;

// Add new custom domains to the project
liftMerger.GetCustomSemanticDomains().ForEach(customDom =>
{
if (!project.SemanticDomains.Any(dom => dom.Id == customDom.Id && dom.Lang == customDom.Lang))
{
project.SemanticDomains.Add(customDom);
}
});

// Store that we have imported LIFT data already for this project
// to signal the frontend not to attempt to import again in this project.
project.LiftImported = true;
Expand Down
3 changes: 2 additions & 1 deletion Backend/Interfaces/ILiftService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public interface ILiftService
ILiftMerger GetLiftImporterExporter(string projectId, string vernLang, IWordRepository wordRepo);
Task<bool> LdmlImport(string dirPath, IProjectRepository projRepo, Project project);
Task<string> LiftExport(string projectId, IWordRepository wordRepo, IProjectRepository projRepo);
Task CreateLiftRanges(List<SemanticDomain> projDoms, string rangesDest);
Task CreateLiftRanges(List<SemanticDomainFull> projDoms, string rangesDest);

// Methods to store, retrieve, and delete an export string in a common dictionary.
void StoreExport(string userId, string filePath);
Expand All @@ -27,6 +27,7 @@ public interface ILiftMerger : ILexiconMerger<LiftObject, LiftEntry, LiftSense,
{
bool DoesImportHaveDefinitions();
bool DoesImportHaveGrammaticalInfo();
List<SemanticDomainFull> GetCustomSemanticDomains();
List<WritingSystem> GetImportAnalysisWritingSystems();
Task<List<Word>> SaveImportEntries();
}
Expand Down
3 changes: 2 additions & 1 deletion Backend/Models/Project.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,10 @@ public class Project
[BsonElement("analysisWritingSystems")]
public List<WritingSystem> AnalysisWritingSystems { get; set; }

/// <summary> Custom, project-specific semantic domains. </summary>
[Required]
[BsonElement("semanticDomains")]
public List<SemanticDomain> SemanticDomains { get; set; }
public List<SemanticDomainFull> SemanticDomains { get; set; }

[Required]
[BsonElement("validCharacters")]
Expand Down
80 changes: 67 additions & 13 deletions Backend/Models/SemanticDomain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,22 @@ public class SemanticDomain
[BsonId]
[BsonElement("_id")]
[BsonRepresentation(BsonType.ObjectId)]
public string MongoId { get; set; }
public string? MongoId { get; set; }

[Required]
[BsonElement("guid")]
#pragma warning disable CA1720
public string Guid { get; set; }
#pragma warning restore CA1720

[Required]
[BsonElement("name")]
public string Name { get; set; }

[Required]
[BsonElement("id")]
public string Id { get; set; }

[Required]
[BsonElement("lang")]
public string Lang { get; set; }
Expand All @@ -37,7 +41,6 @@ public class SemanticDomain

public SemanticDomain()
{
MongoId = "";
Guid = System.Guid.Empty.ToString();
Name = "";
Id = "";
Expand Down Expand Up @@ -78,32 +81,75 @@ public override int GetHashCode()
{
return HashCode.Combine(Name, Id, Lang, Guid, UserId, Created);
}

/// <summary>
/// Check if given id string is a valid id: single non-0 digits divided by periods.
/// If allowCustom is set to true, allow the final digit to be 0.
/// </summary>
public static bool IsValidId(string id, bool allowCustom = false)
{
// Ensure the id is nonempty and composed of digits and periods
if (string.IsNullOrEmpty(id) || !id.All(c => char.IsDigit(c) || c == '.'))
{
return false;
}

// Check that each number between periods is a single non-zero digit
var parts = id.Split(".");
var allSingleDigit = parts.All(d => d.Length == 1);
if (allowCustom)
{
// Custom domains may have 0 as the final digit
parts = parts.Take(parts.Length - 1).ToArray();
}
return allSingleDigit && parts.All(d => d != "0");
}
}

public class SemanticDomainFull : SemanticDomain
{
[Required]
[BsonElement("description")]
public string Description { get; set; }

[Required]
[BsonElement("parentId")]
public string ParentId { get; set; }

[Required]
[BsonElement("questions")]
public List<string> Questions { get; set; }

public SemanticDomainFull()
public SemanticDomainFull() : base()
{
Name = "";
Id = "";
Description = "";
ParentId = "";
Questions = new();
}

public SemanticDomainFull(SemanticDomain semDom)
{
MongoId = semDom.MongoId;
Guid = semDom.Guid;
Name = semDom.Name;
Id = semDom.Id;
Lang = semDom.Lang;
UserId = semDom.UserId;
Created = semDom.Created;

Description = "";
ParentId = "";
Questions = new();
Lang = "";
}

public new SemanticDomainFull Clone()
{
var clone = (SemanticDomainFull)base.Clone();
clone.Description = Description;
clone.Questions = Questions.Select(q => q).ToList();
return clone;
return new(base.Clone())
{
Description = Description,
ParentId = ParentId,
Questions = Questions.Select(q => q).ToList()
};
}

public override bool Equals(object? obj)
Expand All @@ -116,13 +162,14 @@ public override bool Equals(object? obj)
return
base.Equals(other) &&
Description.Equals(other.Description, StringComparison.Ordinal) &&
ParentId.Equals(other.ParentId, StringComparison.Ordinal) &&
Questions.Count == other.Questions.Count &&
Questions.All(other.Questions.Contains);
}

public override int GetHashCode()
{
return HashCode.Combine(Name, Id, Description, Questions);
return HashCode.Combine(Name, Id, Description, ParentId, Questions);
}
}

Expand All @@ -134,36 +181,43 @@ public class SemanticDomainTreeNode
[BsonId]
[BsonElement("_id")]
[BsonRepresentation(BsonType.ObjectId)]
public string MongoId { get; set; }
public string? MongoId { get; set; }

[Required]
[BsonElement("lang")]
public string Lang { get; set; }

[Required]
[BsonElement("guid")]
#pragma warning disable CA1720
public string Guid { get; set; }
#pragma warning restore CA1720

[Required]
[BsonElement("name")]
public string Name { get; set; }

[Required]
[BsonElement("id")]
public string Id { get; set; }

[BsonElement("prev")]
public SemanticDomain? Previous { get; set; }

[BsonElement("next")]
public SemanticDomain? Next { get; set; }

[BsonElement("parent")]
public SemanticDomain? Parent { get; set; }

[Required]
[BsonElement("children")]
public List<SemanticDomain> Children { get; set; }

public SemanticDomainTreeNode(SemanticDomain sd)
{
Guid = sd.Guid;
MongoId = sd.MongoId;
Guid = sd.Guid;
Lang = sd.Lang;
Name = sd.Name;
Id = sd.Id;
Expand Down
Loading
Loading