diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-entry/draft-history-entry.component.html b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-entry/draft-history-entry.component.html index 33b92c532c9..6ea82f88bda 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-entry/draft-history-entry.component.html +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-entry/draft-history-entry.component.html @@ -21,7 +21,7 @@
- @if (this.featureFlags.usfmFormat.enabled && !formattingOptionsSelected && isLatestBuild && draftIsAvailable) { + @if (formattingOptionsSupported && !formattingOptionsSelected && isLatestBuild && draftIsAvailable) {

{{ t("select_formatting_options") }}

@@ -49,7 +49,7 @@

- @if (featureFlags.usfmFormat.enabled && isLatestBuild) { + @if (formattingOptionsSupported && isLatestBuild) { diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-entry/draft-history-entry.component.spec.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-entry/draft-history-entry.component.spec.ts index 5f9e5b4e3f3..dd035d16c99 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-entry/draft-history-entry.component.spec.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-entry/draft-history-entry.component.spec.ts @@ -22,6 +22,7 @@ import { SFProjectService } from '../../../../core/sf-project.service'; import { BuildDto } from '../../../../machine-api/build-dto'; import { BuildStates } from '../../../../machine-api/build-states'; import { DraftGenerationService } from '../../draft-generation.service'; +import { FORMATTING_OPTIONS_SUPPORTED_DATE } from '../../draft-utils'; import { TrainingDataService } from '../../training-data/training-data.service'; import { DraftHistoryEntryComponent } from './draft-history-entry.component'; @@ -33,6 +34,10 @@ const mockedTrainingDataService = mock(TrainingDataService); const mockedActivatedProjectService = mock(ActivatedProjectService); const mockedFeatureFlagsService = mock(FeatureFlagService); +const oneDay = 1000 * 60 * 60 * 24; +const dateBeforeFormattingSupported = new Date(FORMATTING_OPTIONS_SUPPORTED_DATE.getTime() - oneDay).toISOString(); +const dateAfterFormattingSupported = new Date(FORMATTING_OPTIONS_SUPPORTED_DATE.getTime() + oneDay).toISOString(); + describe('DraftHistoryEntryComponent', () => { let component: DraftHistoryEntryComponent; let fixture: ComponentFixture; @@ -99,7 +104,7 @@ describe('DraftHistoryEntryComponent', () => { it('should handle builds with additional info', fakeAsync(() => { when(mockedI18nService.enumerateList(anything())).thenReturn('src'); const user = 'user-display-name'; - const date = 'formatted-date'; + const date = dateAfterFormattingSupported; const trainingBooks = ['EXO']; const translateBooks = ['GEN']; const trainingDataFiles: Map = new Map([['file01', 'training-data.txt']]); @@ -144,7 +149,7 @@ describe('DraftHistoryEntryComponent', () => { it('should state that the model did not have training configuration', fakeAsync(() => { when(mockedI18nService.enumerateList(anything())).thenReturn('src'); const user = 'user-display-name'; - const date = 'formatted-date'; + const date = dateAfterFormattingSupported; const trainingBooks = []; const translateBooks = ['GEN']; const trainingDataFiles = []; @@ -161,7 +166,7 @@ describe('DraftHistoryEntryComponent', () => { it('should show the USFM format option when the project is the latest draft', fakeAsync(() => { const user = 'user-display-name'; - const date = 'formatted-date'; + const date = dateAfterFormattingSupported; const trainingBooks = ['EXO']; const translateBooks = ['GEN']; const trainingDataFiles = ['file01']; @@ -244,8 +249,9 @@ describe('DraftHistoryEntryComponent', () => { when(mockedActivatedProjectService.changes$).thenReturn(of(targetProjectDoc)); const entry = { additionalInfo: { - dateGenerated: new Date().toISOString(), - dateRequested: new Date().toISOString(), + dateGenerated: dateAfterFormattingSupported, + dateRequested: dateAfterFormattingSupported, + dateFinished: dateAfterFormattingSupported, requestedByUserId: 'sf-user-id', translationScriptureRanges: [{ projectId: 'project01', scriptureRange: 'GEN' }] } @@ -337,14 +343,16 @@ describe('DraftHistoryEntryComponent', () => { const projectDoc = getProjectProfileDoc(); when(mockedActivatedProjectService.projectDoc).thenReturn(projectDoc); when(mockedActivatedProjectService.changes$).thenReturn(of(projectDoc)); + when(mockedI18nService.formatDate(anything())).thenReturn('formatted-date'); }); it('should show set draft format UI', fakeAsync(() => { + const date = dateAfterFormattingSupported; component.entry = { id: 'build01', state: BuildStates.Completed, message: 'Completed', - additionalInfo: { dateGenerated: '2025-09-01' } + additionalInfo: { dateGenerated: date, dateFinished: date } } as BuildDto; component.isLatestBuild = true; component.draftIsAvailable = true; @@ -360,7 +368,8 @@ describe('DraftHistoryEntryComponent', () => { state: BuildStates.Completed, message: 'Completed', additionalInfo: { - dateGenerated: '2025-09-01', + dateGenerated: dateAfterFormattingSupported, + dateFinished: dateAfterFormattingSupported, translationScriptureRanges: [{ projectId: 'source01', scriptureRange: 'EXO' }] } } as BuildDto; @@ -377,7 +386,8 @@ describe('DraftHistoryEntryComponent', () => { state: BuildStates.Completed, message: 'Completed', additionalInfo: { - dateGenerated: '2025-09-01', + dateGenerated: dateAfterFormattingSupported, + dateFinished: dateAfterFormattingSupported, translationScriptureRanges: [{ projectId: 'source01', scriptureRange: 'EXO' }] } } as BuildDto; @@ -389,13 +399,38 @@ describe('DraftHistoryEntryComponent', () => { })); it('should hide draft format UI if the draft is not completed', fakeAsync(() => { - component.entry = { id: 'build01', state: BuildStates.Canceled, message: 'Cancelled' } as BuildDto; + component.entry = { + id: 'build01', + state: BuildStates.Canceled, + message: 'Cancelled', + additionalInfo: { dateGenerated: dateAfterFormattingSupported, dateFinished: dateAfterFormattingSupported } + } as BuildDto; component.isLatestBuild = true; component.draftIsAvailable = false; tick(); fixture.detectChanges(); expect(fixture.nativeElement.querySelector('.require-formatting-options')).toBeNull(); })); + + it('should not show the USFM format option for drafts created before the supported date', fakeAsync(() => { + const user = 'user-display-name'; + const date = dateBeforeFormattingSupported; + const trainingBooks = ['EXO']; + const translateBooks = ['GEN']; + const trainingDataFiles = ['file01']; + const entry = getStandardBuildDto({ user, date, trainingBooks, translateBooks, trainingDataFiles }); + + // SUT + component.entry = entry; + component.isLatestBuild = true; + tick(); + fixture.detectChanges(); + + expect(component.scriptureRange).toEqual('GEN'); + expect(component.draftIsAvailable).toBe(true); + expect(fixture.nativeElement.querySelector('.format-usfm')).toBeNull(); + expect(component.formattingOptionsSupported).toBe(false); + })); }); describe('formatDate', () => { @@ -457,8 +492,9 @@ describe('DraftHistoryEntryComponent', () => { id: 'project01' }, additionalInfo: { - dateGenerated: new Date().toISOString(), - dateRequested: new Date().toISOString(), + dateGenerated: new Date(date).toISOString(), + dateFinished: new Date(date).toISOString(), + dateRequested: new Date(date).toISOString(), requestedByUserId: 'sf-user-id', trainingScriptureRanges: trainingBooks.length > 0 ? [{ projectId: 'project02', scriptureRange: trainingBooks.join(';') }] : [], diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-entry/draft-history-entry.component.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-entry/draft-history-entry.component.ts index 1d2afe47d62..33d83ce8f8b 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-entry/draft-history-entry.component.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-entry/draft-history-entry.component.ts @@ -19,6 +19,7 @@ import { BuildStates } from '../../../../machine-api/build-states'; import { RIGHT_TO_LEFT_MARK } from '../../../../shared/utils'; import { DraftDownloadButtonComponent } from '../../draft-download-button/draft-download-button.component'; import { DraftPreviewBooksComponent } from '../../draft-preview-books/draft-preview-books.component'; +import { FORMATTING_OPTIONS_SUPPORTED_DATE } from '../../draft-utils'; import { TrainingDataService } from '../../training-data/training-data.service'; const STATUS_INFO: Record = { @@ -271,6 +272,12 @@ export class DraftHistoryEntryComponent { return this.activatedProjectService.projectDoc?.data?.translateConfig.draftConfig.usfmConfig != null; } + get formattingOptionsSupported(): boolean { + return this.featureFlags.usfmFormat.enabled && this.entry?.additionalInfo?.dateFinished != null + ? new Date(this.entry.additionalInfo.dateFinished) > FORMATTING_OPTIONS_SUPPORTED_DATE + : false; + } + @Input() isLatestBuild: boolean = false; trainingConfigurationOpen = false; diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-utils.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-utils.ts index fe87365ee43..7881d5df669 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-utils.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-utils.ts @@ -3,6 +3,9 @@ import { TranslateSource } from 'realtime-server/lib/esm/scriptureforge/models/t import language_code_mapping from '../../../../../language_code_mapping.json'; import { SelectableProjectWithLanguageCode } from '../../core/paratext.service'; +// Corresponds to Serval 1.11.0 release +export const FORMATTING_OPTIONS_SUPPORTED_DATE: Date = new Date('2025-09-25T00:00:00Z'); + /** Represents draft sources as a set of two {@link TranslateSource} arrays, and one {@link SFProjectProfile} array. */ export interface DraftSourcesAsTranslateSourceArrays { trainingSources: TranslateSource[]; diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/editor-draft/editor-draft.component.html b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/editor-draft/editor-draft.component.html index a4f45494219..9a904618ce5 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/editor-draft/editor-draft.component.html +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/editor-draft/editor-draft.component.html @@ -50,12 +50,12 @@ - } @else { + } @else if (formattingOptionsSupported) { -