diff --git a/src/RealtimeServer/scriptureforge/models/translate-config.ts b/src/RealtimeServer/scriptureforge/models/translate-config.ts index b26c1435b7..45247a0095 100644 --- a/src/RealtimeServer/scriptureforge/models/translate-config.ts +++ b/src/RealtimeServer/scriptureforge/models/translate-config.ts @@ -27,6 +27,14 @@ export interface BaseProject { shortName: string; } +/** + * A per-project scripture range. + */ +export interface ProjectScriptureRange { + projectId: string; + scriptureRange: string; +} + export interface DraftConfig { additionalTrainingData: boolean; additionalTrainingSourceEnabled: boolean; @@ -38,6 +46,7 @@ export interface DraftConfig { lastSelectedTrainingBooks: number[]; lastSelectedTrainingDataFiles: string[]; lastSelectedTrainingScriptureRange?: string; + lastSelectedTrainingScriptureRanges?: ProjectScriptureRange[]; lastSelectedTranslationBooks: number[]; lastSelectedTranslationScriptureRange?: string; servalConfig?: string; diff --git a/src/RealtimeServer/scriptureforge/services/sf-project-migrations.spec.ts b/src/RealtimeServer/scriptureforge/services/sf-project-migrations.spec.ts index 0a5e326834..58f030d50a 100644 --- a/src/RealtimeServer/scriptureforge/services/sf-project-migrations.spec.ts +++ b/src/RealtimeServer/scriptureforge/services/sf-project-migrations.spec.ts @@ -220,36 +220,36 @@ describe('SFProjectMigrations', () => { expect(projectDoc.data.translateConfig.preTranslate).toBe(false); }); }); -}); -describe('version 11', () => { - it('adds biblical terms properties', async () => { - const env = new TestEnvironment(10); - const conn = env.server.connect(); - await createDoc(conn, SF_PROJECTS_COLLECTION, 'project01', {}); - let projectDoc = await fetchDoc(conn, SF_PROJECTS_COLLECTION, 'project01'); - expect(projectDoc.data.biblicalTermsConfig).not.toBeDefined(); + describe('version 11', () => { + it('adds biblical terms properties', async () => { + const env = new TestEnvironment(10); + const conn = env.server.connect(); + await createDoc(conn, SF_PROJECTS_COLLECTION, 'project01', {}); + let projectDoc = await fetchDoc(conn, SF_PROJECTS_COLLECTION, 'project01'); + expect(projectDoc.data.biblicalTermsConfig).not.toBeDefined(); - await env.server.migrateIfNecessary(); + await env.server.migrateIfNecessary(); - projectDoc = await fetchDoc(conn, SF_PROJECTS_COLLECTION, 'project01'); - expect(projectDoc.data.biblicalTermsConfig.biblicalTermsEnabled).toBe(false); - expect(projectDoc.data.biblicalTermsConfig.hasRenderings).toBe(false); + projectDoc = await fetchDoc(conn, SF_PROJECTS_COLLECTION, 'project01'); + expect(projectDoc.data.biblicalTermsConfig.biblicalTermsEnabled).toBe(false); + expect(projectDoc.data.biblicalTermsConfig.hasRenderings).toBe(false); + }); }); -}); -describe('version 12', () => { - it('adds draftConfig to translateConfig', async () => { - const env = new TestEnvironment(11); - const conn = env.server.connect(); - await createDoc(conn, SF_PROJECTS_COLLECTION, 'project01', { translateConfig: {} }); - let projectDoc = await fetchDoc(conn, SF_PROJECTS_COLLECTION, 'project01'); - expect(projectDoc.data.translateConfig.draftConfig).not.toBeDefined(); + describe('version 12', () => { + it('adds draftConfig to translateConfig', async () => { + const env = new TestEnvironment(11); + const conn = env.server.connect(); + await createDoc(conn, SF_PROJECTS_COLLECTION, 'project01', { translateConfig: {} }); + let projectDoc = await fetchDoc(conn, SF_PROJECTS_COLLECTION, 'project01'); + expect(projectDoc.data.translateConfig.draftConfig).not.toBeDefined(); - await env.server.migrateIfNecessary(); + await env.server.migrateIfNecessary(); - projectDoc = await fetchDoc(conn, SF_PROJECTS_COLLECTION, 'project01'); - expect(projectDoc.data.translateConfig.draftConfig).toBeDefined(); + projectDoc = await fetchDoc(conn, SF_PROJECTS_COLLECTION, 'project01'); + expect(projectDoc.data.translateConfig.draftConfig).toBeDefined(); + }); }); describe('version 13', () => { @@ -455,6 +455,28 @@ describe('version 12', () => { expect(projectDoc.data.translateConfig.draftConfig.additionalTrainingSourceEnabled).toBe(false); }); }); + + describe('version 21', () => { + it('copies selected training and translation books to scripture ranges', async () => { + const env = new TestEnvironment(20); + const conn = env.server.connect(); + + await createDoc(conn, SF_PROJECTS_COLLECTION, 'project01', { + translateConfig: { draftConfig: { lastSelectedTrainingBooks: [1, 2, 3], lastSelectedTranslationBooks: [4, 5] } } + }); + let projectDoc = await fetchDoc(conn, SF_PROJECTS_COLLECTION, 'project01'); + expect(projectDoc.data.translateConfig.draftConfig.lastSelectedTrainingBooks).toEqual([1, 2, 3]); + expect(projectDoc.data.translateConfig.draftConfig.lastSelectedTranslationBooks).toEqual([4, 5]); + + await env.server.migrateIfNecessary(); + + projectDoc = await fetchDoc(conn, SF_PROJECTS_COLLECTION, 'project01'); + expect(projectDoc.data.translateConfig.draftConfig.lastSelectedTrainingBooks).toEqual([1, 2, 3]); + expect(projectDoc.data.translateConfig.draftConfig.lastSelectedTranslationBooks).toEqual([4, 5]); + expect(projectDoc.data.translateConfig.draftConfig.lastSelectedTrainingScriptureRange).toEqual('GEN;EXO;LEV'); + expect(projectDoc.data.translateConfig.draftConfig.lastSelectedTranslationScriptureRange).toEqual('NUM;DEU'); + }); + }); }); class TestEnvironment { diff --git a/src/RealtimeServer/scriptureforge/services/sf-project-migrations.ts b/src/RealtimeServer/scriptureforge/services/sf-project-migrations.ts index 00f0b502a2..63971c11d5 100644 --- a/src/RealtimeServer/scriptureforge/services/sf-project-migrations.ts +++ b/src/RealtimeServer/scriptureforge/services/sf-project-migrations.ts @@ -1,3 +1,4 @@ +import { Canon } from '@sillsdev/scripture'; import { Doc, Op } from 'sharedb/lib/client'; import { DocMigration, MigrationConstructor } from '../../common/migration'; import { submitMigrationOp } from '../../common/realtime-server'; @@ -343,6 +344,38 @@ class SFProjectMigration20 extends DocMigration { } } +class SFProjectMigration21 extends DocMigration { + static readonly VERSION = 21; + + async migrateDoc(doc: Doc): Promise { + const ops: Op[] = []; + if (doc.data.translateConfig.draftConfig.lastSelectedTrainingScriptureRange == null) { + const trainingRangeFromBooks: string[] = doc.data.translateConfig.draftConfig.lastSelectedTrainingBooks.map( + (b: number) => Canon.bookNumberToId(b) + ); + if (trainingRangeFromBooks.length > 0) { + ops.push({ + p: ['translateConfig', 'draftConfig', 'lastSelectedTrainingScriptureRange'], + oi: trainingRangeFromBooks.join(';') + }); + } + } + if (doc.data.translateConfig.draftConfig.lastSelectedTranslationScriptureRange == null) { + const translationRangeFromBooks: string[] = doc.data.translateConfig.draftConfig.lastSelectedTranslationBooks.map( + (b: number) => Canon.bookNumberToId(b) + ); + if (translationRangeFromBooks.length > 0) { + ops.push({ + p: ['translateConfig', 'draftConfig', 'lastSelectedTranslationScriptureRange'], + oi: translationRangeFromBooks.join(';') + }); + } + } + + await submitMigrationOp(SFProjectMigration20.VERSION, doc, ops); + } +} + export const SF_PROJECT_MIGRATIONS: MigrationConstructor[] = [ SFProjectMigration1, SFProjectMigration2, @@ -363,5 +396,6 @@ export const SF_PROJECT_MIGRATIONS: MigrationConstructor[] = [ SFProjectMigration17, SFProjectMigration18, SFProjectMigration19, - SFProjectMigration20 + SFProjectMigration20, + SFProjectMigration21 ]; diff --git a/src/RealtimeServer/scriptureforge/services/sf-project-service.ts b/src/RealtimeServer/scriptureforge/services/sf-project-service.ts index 766c721dc2..a11164bff0 100644 --- a/src/RealtimeServer/scriptureforge/services/sf-project-service.ts +++ b/src/RealtimeServer/scriptureforge/services/sf-project-service.ts @@ -268,6 +268,21 @@ export class SFProjectService extends ProjectService { lastSelectedTrainingScriptureRange: { bsonType: 'string' }, + lastSelectedTrainingScriptureRanges: { + bsonType: 'array', + items: { + bsonType: 'object', + properties: { + projectId: { + bsonType: 'string' + }, + scriptureRange: { + bsonType: 'string' + } + }, + additionalProperties: false + } + }, lastSelectedTranslationBooks: { bsonType: 'array', items: { diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/serval-project.component.spec.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/serval-project.component.spec.ts index 37d026bacf..2756fd2f28 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/serval-project.component.spec.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/serval-project.component.spec.ts @@ -222,7 +222,9 @@ describe('ServalProjectComponent', () => { shortName: 'P4' }, lastSelectedTrainingBooks: preTranslate ? [1, 2] : [], - lastSelectedTranslationBooks: preTranslate ? [3, 4] : [] + lastSelectedTranslationBooks: preTranslate ? [3, 4] : [], + lastSelectedTrainingScriptureRange: preTranslate ? 'GEN;EXO' : undefined, + lastSelectedTranslationScriptureRange: preTranslate ? 'LEV;NUM' : undefined }, preTranslate, source: { diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/serval-project.component.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/serval-project.component.ts index 2d08d58c1a..3030cdab7b 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/serval-project.component.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/serval-project.component.ts @@ -16,7 +16,7 @@ import { SFProjectService } from '../core/sf-project.service'; import { BuildDto } from '../machine-api/build-dto'; import { NoticeComponent } from '../shared/notice/notice.component'; import { SharedModule } from '../shared/shared.module'; -import { projectLabel } from '../shared/utils'; +import { booksFromScriptureRange, projectLabel } from '../shared/utils'; import { DraftZipProgress } from '../translate/draft-generation/draft-generation'; import { DraftGenerationService } from '../translate/draft-generation/draft-generation.service'; import { DraftInformationComponent } from '../translate/draft-generation/draft-information/draft-information.component'; @@ -142,13 +142,13 @@ export class ServalProjectComponent extends DataLoadingComponent implements OnIn this.rows = rows; // Setup the books - this.trainingBooks = project.translateConfig.draftConfig.lastSelectedTrainingBooks.map(bookNum => - Canon.bookNumberToEnglishName(bookNum) - ); + this.trainingBooks = booksFromScriptureRange( + project.translateConfig.draftConfig.lastSelectedTrainingScriptureRange ?? '' + ).map(bookNum => Canon.bookNumberToEnglishName(bookNum)); this.trainingFiles = project.translateConfig.draftConfig.lastSelectedTrainingDataFiles; - this.translationBooks = project.translateConfig.draftConfig.lastSelectedTranslationBooks.map(bookNum => - Canon.bookNumberToEnglishName(bookNum) - ); + this.translationBooks = booksFromScriptureRange( + project.translateConfig.draftConfig.lastSelectedTranslationScriptureRange ?? '' + ).map(bookNum => Canon.bookNumberToEnglishName(bookNum)); this.draftConfig = project.translateConfig.draftConfig; this.draftJob$ = SFProjectService.hasDraft(project) ? this.getDraftJob(projectDoc.id) : of(undefined); diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/draft-generation-steps.component.spec.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/draft-generation-steps.component.spec.ts index d2c0450421..025ab30af1 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/draft-generation-steps.component.spec.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/draft-generation-steps.component.spec.ts @@ -310,7 +310,7 @@ describe('DraftGenerationStepsComponent', () => { expect(component.done.emit).toHaveBeenCalledWith({ trainingDataFiles, trainingScriptureRanges: [{ projectId: 'sourceProject', scriptureRange: 'LEV' }], - translationScriptureRanges: [{ projectId: 'sourceProject', scriptureRange: 'GEN;EXO' }], + translationScriptureRange: 'GEN;EXO', fastTraining: false } as DraftGenerationStepsResult); expect(component.isStepsCompleted).toBe(true); @@ -482,7 +482,7 @@ describe('DraftGenerationStepsComponent', () => { { projectId: 'sourceProject', scriptureRange: 'LEV' }, { projectId: 'sourceProject2', scriptureRange: 'LEV' } ], - translationScriptureRanges: [{ projectId: 'sourceProject', scriptureRange: 'GEN;EXO' }], + translationScriptureRange: 'GEN;EXO', fastTraining: false } as DraftGenerationStepsResult); expect(component.isStepsCompleted).toBe(true); @@ -562,7 +562,7 @@ describe('DraftGenerationStepsComponent', () => { expect(component.done.emit).toHaveBeenCalledWith({ trainingDataFiles, trainingScriptureRanges: [{ projectId: 'sourceProject', scriptureRange: 'GEN;EXO' }], - translationScriptureRanges: [{ projectId: 'sourceProject', scriptureRange: 'LEV;NUM' }], + translationScriptureRange: 'LEV;NUM', fastTraining: true } as DraftGenerationStepsResult); expect(generateDraftButton['disabled']).toBe(true); @@ -578,7 +578,7 @@ describe('DraftGenerationStepsComponent', () => { draftConfig: { lastSelectedTrainingDataFiles: [], lastSelectedTranslationScriptureRange: 'GEN;EXO', - lastSelectedTrainingScriptureRange: 'LEV' + lastSelectedTrainingScriptureRanges: [{ projectId: 'test', scriptureRange: 'LEV' }] } } }) diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/draft-generation-steps.component.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/draft-generation-steps.component.ts index 419031a300..5d4204dc45 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/draft-generation-steps.component.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/draft-generation-steps.component.ts @@ -4,6 +4,7 @@ import { TranslocoModule } from '@ngneat/transloco'; import { Canon } from '@sillsdev/scripture'; import { TranslocoMarkupModule } from 'ngx-transloco-markup'; import { TrainingData } from 'realtime-server/lib/esm/scriptureforge/models/training-data'; +import { ProjectScriptureRange } from 'realtime-server/lib/esm/scriptureforge/models/translate-config'; import { Subscription, merge } from 'rxjs'; import { filter, tap } from 'rxjs/operators'; import { ActivatedProjectService } from 'xforge-common/activated-project.service'; @@ -19,7 +20,6 @@ import { SharedModule } from '../../../shared/shared.module'; import { booksFromScriptureRange, projectLabel } from '../../../shared/utils'; import { NllbLanguageService } from '../../nllb-language.service'; import { ConfirmSourcesComponent } from '../confirm-sources/confirm-sources.component'; -import { ProjectScriptureRange } from '../draft-generation'; import { DraftSource, DraftSourceIds, DraftSourcesService } from '../draft-sources.service'; import { TrainingDataMultiSelectComponent } from '../training-data/training-data-multi-select.component'; import { TrainingDataUploadDialogComponent } from '../training-data/training-data-upload-dialog.component'; @@ -30,7 +30,7 @@ export interface DraftGenerationStepsResult { trainingScriptureRange?: string; trainingScriptureRanges: ProjectScriptureRange[]; translationScriptureRange?: string; - translationScriptureRanges: ProjectScriptureRange[]; + translationScriptureRanges?: ProjectScriptureRange[]; fastTraining: boolean; } @@ -322,14 +322,10 @@ export class DraftGenerationStepsComponent extends SubscriptionDisposable implem ) ); } - const translationScriptureRange: ProjectScriptureRange = this.convertToScriptureRange( - this.draftSourceProjectIds!.draftingSourceId, - this.userSelectedTranslateBooks - ); this.done.emit({ trainingScriptureRanges, trainingDataFiles: this.selectedTrainingDataIds, - translationScriptureRanges: [translationScriptureRange], + translationScriptureRange: this.userSelectedTranslateBooks.map(b => Canon.bookNumberToId(b)).join(';'), fastTraining: this.fastTraining }); } @@ -397,8 +393,18 @@ export class DraftGenerationStepsComponent extends SubscriptionDisposable implem private setInitialTrainingBooks(availableBooks: number[]): void { // Get the previously selected training books from the target project - const previousTrainingRange: string = - this.activatedProject.projectDoc?.data?.translateConfig.draftConfig.lastSelectedTrainingScriptureRange ?? ''; + const trainingSourceId = + this.draftSourceProjectIds?.trainingAlternateSourceId ?? this.draftSourceProjectIds?.trainingSourceId; + let previousTrainingRange: string = + this.activatedProject.projectDoc?.data?.translateConfig.draftConfig.lastSelectedTrainingScriptureRanges?.find( + r => r.projectId === trainingSourceId + )?.scriptureRange ?? ''; + const trainingScriptureRange = + this.activatedProject.projectDoc?.data?.translateConfig.draftConfig.lastSelectedTrainingScriptureRange; + if (previousTrainingRange === '' && trainingScriptureRange != null) { + previousTrainingRange = + this.activatedProject.projectDoc?.data?.translateConfig.draftConfig.lastSelectedTrainingScriptureRange ?? ''; + } const previousBooks: Set = new Set(booksFromScriptureRange(previousTrainingRange)); // The intersection is all of the available books in the source project that match the target's previous books diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation.component.spec.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation.component.spec.ts index 00aebd74b9..3ebaf48c78 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation.component.spec.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation.component.spec.ts @@ -185,7 +185,9 @@ describe('DraftGenerationComponent', () => { }, draftConfig: { lastSelectedTrainingBooks: preTranslate ? [1] : [], - lastSelectedTranslationBooks: preTranslate ? [2] : [] + lastSelectedTranslationBooks: preTranslate ? [2] : [], + lastSelectedTrainingScriptureRange: preTranslate ? 'GEN' : undefined, + lastSelectedTranslationScriptureRange: preTranslate ? 'EXO' : undefined } }, texts: [ @@ -2359,7 +2361,7 @@ describe('DraftGenerationComponent', () => { // Update the has draft flag for the project projectDoc.data!.texts[0].chapters[0].hasDraft = true; - projectDoc.data!.translateConfig.draftConfig.lastSelectedTranslationBooks = [1]; + projectDoc.data!.translateConfig.draftConfig.lastSelectedTranslationScriptureRange = 'GEN'; projectSubject.next(projectDoc); buildSubject.next({ ...buildDto, state: BuildStates.Completed }); diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation.component.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation.component.ts index be52ef6559..e99fbe959f 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation.component.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation.component.ts @@ -446,7 +446,7 @@ export class DraftGenerationComponent extends DataLoadingComponent implements On trainingScriptureRange: result.trainingScriptureRange, trainingScriptureRanges: result.trainingScriptureRanges, translationScriptureRange: result.translationScriptureRange, - translationScriptureRanges: result.trainingScriptureRanges, + translationScriptureRanges: result.translationScriptureRanges, fastTraining: result.fastTraining }); } @@ -568,7 +568,7 @@ export class DraftGenerationComponent extends DataLoadingComponent implements On private hasStartedBuild(projectDoc: SFProjectProfileDoc): boolean { return ( projectDoc.data?.translateConfig.preTranslate === true && - projectDoc.data?.translateConfig.draftConfig.lastSelectedTranslationBooks.length > 0 + projectDoc.data?.translateConfig.draftConfig.lastSelectedTranslationScriptureRange != null ); } } diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation.ts index b876724e71..2d501c20b9 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation.ts @@ -1,4 +1,5 @@ import { InjectionToken } from '@angular/core'; +import { ProjectScriptureRange } from 'realtime-server/lib/esm/scriptureforge/models/translate-config'; import { BuildStates } from '../../machine-api/build-states'; /** @@ -14,14 +15,6 @@ export interface BuildConfig { fastTraining: boolean; } -/** - * A per-project scripture range. - */ -export interface ProjectScriptureRange { - projectId: string; - scriptureRange: string; -} - /** * Dictionary of 'segmentRef -> segment text'. */ diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/tabs/editor-tab-menu.service.spec.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/tabs/editor-tab-menu.service.spec.ts index 63b2347977..49afc448e9 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/tabs/editor-tab-menu.service.spec.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/tabs/editor-tab-menu.service.spec.ts @@ -266,7 +266,7 @@ class TestEnvironment { ], translateConfig: { preTranslate: true, - draftConfig: { lastSelectedTranslationBooks: [40], lastSelectedTrainingBooks: [41] } + draftConfig: { lastSelectedTranslationScriptureRange: 'MAT', lastSelectedTrainingScriptureRange: 'MRK' } }, userRoles: TestEnvironment.rolesByUser, biblicalTermsConfig: { biblicalTermsEnabled: true }