-
-
Notifications
You must be signed in to change notification settings - Fork 5
SF-3657 Move project progress calculation to back end #3626
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
base: master
Are you sure you want to change the base?
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #3626 +/- ##
==========================================
- Coverage 82.81% 82.55% -0.26%
==========================================
Files 610 611 +1
Lines 37462 37530 +68
Branches 6142 6143 +1
==========================================
- Hits 31024 30983 -41
- Misses 5500 5605 +105
- Partials 938 942 +4 ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR moves project progress calculation from the frontend to the backend, replacing real-time client-side computation with server-side MongoDB aggregation. The change improves performance by using database-level aggregation and implements a caching layer to reduce redundant API calls.
Key Changes
- Backend MongoDB aggregation pipeline calculates progress by counting verse segments and blank segments per book
- Frontend ProgressService simplified from real-time subscription model to cached promise-based API
- Progress data structure changed from text-based to book-based with explicit verse segment counts
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 17 comments.
Show a summary per file
| File | Description |
|---|---|
| src/SIL.XForge.Scripture/Services/SFProjectService.cs | Adds GetProjectProgressAsync method with MongoDB aggregation pipeline to calculate book progress |
| src/SIL.XForge.Scripture/Services/ISFProjectService.cs | Adds interface method signature for GetProjectProgressAsync |
| src/SIL.XForge.Scripture/Models/ProjectProgress.cs | New BookProgress model to represent verse segment counts per book |
| src/SIL.XForge.Scripture/Controllers/SFProjectsRpcController.cs | New RPC endpoint GetProjectProgress exposing backend calculation to frontend |
| src/SIL.XForge.Scripture/ClientApp/src/app/translate/translate-overview/translate-overview.component.ts | Updated to call new progress API with caching and use ProjectProgress model |
| src/SIL.XForge.Scripture/ClientApp/src/app/translate/translate-overview/translate-overview.component.html | Template updated to work with book-based progress data structure |
| src/SIL.XForge.Scripture/ClientApp/src/app/translate/translate-overview/translate-overview.component.spec.ts | Tests updated to mock new getProgress API instead of observables |
| src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/draft-generation-steps.component.ts | Updated to fetch and use book progress data for training book selection |
| src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/draft-generation-steps.component.spec.ts | Tests updated to mock ProjectProgress instead of TextProgress |
| src/SIL.XForge.Scripture/ClientApp/src/app/shared/progress-service/progress.service.ts | Complete rewrite from real-time subscription model to cached promise-based API with deduplication |
| src/SIL.XForge.Scripture/ClientApp/src/app/shared/progress-service/progress.service.spec.ts | New test suite for cache behavior, concurrency handling, and progress calculations |
| src/SIL.XForge.Scripture/ClientApp/src/app/shared/book-multi-select/book-multi-select.component.ts | Updated to fetch progress via new API and added projectId input requirement |
| src/SIL.XForge.Scripture/ClientApp/src/app/shared/book-multi-select/book-multi-select.component.spec.ts | Tests updated to mock new ProjectProgress data structure |
| src/SIL.XForge.Scripture/ClientApp/src/app/core/sf-project.service.ts | Added getProjectProgress method to call backend RPC endpoint |
src/SIL.XForge.Scripture/ClientApp/src/app/shared/progress-service/progress.service.ts
Outdated
Show resolved
Hide resolved
| async getProjectProgress(projectId: string): Promise<BookProgress[]> { | ||
| // TODO remove non-null assertion after #3622 is merged | ||
| return (await this.onlineInvoke<BookProgress[]>('getProjectProgress', { projectId }))!; |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The TODO comment references issue #3622, but the non-null assertion operator is being used without checking if this will actually be resolved by that issue. The backend method GetProjectProgress returns BookProgress[] directly, not a nullable type, so the non-null assertion may not be needed at all. Verify if this TODO is accurate and if the assertion is actually necessary.
| async getProjectProgress(projectId: string): Promise<BookProgress[]> { | |
| // TODO remove non-null assertion after #3622 is merged | |
| return (await this.onlineInvoke<BookProgress[]>('getProjectProgress', { projectId }))!; | |
| async getProjectProgress(projectId: string): Promise<BookProgress[] | undefined> { | |
| // TODO remove non-null assertion after #3622 is merged | |
| return await this.onlineInvoke<BookProgress[]>('getProjectProgress', { projectId }); |
...L.XForge.Scripture/ClientApp/src/app/shared/book-multi-select/book-multi-select.component.ts
Outdated
Show resolved
Hide resolved
...rge.Scripture/ClientApp/src/app/translate/translate-overview/translate-overview.component.ts
Show resolved
Hide resolved
| .catch(error => { | ||
| this.requestCache.delete(projectId); |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error handling doesn't notify users when progress data fails to load. When the promise rejects, the error is simply re-thrown, but there's no user-facing notification. Consider using the NoticeService to display an error message to the user when progress data cannot be fetched, similar to patterns used elsewhere in the codebase.
| .catch(error => { | |
| this.requestCache.delete(projectId); | |
| .catch((error: unknown) => { | |
| this.requestCache.delete(projectId); | |
| this.noticeService.show('An error occurred while loading project progress.'); |
...rc/app/translate/draft-generation/draft-generation-steps/draft-generation-steps.component.ts
Outdated
Show resolved
Hide resolved
...e.Scripture/ClientApp/src/app/translate/translate-overview/translate-overview.component.html
Outdated
Show resolved
Hide resolved
...rc/app/translate/draft-generation/draft-generation-steps/draft-generation-steps.component.ts
Outdated
Show resolved
Hide resolved
| new BsonDocument | ||
| { | ||
| { "input", new BsonDocument("$ifNull", new BsonArray { "$$segment.attributes.segment", "" }) }, | ||
| { "regex", "^verse_" }, |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The regex pattern "^verse_" will match any segment starting with "verse_", but this doesn't validate that what follows is a valid verse identifier. This could potentially match segments like "verse_" (with nothing after), "verse_invalid", etc. Consider if additional validation is needed to ensure only properly formatted verse segments are counted, or document that this is intentional behavior.
| { "regex", "^verse_" }, | |
| { "regex", "^verse_\\d" }, |
954adec to
2f98d91
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated 7 comments.
...XForge.Scripture/ClientApp/src/app/shared/book-multi-select/book-multi-select.component.html
Outdated
Show resolved
Hide resolved
| public async Task<BookProgress[]> GetProjectProgressAsync(string curUserId, string projectId) | ||
| { | ||
| // Check that the project exists and user has permission to view it | ||
| SFProject project = await GetProjectAsync(projectId); | ||
| if (!project.UserRoles.ContainsKey(curUserId)) | ||
| throw new ForbiddenException(); | ||
|
|
||
| // Checks whether a segment is a verse segment by checking if the attributes.segment field starts with "verse_" | ||
| BsonDocument isVerseSegmentIdExpression = new BsonDocument( | ||
| "$regexMatch", | ||
| new BsonDocument | ||
| { | ||
| { "input", new BsonDocument("$ifNull", new BsonArray { "$$segment.attributes.segment", "" }) }, | ||
| { "regex", "^verse_" }, | ||
| } | ||
| ); | ||
|
|
||
| // Filters for ops that are verse segments (i.e., attributes.segment starts with "verse_") | ||
| BsonDocument verseSegmentOpsFilterExpression = new BsonDocument | ||
| { | ||
| { "input", "$ops" }, | ||
| { "as", "segment" }, | ||
| { "cond", isVerseSegmentIdExpression }, | ||
| }; | ||
| // Same as above filter, except that insert.blank must also be true in order to match a segment | ||
| BsonDocument blankVerseSegmentOpsFilterExpression = new BsonDocument | ||
| { | ||
| { "input", "$ops" }, | ||
| { "as", "segment" }, | ||
| { | ||
| "cond", | ||
| new BsonDocument( | ||
| "$and", | ||
| new BsonArray | ||
| { | ||
| isVerseSegmentIdExpression, | ||
| new BsonDocument("$eq", new BsonArray { "$$segment.insert.blank", true }), | ||
| } | ||
| ) | ||
| }, | ||
| }; | ||
|
|
||
| List<BsonDocument> results = await _database | ||
| .GetCollection<BsonDocument>("texts") | ||
| .Aggregate() | ||
| // Filter for text documents that belong to the specified project | ||
| .Match(Builders<BsonDocument>.Filter.Regex("_id", new BsonRegularExpression($"^{projectId}:"))) | ||
| // Project: | ||
| // - Extract the book ID from the document ID | ||
| // - Count the number of verse segments | ||
| // - Count the number of blank verse segments | ||
| .Project( | ||
| new BsonDocument | ||
| { | ||
| { "_id", 1 }, | ||
| { | ||
| "book", | ||
| new BsonDocument( | ||
| "$arrayElemAt", | ||
| new BsonArray { new BsonDocument("$split", new BsonArray { "$_id", ":" }), 1 } | ||
| ) | ||
| }, | ||
| { | ||
| "verseSegments", | ||
| new BsonDocument("$size", new BsonDocument("$filter", verseSegmentOpsFilterExpression)) | ||
| }, | ||
| { | ||
| "blankVerseSegments", | ||
| new BsonDocument("$size", new BsonDocument("$filter", blankVerseSegmentOpsFilterExpression)) | ||
| }, | ||
| } | ||
| ) | ||
| // Group progress by book and count the total verse segments and blank verse segments for each book | ||
| .Group( | ||
| new BsonDocument | ||
| { | ||
| { "_id", "$book" }, | ||
| { "verseSegments", new BsonDocument("$sum", "$verseSegments") }, | ||
| { "blankVerseSegments", new BsonDocument("$sum", "$blankVerseSegments") }, | ||
| } | ||
| ) | ||
| .ToListAsync(); | ||
|
|
||
| var books = results | ||
| .Select(doc => new BookProgress | ||
| { | ||
| BookId = doc["_id"].AsString, | ||
| VerseSegments = doc["verseSegments"].AsInt32, | ||
| BlankVerseSegments = doc["blankVerseSegments"].AsInt32, | ||
| }) | ||
| .ToArray(); | ||
|
|
||
| return books; | ||
| } |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new GetProjectProgressAsync method in SFProjectService lacks test coverage. Since the test file SFProjectServiceTests.cs exists with comprehensive tests for other methods in this service, tests should be added for this new method to maintain consistent test coverage, especially given the complexity of the MongoDB aggregation pipeline logic.
| getBookNameFromId(bookId: string): string { | ||
| const bookNum = Canon.bookIdToNumber(bookId); | ||
| return this.i18n.localizeBook(bookNum); | ||
| } | ||
|
|
||
| get hasMinimumSegmentsToTrainStatisticalEngine(): boolean { | ||
| return (this.projectProgress?.translatedVerseSegments ?? 0) >= 10; | ||
| } | ||
|
|
||
| bookTranslatedSegments(bookProgress: BookProgress): number { | ||
| return bookProgress.verseSegments - bookProgress.blankVerseSegments; | ||
| } | ||
|
|
||
| bookTranslationRatio(bookProgress: BookProgress): number { | ||
| if (bookProgress.verseSegments === 0) { | ||
| return 0; | ||
| } | ||
| return this.bookTranslatedSegments(bookProgress) / bookProgress.verseSegments; | ||
| } | ||
|
|
||
| getBookName(text: TextInfo): string { | ||
| return this.i18n.localizeBook(text.bookNum); | ||
| } |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The method getBookNameFromId has been added but the existing method getBookName(text: TextInfo) still exists and serves a similar purpose. Consider consolidating these methods or adding a comment explaining why both are needed to avoid confusion and potential duplication.
| if (this.projectId == null) { | ||
| console.error('app-book-multi-select requires a projectId input to initialize when not in basic mode'); | ||
| return; | ||
| } |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message is logged to console.error but doesn't notify the user or handle the error gracefully. When projectId is missing in non-basic mode, the component returns early but leaves the UI in a partially initialized state. Consider throwing an error or showing a user-facing error message using the noticeService to make this issue more visible.
| await firstValueFrom(this.progressService.isLoaded$.pipe(filter(loaded => loaded))); | ||
| // Only load progress if not in basic mode | ||
| let progress: ProjectProgress | undefined; | ||
| if (this.basicMode !== true) { |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comparison basicMode !== true is used instead of a simple falsy check. However, according to the coding guidelines, you should use explicit comparisons rather than relying on truthy/falsy. Since basicMode is a boolean, consider using basicMode === false instead of basicMode !== true for better clarity about what state you're checking for.
| <mat-list role="list"> | ||
| @for (textProgress of progressService.texts; track trackTextByBookNum($index, textProgress)) { | ||
| <mat-list-item role="listitem" [appRouterLink]="['./', getBookId(textProgress.text)]"> | ||
| @for (bookProgress of projectProgress.books; track bookProgress.bookId) { |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The template is now using trackTextByBookId with bookProgress.bookId directly in the @for loop instead of using the trackBy function. Consider removing the trackTextByBookId function if it's no longer needed, or update the @for directive to use it: @for (bookProgress of projectProgress.books; track trackTextByBookId($index, bookProgress))
| @for (bookProgress of projectProgress.books; track bookProgress.bookId) { | |
| @for (bookProgress of projectProgress.books; track trackTextByBookId($index, bookProgress)) { |
| verseSegments = this.books.reduce((acc, book) => acc + book.verseSegments, 0); | ||
| blankVerseSegments = this.books.reduce((acc, book) => acc + book.blankVerseSegments, 0); | ||
| translatedVerseSegments = this.verseSegments - this.blankVerseSegments; | ||
| ratio = this.verseSegments === 0 ? 0 : this.translatedVerseSegments / this.verseSegments; |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fields verseSegments, blankVerseSegments, translatedVerseSegments, and ratio are declared without explicit type annotations. According to the coding guidelines, variables should have explicit type annotations. Add type annotations for these fields (e.g., verseSegments: number = this.books.reduce...).
49d180d to
8920f29
Compare
RaymondLuong3
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like there is some test coverage issues with this PR. Particularly I think the coverage for SFProjectsRpcController can easily be added.
I tested this out and the progress appears to be fast and the caching is working nicely.
Can you fill out the acceptance tests for this on the issue?
@RaymondLuong3 reviewed 17 files and all commit messages, and made 8 comments.
Reviewable status: all files reviewed, 19 unresolved discussions (waiting on @for and @Nateowami).
src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/draft-generation-steps.component.ts line 63 at r4 (raw file):
// The project needs at least 10 translated segments for the statistical engine to be able to train. Additionally, the // segments need to be aligned with training data in the source project, though this component doesn't check for that. const MIN_TRANSLATED_SEGMENTS_FOR_STATISTICAL_ENGINE = 10 as const;
This might not be named right. It is being used to determine if a book should be auto selected for training data in the NMT engine.
Code quote:
const MIN_TRANSLATED_SEGMENTS_FOR_STATISTICAL_ENGINE = 10 as const;src/SIL.XForge.Scripture/ClientApp/src/app/shared/progress-service/progress.service.ts line 104 at r4 (raw file):
* For most books, this will be the same as the ratio of translated segments to total segments, but if a book has very * few segments but many expected verses (total segments < 10% of expected verses), it's unlikely the book actually * combines verses so much that it's produced this ratio, and more likely the book is just missing most verses. In this
typo
Code quote:
it's produced this ratio,src/SIL.XForge.Scripture/ClientApp/src/app/shared/progress-service/progress.service.ts line 111 at r4 (raw file):
const translatedSegments = bookProgress.verseSegments - bookProgress.blankVerseSegments; const expectedNumberOfVerses = verseCounts[bookProgress.bookId] ?? 0; if (expectedNumberOfVerses !== 0 && bookProgress.verseSegments < expectedNumberOfVerses * 0.1) {
I would give the 0.1 a meaningful name.
src/SIL.XForge.Scripture/ClientApp/src/app/shared/book-multi-select/book-multi-select.component.ts line 152 at r4 (raw file):
} /** Takes a number between 0 and 100, and rounds it by flooring any number between 99 and 100 to 99 */
Seems like a strange thing to do. What problem does this fix? Do percentages round up?
Code quote:
/** Takes a number between 0 and 100, and rounds it by flooring any number between 99 and 100 to 99 */src/SIL.XForge.Scripture/ClientApp/src/app/shared/progress-service/progress.service.spec.ts line 23 at r4 (raw file):
{ bookId: 'MAT', verseSegments: 50, blankVerseSegments: 10 } ]; when(mockedProjectService.getProjectProgress(projectId)).thenReturn(Promise.resolve(expectedBooks));
Nit: You should be able to use thenResolve(...) to make it cleaner
Code quote:
thenReturn(Promise.resolve(expectedBooks));src/SIL.XForge.Scripture/ClientApp/src/app/shared/progress-service/progress.service.spec.ts line 64 at r4 (raw file):
when(mockedProjectService.getProjectProgress(projectId)) .thenReturn(Promise.resolve(firstBooks))
nit: You should be able to use .thenResolve(...)
Code quote:
.thenReturn(Promise.resolve(firstBooks))src/SIL.XForge.Scripture/ClientApp/src/app/shared/progress-service/progress.service.spec.ts line 134 at r4 (raw file):
const project2Books: BookProgress[] = [{ bookId: 'MAT', verseSegments: 50, blankVerseSegments: 10 }]; when(mockedProjectService.getProjectProgress('project1')).thenReturn(Promise.resolve(project1Books));
nit: This can probably use thenResolve(...)
Code quote:
thenReturn(Promise.resolve(project1Books));65f28f7 to
522913e
Compare
Nateowami
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Nateowami made 3 comments and resolved 5 discussions.
Reviewable status: 10 of 17 files reviewed, 14 unresolved discussions (waiting on @for and @RaymondLuong3).
src/SIL.XForge.Scripture/ClientApp/src/app/shared/book-multi-select/book-multi-select.component.ts line 152 at r4 (raw file):
Previously, RaymondLuong3 (Raymond Luong) wrote…
Seems like a strange thing to do. What problem does this fix? Do percentages round up?
We are avoiding showing 100% when the actual progress is something less than 100%. In general, "100%" means something is complete.
src/SIL.XForge.Scripture/ClientApp/src/app/shared/progress-service/progress.service.ts line 104 at r4 (raw file):
Previously, RaymondLuong3 (Raymond Luong) wrote…
typo
I'm not sure what the typo is; when I re-read it, it says what I intended to write. Maybe it's unclear?
src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/draft-generation-steps.component.ts line 63 at r4 (raw file):
Previously, RaymondLuong3 (Raymond Luong) wrote…
This might not be named right. It is being used to determine if a book should be auto selected for training data in the NMT engine.
🤦♂️ 🤦♂️ 🤦♂️ Thanks for catching that.
522913e to
7f7763f
Compare
Nateowami
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Particularly I think the coverage for SFProjectsRpcController can easily be added.
I'm really not sure how we would write tests for the query logic without bringing an entire Mongo implementation into the tests. Which is possible, but seems like a lot to add for just this change.
@Nateowami made 1 comment.
Reviewable status: 10 of 17 files reviewed, 14 unresolved discussions (waiting on @for and @RaymondLuong3).
RaymondLuong3
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The controller calls the project service. Writing the tests for the controller means you will mock the service and not have to write any logic to mock a query.
@RaymondLuong3 partially reviewed 7 files and all commit messages, made 3 comments, and resolved 1 discussion.
Reviewable status: all files reviewed, 13 unresolved discussions (waiting on @for and @Nateowami).
src/SIL.XForge.Scripture/ClientApp/src/app/shared/book-multi-select/book-multi-select.component.ts line 152 at r4 (raw file):
Previously, Nateowami wrote…
We are avoiding showing 100% when the actual progress is something less than 100%. In general, "100%" means something is complete.
That is what I thought. But it still feels strange that it is needed since the percentage is <100 it should not return 1.0. Maybe it round up for values 0.9999 and you want to account for that.
src/SIL.XForge.Scripture/ClientApp/src/app/shared/progress-service/progress.service.ts line 104 at r4 (raw file):
Previously, Nateowami wrote…
I'm not sure what the typo is; when I re-read it, it says what I intended to write. Maybe it's unclear?
Oh interesting. I looked it up and my AI tells me that it's can be a contraction for it has. When I was reading it I was sure "it's" was not the right word.
ca0a6da to
15bb34a
Compare
Nateowami
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I missed that you were asking about testing the SFProjectsRpcController instead of the SFProjectService. I struggle to think of a test that could fail meaningfully.
@Nateowami made 3 comments.
Reviewable status: 14 of 17 files reviewed, 13 unresolved discussions (waiting on @for and @RaymondLuong3).
src/SIL.XForge.Scripture/ClientApp/src/app/shared/book-multi-select/book-multi-select.component.ts line 152 at r4 (raw file):
Previously, RaymondLuong3 (Raymond Luong) wrote…
That is what I thought. But it still feels strange that it is needed since the percentage is <100 it should not return 1.0. Maybe it round up for values 0.9999 and you want to account for that.
I'm not really sure what you mean, but I've made changes that I hope simplify things. In general I think all ratios should be 0-1 unless/until it's time do display them to a user, at which point they can be formatted. I've made that change in hopes of simplifying things.
As to the question of how the number formatter works, it certainly seems that is rounds:
Intl.NumberFormat('en', { style: 'percent' }).format(0.999) // '100%'src/SIL.XForge.Scripture/ClientApp/src/app/shared/progress-service/progress.service.ts line 111 at r4 (raw file):
Previously, RaymondLuong3 (Raymond Luong) wrote…
I would give the 0.1 a meaningful name.
Done.
15bb34a to
d19acaf
Compare
RaymondLuong3
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's true that a meaningful test is not really necessary. But a simple test to exercise the code and cover it is sufficient.
@RaymondLuong3 reviewed 5 files and all commit messages, made 4 comments, and resolved 3 discussions.
Reviewable status: all files reviewed, 12 unresolved discussions (waiting on @for and @Nateowami).
src/SIL.XForge.Scripture/ClientApp/src/app/shared/book-multi-select/book-multi-select.component.ts line 152 at r4 (raw file):
Previously, Nateowami wrote…
I'm not really sure what you mean, but I've made changes that I hope simplify things. In general I think all ratios should be 0-1 unless/until it's time do display them to a user, at which point they can be formatted. I've made that change in hopes of simplifying things.
As to the question of how the number formatter works, it certainly seems that is rounds:
Intl.NumberFormat('en', { style: 'percent' }).format(0.999) // '100%'
Thanks, the changes look good.
src/SIL.XForge.Scripture/ClientApp/src/app/shared/book-multi-select/book-multi-select.component.html line 56 at r9 (raw file):
[value]="book" [selected]="book.selected" [matTooltip]="t('book_progress', { percent: normalizeRatioToPercent(book.progress) | l10nPercent })"
Looks like you need to leave the percent parameter as a decimal value between 0-100
Code quote:
[matTooltip]="t('book_progress', { percent: normalizeRatioToPercent(book.progress)src/SIL.XForge.Scripture/ClientApp/src/app/shared/book-multi-select/book-multi-select.component.html line 59 at r9 (raw file):
> {{ "canon.book_names." + book.bookId | transloco }} <div class="border-fill" [style.width]="book.progress + '%'"></div>
You will want to normalizeRatioToPercentage the progress here
Code quote:
<div class="border-fill" [style.width]="book.progress + '%'"></div>d19acaf to
a72446d
Compare
Nateowami
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see what the value is in trying to "exercise the code". I don't think it makes sense to add tests for the sake of having tests; tests need to have a purpose otherwise they just add a maintenance cost with no benefits. I've added a test to make sure it doesn't allow users that aren't on a project to see progress, since that's actually important and possible to test without impacting the Mongo query logic.
@Nateowami made 3 comments.
Reviewable status: 15 of 17 files reviewed, 12 unresolved discussions (waiting on @for and @RaymondLuong3).
src/SIL.XForge.Scripture/ClientApp/src/app/shared/book-multi-select/book-multi-select.component.html line 56 at r9 (raw file):
Previously, RaymondLuong3 (Raymond Luong) wrote…
Looks like you need to leave the percent parameter as a decimal value between 0-100
Somehow I thought I just moved where the multiplication by 100 was done, but I clearly messed up. Thanks for catching it.
src/SIL.XForge.Scripture/ClientApp/src/app/shared/book-multi-select/book-multi-select.component.html line 59 at r9 (raw file):
Previously, RaymondLuong3 (Raymond Luong) wrote…
You will want to
normalizeRatioToPercentagethe progress here
Fixed.

This change is