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

SF-3202 Add IsDraftingEnabled flag to Projects API #3002

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
16 changes: 16 additions & 0 deletions src/SIL.XForge.Scripture/Models/ParatextProject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,20 @@ public class ParatextProject
/// </summary>
public bool IsConnected { get; init; }

/// <summary>
/// If the specified project has drafting enabled.
/// </summary>
/// <remarks>
/// A <c>true</c> value does not infer that the user has access to drafting,
/// but that drafting has been configured or can be configured for the project.
/// </remarks>
public bool IsDraftingEnabled { get; init; }

/// <summary>
/// If the specified project has a draft generated.
/// </summary>
public bool HasDraft { get; init; }

/// <summary>
/// A descriptive string of object's properties, for debugging.
/// </summary>
Expand All @@ -77,6 +91,8 @@ public override string ToString()
ProjectId,
IsConnectable.ToString(),
IsConnected.ToString(),
IsDraftingEnabled.ToString(),
HasDraft.ToString(),
IsRightToLeft?.ToString(),
}
)
Expand Down
70 changes: 40 additions & 30 deletions src/SIL.XForge.Scripture/Services/ParatextService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -675,23 +675,25 @@ CancellationToken token
token
);

users = JArray
.Parse(response)
.Where(m =>
!string.IsNullOrEmpty((string?)m["userId"])
&& !string.IsNullOrEmpty((string)m["username"])
&& !string.IsNullOrEmpty((string?)m["role"])
)
.Select(m => new ParatextProjectUser
{
ParatextId = (string)m["userId"] ?? string.Empty,
Role = (string)m["role"] ?? string.Empty,
Username = (string)m["username"] ?? string.Empty,
})
.ToList();
users =
[
.. JArray
.Parse(response)
.Where(m =>
!string.IsNullOrEmpty((string?)m["userId"])
&& !string.IsNullOrEmpty((string)m["username"])
&& !string.IsNullOrEmpty((string?)m["role"])
)
.Select(m => new ParatextProjectUser
{
ParatextId = (string)m["userId"] ?? string.Empty,
Role = (string)m["role"] ?? string.Empty,
Username = (string)m["username"] ?? string.Empty,
}),
];

// Get the mapping of Scripture Forge user IDs to Paratext usernames
string[] paratextIds = users.Select(p => p.ParatextId).ToArray();
string[] paratextIds = [.. users.Select(p => p.ParatextId)];
Dictionary<string, string> userMapping = _realtimeService
.QuerySnapshots<User>()
.Where(u => paratextIds.Contains(u.ParatextId))
Expand Down Expand Up @@ -1253,10 +1255,7 @@ Dictionary<string, ParatextUserProfile> ptProjectUsers
if (existingThread is null)
{
// The thread has been removed
threadChange.NoteIdsRemoved = threadDoc
.Data.Notes.Where(n => !n.Deleted)
.Select(n => n.DataId)
.ToList();
threadChange.NoteIdsRemoved = [.. threadDoc.Data.Notes.Where(n => !n.Deleted).Select(n => n.DataId)];
if (threadChange.NoteIdsRemoved.Count > 0)
changes.Add(threadChange);
continue;
Expand Down Expand Up @@ -1534,8 +1533,8 @@ Term term in projectSettingsBiblicalTerms.Terms.Where(t =>
TermLocalization termLocalization = termLocalizations.GetTermLocalization(term.Id);
BiblicalTermDefinition biblicalTermDefinition = new BiblicalTermDefinition
{
Categories = term.CategoryIds.Select(termLocalizations.GetCategoryLocalization).ToList(),
Domains = term.SemanticDomains.Select(termLocalizations.GetDomainLocalization).ToList(),
Categories = [.. term.CategoryIds.Select(termLocalizations.GetCategoryLocalization)],
Domains = [.. term.SemanticDomains.Select(termLocalizations.GetDomainLocalization)],
Gloss = !string.IsNullOrEmpty(termLocalization.Gloss) ? termLocalization.Gloss : term.Gloss,
Notes = termLocalization.Notes,
};
Expand All @@ -1546,11 +1545,11 @@ Term term in projectSettingsBiblicalTerms.Terms.Where(t =>
{
TermId = term.Id,
Transliteration = term.Transliteration,
Renderings = termRendering.RenderingsEntries.ToList(),
Renderings = [.. termRendering.RenderingsEntries],
Description = termRendering.Notes,
Language = term.Language,
Links = term.Links.ToList(),
References = term.VerseRefs().Select(v => v.BBBCCCVVV).ToList(),
Links = [.. term.Links],
References = [.. term.VerseRefs().Select(v => v.BBBCCCVVV)],
Definitions = definitions,
};
biblicalTermsChanges.BiblicalTerms.Add(biblicalTerm);
Expand Down Expand Up @@ -2252,8 +2251,7 @@ private IReadOnlyList<ParatextProject> GetProjects(
IEnumerable<ProjectMetadata> projectsMetadata
)
{
if (userSecret == null)
throw new ArgumentNullException();
ArgumentNullException.ThrowIfNull(userSecret, nameof(userSecret));

List<ParatextProject> paratextProjects = [];
IQueryable<SFProject> existingSfProjects = _realtimeService.QuerySnapshots<SFProject>();
Expand Down Expand Up @@ -2281,6 +2279,17 @@ IEnumerable<ProjectMetadata> projectsMetadata
correspondingSfProject?.WritingSystem.Tag ?? projectMD?.LanguageId.Code
);

// Determine if drafting is enabled
bool isBackTranslation =
correspondingSfProject?.TranslateConfig.ProjectType == ProjectType.BackTranslation.ToString();
bool preTranslationEnabled = correspondingSfProject?.TranslateConfig.PreTranslate == true;
bool isDraftingEnabled = isBackTranslation || preTranslationEnabled;

// Determine if there is a draft
bool hasDraft =
isDraftingEnabled
&& correspondingSfProject?.Texts.Any(t => t.Chapters.Any(c => c.HasDraft == true)) == true;

paratextProjects.Add(
new ParatextProject
{
Expand All @@ -2293,6 +2302,8 @@ IEnumerable<ProjectMetadata> projectsMetadata
ProjectId = correspondingSfProject?.Id,
IsConnectable = ptProjectIsConnectable,
IsConnected = sfProjectExists && sfUserIsOnSfProject,
IsDraftingEnabled = isDraftingEnabled,
HasDraft = hasDraft,
}
);
}
Expand Down Expand Up @@ -2712,9 +2723,7 @@ private SyncMetricInfo PutCommentThreads(

private void WriteCommentXml(CommentManager commentManager, string username)
{
CommentList userComments = new CommentList(
commentManager.AllComments.Where(comment => comment.User == username)
);
CommentList userComments = [.. commentManager.AllComments.Where(comment => comment.User == username)];
string fileName = commentManager.GetUserFileName(username);
string path = Path.Combine(commentManager.ScrText.Directory, fileName);
using Stream stream = _fileSystemService.CreateFile(path);
Expand Down Expand Up @@ -2817,6 +2826,7 @@ CancellationToken token
: 0,
IsConnectable = false,
IsConnected = false,
IsDraftingEnabled = false,
IsInstalled = resourceRevisions.ContainsKey(r.DBLEntryUid.Id),
LanguageRegion = writingSystem.Region,
LanguageScript = writingSystem.Script,
Expand Down Expand Up @@ -3108,7 +3118,7 @@ private static string GetNoteContentFromComment(Paratext.Data.ProjectComments.Co
return content;
XDocument doc = XDocument.Parse(content);
XElement contentNode = (XElement)doc.FirstNode;
XNode[] nodes = contentNode.Nodes().ToArray();
XNode[] nodes = [.. contentNode.Nodes()];
if (!nodes.Any())
return string.Empty;

Expand Down
126 changes: 110 additions & 16 deletions test/SIL.XForge.Scripture.Tests/Services/ParatextServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public async Task GetProjectsAsync_ReturnCorrectRepos()
);

// Repos are returned in alphabetical order by paratext project name.
List<string> repoList = repos.Select(repo => repo.Name).ToList();
List<string> repoList = [.. repos.Select(repo => repo.Name)];
Assert.That(StringComparer.InvariantCultureIgnoreCase.Compare(repoList[0], repoList[1]), Is.LessThan(0));
Assert.That(StringComparer.InvariantCultureIgnoreCase.Compare(repoList[1], repoList[2]), Is.LessThan(0));
}
Expand Down Expand Up @@ -263,6 +263,75 @@ await env.Service.GetProjectsAsync(testCase.userSecret)
}
}

[Test]
public async Task GetProjectsAsync_IsDraftingEnabled()
{
// Setup
var env = new TestEnvironment();
UserSecret userSecret = TestEnvironment.MakeUserSecret(env.User01, env.Username01, env.ParatextUserId01);
env.SetSharedRepositorySource(userSecret, UserRoles.Administrator);

// 1: A back translation with pre-translation disabled
SFProject project1 = env.NewSFProject(env.Project01);
project1.TranslateConfig.ProjectType = ProjectType.BackTranslation.ToString();

// 2: Not a back translation and pre-translation enabled
SFProject project2 = env.NewSFProject(env.Project02);
project2.TranslateConfig.PreTranslate = true;

// 3: Not a back translation and pre-translation disabled
SFProject project3 = env.NewSFProject(env.Project03);
env.AddProjectRepository([project1, project2, project3]);

// SUT
IReadOnlyList<ParatextProject> projects = await env.Service.GetProjectsAsync(userSecret);
Assert.AreEqual(3, projects.Count);

// 1: A back translation with pre-translation disabled
Assert.IsTrue(projects[0].IsDraftingEnabled);

// 2: Not a back translation and pre-translation enabled
Assert.IsTrue(projects[1].IsDraftingEnabled);

// 3: Not a back translation and pre-translation disabled
Assert.IsFalse(projects[2].IsDraftingEnabled);
}

[Test]
public async Task GetProjectsAsync_HasDraft()
{
// Setup
var env = new TestEnvironment();
UserSecret userSecret = TestEnvironment.MakeUserSecret(env.User01, env.Username01, env.ParatextUserId01);
env.SetSharedRepositorySource(userSecret, UserRoles.Administrator);

// 1: Pre-translation enabled and a draft is present
SFProject project1 = env.NewSFProject(env.Project01);
project1.TranslateConfig.PreTranslate = true;
project1.Texts[0].Chapters[0].HasDraft = true;

// 2: Pre-translation enabled and no draft is present
SFProject project2 = env.NewSFProject(env.Project02);
project2.TranslateConfig.PreTranslate = true;

// 3: Pre-translation disabled
SFProject project3 = env.NewSFProject(env.Project03);
env.AddProjectRepository([project1, project2, project3]);

// SUT
IReadOnlyList<ParatextProject> projects = await env.Service.GetProjectsAsync(userSecret);
Assert.AreEqual(3, projects.Count);

// 1: Pre-translation enabled and a draft is present
Assert.IsTrue(projects[0].HasDraft);

// 2: Pre-translation enabled and no draft is present
Assert.IsFalse(projects[1].HasDraft);

// 3: Pre-translation disabled
Assert.IsFalse(projects[2].HasDraft);
}

[Test]
public async Task GetResourcesAsync_ReturnResources()
{
Expand Down Expand Up @@ -1698,9 +1767,17 @@ public async Task GetNoteThreadChanges_DuplicateCommentsToExistingThread()
{
new ParatextUserProfile { OpaqueUserId = "syncuser01", Username = env.Username01 },
}.ToDictionary(u => u.Username);
List<NoteThreadChange> changes = env
.Service.GetNoteThreadChanges(userSecret, projectId, 40, noteThreadDocs, chapterDeltas, ptProjectUsers)
.ToList();
List<NoteThreadChange> changes =
[
.. env.Service.GetNoteThreadChanges(
userSecret,
projectId,
40,
noteThreadDocs,
chapterDeltas,
ptProjectUsers
),
];

Assert.That(changes.Count, Is.EqualTo(1));
Assert.That(changes[0].NotesAdded.Count, Is.EqualTo(1));
Expand Down Expand Up @@ -2657,9 +2734,17 @@ public async Task GetNoteThreadChanges_SupportsBiblicalTerms()
Dictionary<int, ChapterDelta> chapterDeltas = env.GetChapterDeltasByBook(1, "Context before ", "Text selected");

// SUT
IList<NoteThreadChange> changes = env
.Service.GetNoteThreadChanges(userSecret, ptProjectId, 40, noteThreadDocs, chapterDeltas, ptProjectUsers)
.ToList();
IList<NoteThreadChange> changes =
[
.. env.Service.GetNoteThreadChanges(
userSecret,
ptProjectId,
40,
noteThreadDocs,
chapterDeltas,
ptProjectUsers
),
];
// We fetched a single change, of one new note to create.

Assert.That(changes.Count, Is.EqualTo(1));
Expand Down Expand Up @@ -2755,9 +2840,10 @@ public async Task UpdateParatextComments_AddsComment()
CommentThread thread = env.ProjectCommentManager.FindThread(thread1);
Assert.That(thread, Is.Null);
string[] noteThreadDataIds = [dataId1, dataId2, dataId3];
List<IDocument<NoteThread>> noteThreadDocs = (
await TestEnvironment.GetNoteThreadDocsAsync(conn, noteThreadDataIds)
).ToList();
List<IDocument<NoteThread>> noteThreadDocs =
[
.. (await TestEnvironment.GetNoteThreadDocsAsync(conn, noteThreadDataIds)),
];
Dictionary<string, ParatextUserProfile> ptProjectUsers = new Dictionary<string, ParatextUserProfile>
{
{
Expand Down Expand Up @@ -6569,11 +6655,13 @@ public IInternetSharedRepositorySource SetSharedRepositorySource(
return mockSource;
}

public SFProject NewSFProject() =>
new SFProject
public SFProject NewSFProject(string? projectId = null)
{
projectId ??= Project01;
return new SFProject
{
Id = "sf_id_" + Project01,
ParatextId = PTProjectIds[Project01].Id,
Id = "sf_id_" + projectId,
ParatextId = PTProjectIds[projectId].Id,
Name = "Full Name " + Project01,
ShortName = "P01",
WritingSystem = new WritingSystem { Tag = "en" },
Expand Down Expand Up @@ -6634,6 +6722,7 @@ public SFProject NewSFProject() =>
},
},
};
}

public void AddTextDataOps(string projectId, string book, int chapter)
{
Expand Down Expand Up @@ -6685,7 +6774,12 @@ public void AddTextDataOps(string projectId, string book, int chapter)
public void AddProjectRepository(SFProject proj = null)
{
proj ??= NewSFProject();
RealtimeService.AddRepository("sf_projects", OTType.Json0, new MemoryRepository<SFProject>([proj]));
AddProjectRepository([proj]);
}

public void AddProjectRepository(SFProject[] projects)
{
RealtimeService.AddRepository("sf_projects", OTType.Json0, new MemoryRepository<SFProject>(projects));
MockFileSystemService
.DirectoryExists(
Arg.Is<string>((string path) => path.EndsWith(Path.Combine(PTProjectIds[Project01].Id, "target")))
Expand Down Expand Up @@ -7319,7 +7413,7 @@ private static string GetAssignedUserStr(ThreadNoteComponents[] notes)
{
if (notes == null)
return CommentThread.unassignedUser;
List<ThreadNoteComponents> notesList = new List<ThreadNoteComponents>(notes);
List<ThreadNoteComponents> notesList = [.. notes];
return notesList.LastOrDefault(n => n.assignedPTUser != null).assignedPTUser
?? CommentThread.unassignedUser;
}
Expand Down
Loading