diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-import-wizard/draft-import-wizard.component.html b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-import-wizard/draft-import-wizard.component.html
index 7e85db47cf7..7c573b8e6c9 100644
--- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-import-wizard/draft-import-wizard.component.html
+++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-import-wizard/draft-import-wizard.component.html
@@ -309,7 +309,7 @@
{{ t("importing_draft") }}
@if (progress.failedChapters.length > 0) {
error
- {{ t("failed") }} {{ getFailedChapters(progress) }}
+ {{ t("failed") }} {{ getFailedChapters(progress) }}
}
diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-import-wizard/draft-import-wizard.component.scss b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-import-wizard/draft-import-wizard.component.scss
index d734deebb40..3c07f4f1f94 100644
--- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-import-wizard/draft-import-wizard.component.scss
+++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-import-wizard/draft-import-wizard.component.scss
@@ -46,6 +46,10 @@
.error-icon {
font-size: 1.1rem;
}
+
+ .error-message {
+ width: 100%;
+ }
}
}
diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-import-wizard/draft-import-wizard.component.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-import-wizard/draft-import-wizard.component.ts
index 8fbc151812c..3e0a62cd464 100644
--- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-import-wizard/draft-import-wizard.component.ts
+++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-import-wizard/draft-import-wizard.component.ts
@@ -792,16 +792,30 @@ export class DraftImportWizardComponent implements OnInit {
}
getFailedChapters(progress: ImportProgress): string {
- const failedChapters: number[] = progress.failedChapters.filter(f => f.chapterNum !== 0).map(f => f.chapterNum);
if (!progress.failedChapters.some(f => f.chapterNum === 0)) {
- // A subset of chapters failed
- return failedChapters.join(', ');
+ // A subset of chapters failed. Display the failure message next to each chapter, if present.
+ return progress.failedChapters
+ .filter(f => f.chapterNum !== 0)
+ .map(f => (f.message ? `${f.chapterNum} (${f.message})` : f.chapterNum))
+ .join(', ');
} else if (progress.totalChapters > 1) {
- // All chapters failed, so display as a range
- return `1-${progress.totalChapters}`;
+ // All chapters failed, so display as a range, with any failure messages
+ return (
+ `1-${progress.totalChapters}. ` +
+ progress.failedChapters
+ .filter(f => f.chapterNum === 0 && f.message != null)
+ .map(f => f.message)
+ .join(', ')
+ ).trim();
} else {
- // The only chapter in the book failed
- return `${progress.totalChapters}`;
+ // The only chapter in the book failed, so display that chapter number with any failure messages
+ return (
+ `${progress.totalChapters}. ` +
+ progress.failedChapters
+ .filter(f => f.chapterNum === 0 && f.message != null)
+ .map(f => f.message)
+ .join(', ')
+ ).trim();
}
}
diff --git a/src/SIL.XForge.Scripture/Services/IParatextService.cs b/src/SIL.XForge.Scripture/Services/IParatextService.cs
index a1644cd1c2e..75f556051fe 100644
--- a/src/SIL.XForge.Scripture/Services/IParatextService.cs
+++ b/src/SIL.XForge.Scripture/Services/IParatextService.cs
@@ -82,6 +82,8 @@ Task UpdateParatextPermissionsForNewBooksAsync(
UserSecret userSecret,
string paratextId,
IDocument projectDoc,
+ int[] booksToUpdate,
+ bool currentUserOnly,
bool writeToParatext
);
string? GetLatestSharedVersion(UserSecret userSecret, string paratextId);
diff --git a/src/SIL.XForge.Scripture/Services/MachineApiService.cs b/src/SIL.XForge.Scripture/Services/MachineApiService.cs
index 35d30caf7b8..5cef43831a7 100644
--- a/src/SIL.XForge.Scripture/Services/MachineApiService.cs
+++ b/src/SIL.XForge.Scripture/Services/MachineApiService.cs
@@ -501,15 +501,28 @@ await projectService.UpdatePermissionsAsync(
.ToList(),
cancellationToken
);
+
+ // When the user is a project administrator, and do not have permission to
+ // update the specified books, add the permissions for them to update them.
+ await paratextService.UpdateParatextPermissionsForNewBooksAsync(
+ userSecret,
+ targetProjectDoc.Data.ParatextId,
+ targetProjectDoc,
+ booksToUpdate: [.. updatedBooks],
+ currentUserOnly: true,
+ writeToParatext: false
+ );
}
if (createdBooks.Count > 0)
{
- // Update permissions for new books
+ // Update permissions for new books, adding all users that should have access to them
await paratextService.UpdateParatextPermissionsForNewBooksAsync(
userSecret,
targetProjectDoc.Data.ParatextId,
targetProjectDoc,
+ booksToUpdate: [],
+ currentUserOnly: false,
writeToParatext: false
);
}
@@ -533,7 +546,7 @@ await draftHubContext.NotifyDraftApplyProgress(
BookNum = bookNum,
ChapterNum = 0,
Status = DraftApplyStatus.Failed,
- Message = $"Could not save draft for {Canon.BookNumberToId(bookNum)}.",
+ Message = $"You do not have permission to write to this book.",
}
);
}
@@ -563,7 +576,7 @@ await draftHubContext.NotifyDraftApplyProgress(
BookNum = bookNum,
ChapterNum = 0,
Status = DraftApplyStatus.Failed,
- Message = $"Could not save draft for {Canon.BookNumberToId(bookNum)}.",
+ Message = $"You do not have permission to write to this book.",
}
);
}
@@ -585,8 +598,7 @@ await draftHubContext.NotifyDraftApplyProgress(
BookNum = bookNum,
ChapterNum = chapterDelta.Number,
Status = DraftApplyStatus.Failed,
- Message =
- $"Could not save draft for {Canon.BookNumberToId(bookNum)} {chapterDelta.Number}.",
+ Message = $"You do not have permission to write to this chapter.",
}
);
continue;
@@ -619,8 +631,7 @@ await draftHubContext.NotifyDraftApplyProgress(
BookNum = bookNum,
ChapterNum = chapterDelta.Number,
Status = DraftApplyStatus.Failed,
- Message =
- $"Could not save draft for {Canon.BookNumberToId(bookNum)} {chapterDelta.Number}.",
+ Message = $"You do not have permission to write to this chapter.",
}
);
continue;
diff --git a/src/SIL.XForge.Scripture/Services/ParatextService.cs b/src/SIL.XForge.Scripture/Services/ParatextService.cs
index 6f62568ee5a..27deb6473d5 100644
--- a/src/SIL.XForge.Scripture/Services/ParatextService.cs
+++ b/src/SIL.XForge.Scripture/Services/ParatextService.cs
@@ -1732,10 +1732,27 @@ IReadOnlyList biblicalTerms
return syncMetricInfo;
}
+ ///
+ /// Updates Paratext permissions for any new books, or the books specified in .
+ ///
+ /// The user secret.
+ /// The Paratext project identifier
+ /// The project document.
+ ///
+ /// The book numbers to update.
+ /// If specified these books will be updated, otherwise only new books not on disk will be updated.
+ ///
+ /// If true, only update permissions for the current user.
+ ///
+ /// If true, update the Paratext project on disk; otherwise if false, update the project document.
+ ///
+ ///
public async Task UpdateParatextPermissionsForNewBooksAsync(
UserSecret userSecret,
string paratextId,
IDocument projectDoc,
+ int[] booksToUpdate,
+ bool currentUserOnly,
bool writeToParatext
)
{
@@ -1751,19 +1768,26 @@ bool writeToParatext
return syncMetricInfo;
}
- // Get all projects that are not on disk
+ // Get all books that are not on disk
for (int i = 0; i < projectDoc.Data.Texts.Count; i++)
{
TextInfo text = projectDoc.Data.Texts[i];
int bookNum = text.BookNum;
- if (scrText.BookPresent(bookNum))
+ if (booksToUpdate.Length == 0 && scrText.BookPresent(bookNum))
+ {
+ // This book is already on disk, so skip to the next book
+ continue;
+ }
+ else if (booksToUpdate.Length > 0 && !booksToUpdate.Contains(bookNum))
{
- // Book is on disk, skip to the next book
+ // This book is not one of the ones we are to update, so skip to the next book
continue;
}
// Add any users to the book who would have the ability to access it
- foreach (var user in projectDoc.Data.ParatextUsers)
+ foreach (
+ var user in projectDoc.Data.ParatextUsers.Where(u => !currentUserOnly || u.SFUserId == userSecret.Id)
+ )
{
// If there is no SF user id or PT username, ignore this user
if (string.IsNullOrEmpty(user.SFUserId) || string.IsNullOrEmpty(user.Username))
diff --git a/src/SIL.XForge.Scripture/Services/ParatextSyncRunner.cs b/src/SIL.XForge.Scripture/Services/ParatextSyncRunner.cs
index 1298a152c22..6b48e9fffe5 100644
--- a/src/SIL.XForge.Scripture/Services/ParatextSyncRunner.cs
+++ b/src/SIL.XForge.Scripture/Services/ParatextSyncRunner.cs
@@ -216,6 +216,8 @@ CancellationToken token
_userSecret,
targetParatextId,
_projectDoc,
+ booksToUpdate: [],
+ currentUserOnly: false,
writeToParatext: true
);
await GetAndUpdateParatextBooksAndNotes(
diff --git a/test/SIL.XForge.Scripture.Tests/Services/MachineApiServiceTests.cs b/test/SIL.XForge.Scripture.Tests/Services/MachineApiServiceTests.cs
index e809c420cc6..4ba95f69ec6 100644
--- a/test/SIL.XForge.Scripture.Tests/Services/MachineApiServiceTests.cs
+++ b/test/SIL.XForge.Scripture.Tests/Services/MachineApiServiceTests.cs
@@ -240,6 +240,8 @@ public async Task ApplyPreTranslationToProjectAsync_ExceptionFromParatext()
Arg.Any(),
Arg.Any(),
Arg.Any>(),
+ booksToUpdate: [],
+ currentUserOnly: false,
writeToParatext: false
)
.ThrowsAsync(new NotSupportedException());
@@ -5118,13 +5120,15 @@ int writeChapters
}
});
- // Update the permissions for the user applying the draft
+ // Update the permissions for the user adding new books
ParatextService
.When(x =>
x.UpdateParatextPermissionsForNewBooksAsync(
Arg.Any(),
Arg.Any(),
Arg.Any>(),
+ booksToUpdate: [],
+ currentUserOnly: false,
writeToParatext: false
)
)
@@ -5147,6 +5151,39 @@ int writeChapters
}
}
});
+
+ // Update the permissions for the user applying the draft to existing books
+ ParatextService
+ .When(x =>
+ x.UpdateParatextPermissionsForNewBooksAsync(
+ Arg.Any(),
+ Arg.Any(),
+ Arg.Any>(),
+ booksToUpdate: Arg.Any(),
+ currentUserOnly: true,
+ writeToParatext: false
+ )
+ )
+ .Do(callInfo =>
+ {
+ UserSecret userSecret = callInfo.ArgAt(0);
+ var projectDoc = callInfo.ArgAt>(2);
+ int[] booksToUpdate = callInfo.ArgAt(3);
+ foreach (var text in projectDoc.Data.Texts.Where(t => booksToUpdate.Contains(t.BookNum)))
+ {
+ text.Permissions.TryAdd(
+ userSecret.Id,
+ canWriteBook ? TextInfoPermission.Write : TextInfoPermission.Read
+ );
+ foreach (var chapter in text.Chapters)
+ {
+ chapter.Permissions.TryAdd(
+ userSecret.Id,
+ chapter.Number <= writeChapters ? TextInfoPermission.Write : TextInfoPermission.Read
+ );
+ }
+ }
+ });
}
public async Task VerifyDraftAsync(
@@ -5166,6 +5203,8 @@ await ParatextService
Arg.Any(),
Arg.Any(),
Arg.Any>(),
+ booksToUpdate: [],
+ currentUserOnly: false,
writeToParatext: false
);
}
diff --git a/test/SIL.XForge.Scripture.Tests/Services/ParatextServiceTests.cs b/test/SIL.XForge.Scripture.Tests/Services/ParatextServiceTests.cs
index ba2e68f65da..d84258ea479 100644
--- a/test/SIL.XForge.Scripture.Tests/Services/ParatextServiceTests.cs
+++ b/test/SIL.XForge.Scripture.Tests/Services/ParatextServiceTests.cs
@@ -6443,6 +6443,8 @@ public async Task UpdateParatextPermissionsForNewBooksAsync_AllBooksPresentOnDis
userSecret,
paratextId,
projectDoc,
+ booksToUpdate: [],
+ currentUserOnly: false,
writeToParatext: false
);
SyncMetricInfo expected = new SyncMetricInfo();
@@ -6472,6 +6474,8 @@ public async Task UpdateParatextPermissionsForNewBooksAsync_MissingScrText()
userSecret,
paratextId,
projectDoc,
+ booksToUpdate: [],
+ currentUserOnly: false,
writeToParatext: false
);
SyncMetricInfo expected = new SyncMetricInfo();
@@ -6501,6 +6505,8 @@ public async Task UpdateParatextPermissionsForNewBooksAsync_ProjectNotEditable()
userSecret,
paratextId,
projectDoc,
+ booksToUpdate: [],
+ currentUserOnly: false,
writeToParatext: false
);
SyncMetricInfo expected = new SyncMetricInfo();
@@ -6530,6 +6536,8 @@ public async Task UpdateParatextPermissionsForNewBooksAsync_ProjectHasNoBooks()
userSecret,
paratextId,
projectDoc,
+ booksToUpdate: [],
+ currentUserOnly: false,
writeToParatext: false
);
SyncMetricInfo expected = new SyncMetricInfo();
@@ -6564,6 +6572,8 @@ public async Task UpdateParatextPermissionsForNewBooksAsync_UpdatesMongo()
userSecret,
paratextId,
projectDoc,
+ booksToUpdate: [],
+ currentUserOnly: false,
writeToParatext: false
);
SyncMetricInfo expected = new SyncMetricInfo { Updated = 1 };
@@ -6608,6 +6618,8 @@ public async Task UpdateParatextPermissionsForNewBooksAsync_UpdatesParatext()
userSecret,
paratextId,
projectDoc,
+ booksToUpdate: [],
+ currentUserOnly: false,
writeToParatext: true
);
SyncMetricInfo expected = new SyncMetricInfo { Updated = 1 };
diff --git a/test/SIL.XForge.Scripture.Tests/Services/ParatextSyncRunnerTests.cs b/test/SIL.XForge.Scripture.Tests/Services/ParatextSyncRunnerTests.cs
index 1b43b7a846d..1955c8cb9d8 100644
--- a/test/SIL.XForge.Scripture.Tests/Services/ParatextSyncRunnerTests.cs
+++ b/test/SIL.XForge.Scripture.Tests/Services/ParatextSyncRunnerTests.cs
@@ -758,6 +758,8 @@ await env
Arg.Any(),
Arg.Any(),
Arg.Any>(),
+ booksToUpdate: [],
+ currentUserOnly: false,
writeToParatext: true
);
}