diff --git a/mongodb/Projects/FindProjectsUsingProjectAsSource.mongodb b/mongodb/Projects/FindProjectsUsingProjectAsSource.mongodb index a7399e010e7..557e07a2842 100644 --- a/mongodb/Projects/FindProjectsUsingProjectAsSource.mongodb +++ b/mongodb/Projects/FindProjectsUsingProjectAsSource.mongodb @@ -1,20 +1,22 @@ -use('xforge') +use("xforge"); // Query to find what project is using a project as a source (and therefore preventing the specified project from being // deleted). This is a re-implementation of the logic in SFProjectService.IsSourceProject -const shortName = ''; +const shortName = ""; -const id = db.sf_projects.findOne({ shortName })._id +const id = db.sf_projects.findOne({ shortName })._id; db.sf_projects.find({ - $or: [{ - 'translateConfig.source.projectRef': id - }, { - 'translateConfig.draftConfig.alternateSource.projectRef': id - }, { - 'translateConfig.draftConfig.alternateTrainingSource.projectRef': id - }, { - 'translateConfig.draftConfig.additionalTrainingSource.projectRef': id - }] -}) + $or: [ + { + "translateConfig.source.projectRef": id + }, + { + "translateConfig.draftConfig.draftingSources.projectRef": id + }, + { + "translateConfig.draftConfig.trainingSources.projectRef": id + } + ] +}); diff --git a/src/RealtimeServer/scriptureforge/models/sf-project-test-data.ts b/src/RealtimeServer/scriptureforge/models/sf-project-test-data.ts index a1111596f05..e9139110262 100644 --- a/src/RealtimeServer/scriptureforge/models/sf-project-test-data.ts +++ b/src/RealtimeServer/scriptureforge/models/sf-project-test-data.ts @@ -19,9 +19,8 @@ function testProjectProfile(ordinal: number): SFProjectProfile { preTranslate: false, defaultNoteTagId: 1, draftConfig: { - additionalTrainingSourceEnabled: false, - alternateSourceEnabled: false, - alternateTrainingSourceEnabled: false, + draftingSources: [], + trainingSources: [], lastSelectedTrainingDataFiles: [] } }, diff --git a/src/RealtimeServer/scriptureforge/models/sf-project.ts b/src/RealtimeServer/scriptureforge/models/sf-project.ts index 2a3f24ea066..ce2991d96a4 100644 --- a/src/RealtimeServer/scriptureforge/models/sf-project.ts +++ b/src/RealtimeServer/scriptureforge/models/sf-project.ts @@ -22,9 +22,8 @@ export const SF_PROJECT_INDEX_PATHS: (string | [string, CreateIndexesOptions])[] obj().pathStr(p => p.shortName), // Indexes for SFProjectService.IsSourceProject() in .NET [obj().pathStr(p => p.translateConfig.source!.projectRef), { sparse: true }], - [obj().pathStr(p => p.translateConfig.draftConfig.additionalTrainingSource!.projectRef), { sparse: true }], - [obj().pathStr(p => p.translateConfig.draftConfig.alternateSource!.projectRef), { sparse: true }], - [obj().pathStr(p => p.translateConfig.draftConfig.alternateTrainingSource!.projectRef), { sparse: true }] + ['translateConfig.draftConfig.draftingSources.projectRef', { sparse: true }], + ['translateConfig.draftConfig.trainingSources.projectRef', { sparse: true }] ]; /** Length of id for a DBL resource. */ diff --git a/src/RealtimeServer/scriptureforge/models/translate-config.ts b/src/RealtimeServer/scriptureforge/models/translate-config.ts index f503b8cf0dc..b3188123564 100644 --- a/src/RealtimeServer/scriptureforge/models/translate-config.ts +++ b/src/RealtimeServer/scriptureforge/models/translate-config.ts @@ -59,12 +59,8 @@ export interface DraftUsfmConfig { } export interface DraftConfig { - additionalTrainingSourceEnabled: boolean; - additionalTrainingSource?: TranslateSource; - alternateSourceEnabled: boolean; - alternateSource?: TranslateSource; - alternateTrainingSourceEnabled: boolean; - alternateTrainingSource?: TranslateSource; + draftingSources: TranslateSource[]; + trainingSources: TranslateSource[]; lastSelectedTrainingDataFiles: string[]; lastSelectedTrainingScriptureRanges?: ProjectScriptureRange[]; lastSelectedTranslationScriptureRanges?: ProjectScriptureRange[]; diff --git a/src/RealtimeServer/scriptureforge/services/sf-project-migrations.spec.ts b/src/RealtimeServer/scriptureforge/services/sf-project-migrations.spec.ts index 283616fe8a6..f3c8c6728c3 100644 --- a/src/RealtimeServer/scriptureforge/services/sf-project-migrations.spec.ts +++ b/src/RealtimeServer/scriptureforge/services/sf-project-migrations.spec.ts @@ -905,6 +905,153 @@ describe('SFProjectMigrations', () => { ]); }); }); + + describe('version 27', () => { + it('adds empty arrays if preTranslate is false', async () => { + const env = new TestEnvironment(26); + const conn = env.server.connect(); + await createDoc(conn, SF_PROJECTS_COLLECTION, 'project01', { + translateConfig: { preTranslate: false, draftConfig: {} } + }); + let projectDoc = await fetchDoc(conn, SF_PROJECTS_COLLECTION, 'project01'); + expect(projectDoc.data.translateConfig.draftConfig.draftingSources).toBeUndefined(); + expect(projectDoc.data.translateConfig.draftConfig.trainingSources).toBeUndefined(); + + await env.server.migrateIfNecessary(); + + projectDoc = await fetchDoc(conn, SF_PROJECTS_COLLECTION, 'project01'); + expect(projectDoc.data.translateConfig.draftConfig.draftingSources).toEqual([]); + expect(projectDoc.data.translateConfig.draftConfig.trainingSources).toEqual([]); + }); + + it('adds empty arrays if preTranslate is true but sources were disabled', async () => { + const env = new TestEnvironment(26); + const conn = env.server.connect(); + await createDoc(conn, SF_PROJECTS_COLLECTION, 'project01', { + translateConfig: { + preTranslate: true, + draftConfig: { + alternateTrainingSource: { projectRef: 'project02' }, + alternateTrainingSourceEnabled: false, + alternateSource: { projectRef: 'project03' }, + alternateSourceEnabled: false, + additionalTrainingSource: { projectRef: 'project04' }, + additionalTrainingSourceEnabled: false + } + } + }); + let projectDoc = await fetchDoc(conn, SF_PROJECTS_COLLECTION, 'project01'); + expect(projectDoc.data.translateConfig.draftConfig.draftingSources).toBeUndefined(); + expect(projectDoc.data.translateConfig.draftConfig.trainingSources).toBeUndefined(); + + await env.server.migrateIfNecessary(); + + projectDoc = await fetchDoc(conn, SF_PROJECTS_COLLECTION, 'project01'); + expect(projectDoc.data.translateConfig.draftConfig.draftingSources).toEqual([]); + expect(projectDoc.data.translateConfig.draftConfig.trainingSources).toEqual([]); + expect(projectDoc.data.translateConfig.draftConfig.alternateTrainingSource).toBeUndefined(); + expect(projectDoc.data.translateConfig.draftConfig.alternateTrainingSourceEnabled).toBeUndefined(); + expect(projectDoc.data.translateConfig.draftConfig.alternateSource).toBeUndefined(); + expect(projectDoc.data.translateConfig.draftConfig.alternateSourceEnabled).toBeUndefined(); + expect(projectDoc.data.translateConfig.draftConfig.additionalTrainingSource).toBeUndefined(); + expect(projectDoc.data.translateConfig.draftConfig.additionalTrainingSourceEnabled).toBeUndefined(); + }); + + it('adds empty arrays if preTranslate is true and sources are enabled but undefined', async () => { + const env = new TestEnvironment(26); + const conn = env.server.connect(); + await createDoc(conn, SF_PROJECTS_COLLECTION, 'project01', { + translateConfig: { + preTranslate: true, + draftConfig: { + alternateTrainingSourceEnabled: true, + alternateSourceEnabled: true, + additionalTrainingSourceEnabled: true + } + } + }); + let projectDoc = await fetchDoc(conn, SF_PROJECTS_COLLECTION, 'project01'); + expect(projectDoc.data.translateConfig.draftConfig.draftingSources).toBeUndefined(); + expect(projectDoc.data.translateConfig.draftConfig.trainingSources).toBeUndefined(); + + await env.server.migrateIfNecessary(); + + projectDoc = await fetchDoc(conn, SF_PROJECTS_COLLECTION, 'project01'); + expect(projectDoc.data.translateConfig.draftConfig.draftingSources).toEqual([]); + expect(projectDoc.data.translateConfig.draftConfig.trainingSources).toEqual([]); + }); + + it('adds source to both arrays if preTranslate is true but sources were disabled', async () => { + const env = new TestEnvironment(26); + const conn = env.server.connect(); + await createDoc(conn, SF_PROJECTS_COLLECTION, 'project01', { + translateConfig: { + preTranslate: true, + draftConfig: { + alternateTrainingSource: { projectRef: 'project02' }, + alternateTrainingSourceEnabled: false, + alternateSource: { projectRef: 'project03' }, + alternateSourceEnabled: false, + additionalTrainingSource: { projectRef: 'project04' }, + additionalTrainingSourceEnabled: false + }, + source: { projectRef: 'project05' } + } + }); + let projectDoc = await fetchDoc(conn, SF_PROJECTS_COLLECTION, 'project01'); + expect(projectDoc.data.translateConfig.draftConfig.draftingSources).toBeUndefined(); + expect(projectDoc.data.translateConfig.draftConfig.trainingSources).toBeUndefined(); + + await env.server.migrateIfNecessary(); + + projectDoc = await fetchDoc(conn, SF_PROJECTS_COLLECTION, 'project01'); + expect(projectDoc.data.translateConfig.draftConfig.draftingSources).toEqual([{ projectRef: 'project05' }]); + expect(projectDoc.data.translateConfig.draftConfig.trainingSources).toEqual([{ projectRef: 'project05' }]); + expect(projectDoc.data.translateConfig.draftConfig.alternateTrainingSource).toBeUndefined(); + expect(projectDoc.data.translateConfig.draftConfig.alternateTrainingSourceEnabled).toBeUndefined(); + expect(projectDoc.data.translateConfig.draftConfig.alternateSource).toBeUndefined(); + expect(projectDoc.data.translateConfig.draftConfig.alternateSourceEnabled).toBeUndefined(); + expect(projectDoc.data.translateConfig.draftConfig.additionalTrainingSource).toBeUndefined(); + expect(projectDoc.data.translateConfig.draftConfig.additionalTrainingSourceEnabled).toBeUndefined(); + }); + + it('adds sources to the appropriate arrays if preTranslate is true and sources are configured', async () => { + const env = new TestEnvironment(26); + const conn = env.server.connect(); + await createDoc(conn, SF_PROJECTS_COLLECTION, 'project01', { + translateConfig: { + preTranslate: true, + draftConfig: { + alternateTrainingSource: { projectRef: 'project02' }, + alternateTrainingSourceEnabled: true, + alternateSource: { projectRef: 'project03' }, + alternateSourceEnabled: true, + additionalTrainingSource: { projectRef: 'project04' }, + additionalTrainingSourceEnabled: true + }, + source: { projectRef: 'project05' } + } + }); + let projectDoc = await fetchDoc(conn, SF_PROJECTS_COLLECTION, 'project01'); + expect(projectDoc.data.translateConfig.draftConfig.draftingSources).toBeUndefined(); + expect(projectDoc.data.translateConfig.draftConfig.trainingSources).toBeUndefined(); + + await env.server.migrateIfNecessary(); + + projectDoc = await fetchDoc(conn, SF_PROJECTS_COLLECTION, 'project01'); + expect(projectDoc.data.translateConfig.draftConfig.draftingSources).toEqual([{ projectRef: 'project03' }]); + expect(projectDoc.data.translateConfig.draftConfig.trainingSources).toEqual([ + { projectRef: 'project02' }, + { projectRef: 'project04' } + ]); + expect(projectDoc.data.translateConfig.draftConfig.alternateTrainingSource).toBeUndefined(); + expect(projectDoc.data.translateConfig.draftConfig.alternateTrainingSourceEnabled).toBeUndefined(); + expect(projectDoc.data.translateConfig.draftConfig.alternateSource).toBeUndefined(); + expect(projectDoc.data.translateConfig.draftConfig.alternateSourceEnabled).toBeUndefined(); + expect(projectDoc.data.translateConfig.draftConfig.additionalTrainingSource).toBeUndefined(); + expect(projectDoc.data.translateConfig.draftConfig.additionalTrainingSourceEnabled).toBeUndefined(); + }); + }); }); class TestEnvironment { diff --git a/src/RealtimeServer/scriptureforge/services/sf-project-migrations.ts b/src/RealtimeServer/scriptureforge/services/sf-project-migrations.ts index 1f55d47020c..68d543ca2e1 100644 --- a/src/RealtimeServer/scriptureforge/services/sf-project-migrations.ts +++ b/src/RealtimeServer/scriptureforge/services/sf-project-migrations.ts @@ -7,7 +7,7 @@ import { NoteTag } from '../models/note-tag'; import { SF_PROJECT_RIGHTS, SFProjectDomain } from '../models/sf-project-rights'; import { SFProjectRole } from '../models/sf-project-role'; import { TextInfoPermission } from '../models/text-info-permission'; -import { TranslateShareLevel } from '../models/translate-config'; +import { TranslateShareLevel, TranslateSource } from '../models/translate-config'; class SFProjectMigration1 extends DocMigration { static readonly VERSION = 1; @@ -553,6 +553,83 @@ class SFProjectMigration26 extends DocMigration { } } +class SFProjectMigration27 extends DocMigration { + static readonly VERSION = 27; + + async migrateDoc(doc: Doc): Promise { + const ops: Op[] = []; + const draftingSources: TranslateSource[] = []; + const trainingSources: TranslateSource[] = []; + + // Migrate the old values to the new structure + if (doc.data.translateConfig.preTranslate === true) { + const translateConfig = doc.data.translateConfig; + const draftConfig = translateConfig.draftConfig; + if (draftConfig.alternateTrainingSourceEnabled && draftConfig.alternateTrainingSource != null) { + trainingSources.push(draftConfig.alternateTrainingSource); + } else if (translateConfig.source != null) { + trainingSources.push(translateConfig.source); + } + if (draftConfig.additionalTrainingSourceEnabled && draftConfig.additionalTrainingSource != null) { + trainingSources.push(draftConfig.additionalTrainingSource); + } + if (draftConfig.alternateSourceEnabled && draftConfig.alternateSource != null) { + draftingSources.push(draftConfig.alternateSource); + } else if (translateConfig.source != null) { + draftingSources.push(translateConfig.source); + } + } + + // Create the new structure + if (doc.data.translateConfig.draftConfig.draftingSources == null) { + ops.push({ p: ['translateConfig', 'draftConfig', 'draftingSources'], oi: draftingSources }); + } + if (doc.data.translateConfig.draftConfig.trainingSources == null) { + ops.push({ p: ['translateConfig', 'draftConfig', 'trainingSources'], oi: trainingSources }); + } + + // Remove the old values + if (doc.data.translateConfig.draftConfig.alternateSourceEnabled != null) { + ops.push({ + p: ['translateConfig', 'draftConfig', 'alternateSourceEnabled'], + od: doc.data.translateConfig.draftConfig.alternateSourceEnabled + }); + } + if (doc.data.translateConfig.draftConfig.alternateSource != null) { + ops.push({ + p: ['translateConfig', 'draftConfig', 'alternateSource'], + od: doc.data.translateConfig.draftConfig.alternateSource + }); + } + if (doc.data.translateConfig.draftConfig.alternateTrainingSourceEnabled != null) { + ops.push({ + p: ['translateConfig', 'draftConfig', 'alternateTrainingSourceEnabled'], + od: doc.data.translateConfig.draftConfig.alternateTrainingSourceEnabled + }); + } + if (doc.data.translateConfig.draftConfig.alternateTrainingSource != null) { + ops.push({ + p: ['translateConfig', 'draftConfig', 'alternateTrainingSource'], + od: doc.data.translateConfig.draftConfig.alternateTrainingSource + }); + } + if (doc.data.translateConfig.draftConfig.additionalTrainingSourceEnabled != null) { + ops.push({ + p: ['translateConfig', 'draftConfig', 'additionalTrainingSourceEnabled'], + od: doc.data.translateConfig.draftConfig.additionalTrainingSourceEnabled + }); + } + if (doc.data.translateConfig.draftConfig.additionalTrainingSource != null) { + ops.push({ + p: ['translateConfig', 'draftConfig', 'additionalTrainingSource'], + od: doc.data.translateConfig.draftConfig.additionalTrainingSource + }); + } + + await submitMigrationOp(SFProjectMigration27.VERSION, doc, ops); + } +} + export const SF_PROJECT_MIGRATIONS: MigrationConstructor[] = monotonicallyIncreasingMigrationList([ SFProjectMigration1, SFProjectMigration2, @@ -579,5 +656,6 @@ export const SF_PROJECT_MIGRATIONS: MigrationConstructor[] = monotonicallyIncrea SFProjectMigration23, SFProjectMigration24, SFProjectMigration25, - SFProjectMigration26 + SFProjectMigration26, + SFProjectMigration27 ]); diff --git a/src/RealtimeServer/scriptureforge/services/sf-project-service.ts b/src/RealtimeServer/scriptureforge/services/sf-project-service.ts index 5c193b4f7af..cd2731daa63 100644 --- a/src/RealtimeServer/scriptureforge/services/sf-project-service.ts +++ b/src/RealtimeServer/scriptureforge/services/sf-project-service.ts @@ -130,125 +130,85 @@ export class SFProjectService extends ProjectService { draftConfig: { bsonType: 'object', properties: { - additionalTrainingSourceEnabled: { - bsonType: 'bool' - }, - additionalTrainingSource: { - bsonType: 'object', - properties: { - paratextId: { - bsonType: 'string' - }, - projectRef: { - bsonType: 'string', - pattern: '^[0-9a-f]+$' - }, - name: { - bsonType: 'string' - }, - shortName: { - bsonType: 'string' - }, - writingSystem: { - bsonType: 'object', - properties: { - region: { - bsonType: 'string' - }, - script: { - bsonType: 'string' - }, - tag: { - bsonType: 'string' - } + draftingSources: { + bsonType: 'array', + items: { + bsonType: 'object', + properties: { + paratextId: { + bsonType: 'string' }, - additionalProperties: false - }, - isRightToLeft: { - bsonType: 'bool' - } - }, - additionalProperties: false - }, - alternateSourceEnabled: { - bsonType: 'bool' - }, - alternateSource: { - bsonType: 'object', - properties: { - paratextId: { - bsonType: 'string' - }, - projectRef: { - bsonType: 'string', - pattern: '^[0-9a-f]+$' - }, - name: { - bsonType: 'string' - }, - shortName: { - bsonType: 'string' - }, - writingSystem: { - bsonType: 'object', - properties: { - region: { - bsonType: 'string' - }, - script: { - bsonType: 'string' + projectRef: { + bsonType: 'string', + pattern: '^[0-9a-f]+$' + }, + name: { + bsonType: 'string' + }, + shortName: { + bsonType: 'string' + }, + writingSystem: { + bsonType: 'object', + properties: { + region: { + bsonType: 'string' + }, + script: { + bsonType: 'string' + }, + tag: { + bsonType: 'string' + } }, - tag: { - bsonType: 'string' - } + additionalProperties: false }, - additionalProperties: false + isRightToLeft: { + bsonType: 'bool' + } }, - isRightToLeft: { - bsonType: 'bool' - } - }, - additionalProperties: false - }, - alternateTrainingSourceEnabled: { - bsonType: 'bool' + additionalProperties: false + } }, - alternateTrainingSource: { - bsonType: 'object', - properties: { - paratextId: { - bsonType: 'string' - }, - projectRef: { - bsonType: 'string', - pattern: '^[0-9a-f]+$' - }, - name: { - bsonType: 'string' - }, - shortName: { - bsonType: 'string' - }, - writingSystem: { - bsonType: 'object', - properties: { - region: { - bsonType: 'string' - }, - script: { - bsonType: 'string' + trainingSources: { + bsonType: 'array', + items: { + bsonType: 'object', + properties: { + paratextId: { + bsonType: 'string' + }, + projectRef: { + bsonType: 'string', + pattern: '^[0-9a-f]+$' + }, + name: { + bsonType: 'string' + }, + shortName: { + bsonType: 'string' + }, + writingSystem: { + bsonType: 'object', + properties: { + region: { + bsonType: 'string' + }, + script: { + bsonType: 'string' + }, + tag: { + bsonType: 'string' + } }, - tag: { - bsonType: 'string' - } + additionalProperties: false }, - additionalProperties: false + isRightToLeft: { + bsonType: 'bool' + } }, - isRightToLeft: { - bsonType: 'bool' - } - }, - additionalProperties: false + additionalProperties: false + } }, lastSelectedTrainingDataFiles: { bsonType: 'array', diff --git a/src/SIL.XForge.Scripture/ClientApp/e2e/workflows/smoke-tests.mts b/src/SIL.XForge.Scripture/ClientApp/e2e/workflows/smoke-tests.mts index bedd02c7ec5..ec9a0f52820 100644 --- a/src/SIL.XForge.Scripture/ClientApp/e2e/workflows/smoke-tests.mts +++ b/src/SIL.XForge.Scripture/ClientApp/e2e/workflows/smoke-tests.mts @@ -72,7 +72,7 @@ export async function traverseHomePageAndLoginPage(page: Page, context: Screensh // Log in with Paratext await page.locator('a').filter({ hasText: 'Log in with Paratext' }).click(); - await expect(page.getByText('Sign in with your Paratext Registry account')).toBeVisible(); + await expect(page.getByText('Sign in with your Paratext Registry account')).toBeVisible({ timeout: 10_000 }); await page.fill('input[name=email]', 'user@example.com'); await page.click('#login-form button[type=submit]'); await screenshot(page, { pageName: 'registry_login_page', ...context }); diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/core/models/sf-project-settings.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/core/models/sf-project-settings.ts index 47a94f3c28c..1b759da08bc 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/core/models/sf-project-settings.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/core/models/sf-project-settings.ts @@ -8,14 +8,10 @@ export interface SFProjectSettings { sourceParatextId?: string | null; biblicalTermsEnabled?: boolean | null; + /* DraftSourcesSettingsChange */ additionalTrainingDataFiles?: string[] | null; - additionalTrainingSourceEnabled?: boolean | null; - additionalTrainingSourceParatextId?: string | null; - alternateSourceEnabled?: boolean | null; - alternateSourceParatextId?: string | null; - alternateTrainingSourceEnabled?: boolean | null; - alternateTrainingSourceParatextId?: string | null; - servalConfig?: string | null; + draftingSourcesParatextIds?: string[] | null; + trainingSourcesParatextIds?: string[] | null; checkingEnabled?: boolean | null; usersSeeEachOthersResponses?: boolean | null; 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 367f413f29e..fa107def7d4 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 @@ -299,30 +299,31 @@ describe('ServalProjectComponent', () => { ], translateConfig: { draftConfig: { - alternateSourceEnabled: true, - alternateSource: { - paratextId: 'ptproject03', - projectRef: 'project03', - name: 'Project 03', - shortName: 'P3', - writingSystem: { tag: 'en' } - }, - alternateTrainingSourceEnabled: true, - alternateTrainingSource: { - paratextId: 'ptproject04', - projectRef: 'project04', - name: 'Project 04', - shortName: 'P4', - writingSystem: { tag: 'en' } - }, - additionalTrainingSourceEnabled: true, - additionalTrainingSource: { - paratextId: 'ptproject05', - projectRef: 'project05', - name: 'Project 05', - shortName: 'P5', - writingSystem: { tag: 'en' } - }, + draftingSources: [ + { + paratextId: 'ptproject03', + projectRef: 'project03', + name: 'Project 03', + shortName: 'P3', + writingSystem: { tag: 'en' } + } + ], + trainingSources: [ + { + paratextId: 'ptproject04', + projectRef: 'project04', + name: 'Project 04', + shortName: 'P4', + writingSystem: { tag: 'en' } + }, + { + paratextId: 'ptproject05', + projectRef: 'project05', + name: 'Project 05', + shortName: 'P5', + writingSystem: { tag: 'en' } + } + ], lastSelectedTrainingScriptureRanges: args.draftConfig?.lastSelectedTrainingScriptureRanges ?? undefined, lastSelectedTranslationScriptureRanges: args.draftConfig?.lastSelectedTranslationScriptureRanges ?? undefined, diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/serval-projects.component.spec.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/serval-projects.component.spec.ts index 83d0e981cf2..53cb41fbdad 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/serval-projects.component.spec.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/serval-projects.component.spec.ts @@ -191,20 +191,22 @@ class TestEnvironment { name: 'Project 01', translateConfig: { draftConfig: { - alternateSourceEnabled: true, - alternateSource: { - paratextId: 'ptproject03', - projectRef: 'project03', - name: 'Project 03', - shortName: 'P3' - }, - alternateTrainingSourceEnabled: true, - alternateTrainingSource: { - paratextId: 'ptproject04', - projectRef: 'project04', - name: 'Project 04', - shortName: 'P4' - } + draftingSources: [ + { + paratextId: 'ptproject03', + projectRef: 'project03', + name: 'Project 03', + shortName: 'P3' + } + ], + trainingSources: [ + { + paratextId: 'ptproject04', + projectRef: 'project04', + name: 'Project 04', + shortName: 'P4' + } + ] }, preTranslate: true, source: { @@ -235,20 +237,22 @@ class TestEnvironment { shortName: 'P3', translateConfig: { draftConfig: { - alternateSourceEnabled: true, - alternateSource: { - paratextId: 'resource16char02', - projectRef: 'resource02', - name: 'Resource 02', - shortName: 'R2' - }, - alternateTrainingSourceEnabled: true, - alternateTrainingSource: { - paratextId: 'resource16char03', - projectRef: 'resource03', - name: 'Resource 03', - shortName: 'R3' - } + draftingSources: [ + { + paratextId: 'resource16char02', + projectRef: 'resource02', + name: 'Resource 02', + shortName: 'R2' + } + ], + trainingSources: [ + { + paratextId: 'resource16char03', + projectRef: 'resource03', + name: 'Resource 03', + shortName: 'R3' + } + ] }, preTranslate: false, source: { diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/confirm-sources/confirm-sources.stories.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/confirm-sources/confirm-sources.stories.ts index b8b3352ecd8..a66ba5752ba 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/confirm-sources/confirm-sources.stories.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/confirm-sources/confirm-sources.stories.ts @@ -30,30 +30,31 @@ when(mockActivatedProject.changes$).thenReturn( paratextId: 'source-project' }, draftConfig: { - alternateTrainingSourceEnabled: true, - alternateTrainingSource: { - projectRef: 'alternate-training-source', - shortName: 'ALT-TS', - name: 'Alternate Training Source', - paratextId: 'alternate-training-source', - writingSystem: { tag: 'es' } - }, - additionalTrainingSourceEnabled: true, - additionalTrainingSource: { - projectRef: 'additional-training-source', - shortName: 'ADD-TS', - name: 'Additional Training Source', - paratextId: 'additional-training-source', - writingSystem: { tag: 'es' } - }, - alternateSourceEnabled: true, - alternateSource: { - projectRef: 'alternate-drafting-source', - shortName: 'ADS', - name: 'Alternate Drafting Source', - paratextId: 'alternate-drafting-source', - writingSystem: { tag: 'es' } - } + draftingSources: [ + { + projectRef: 'first-drafting-source', + shortName: 'FDS', + name: 'First Drafting Source', + paratextId: 'first-drafting-source', + writingSystem: { tag: 'es' } + } + ], + trainingSources: [ + { + projectRef: 'first-training-source', + shortName: 'FTS', + name: 'First Training Source', + paratextId: 'first-training-source', + writingSystem: { tag: 'es' } + }, + { + projectRef: 'second-training-source', + shortName: 'STS', + name: 'Second Training Source', + paratextId: 'second-training-source', + writingSystem: { tag: 'es' } + } + ] } } }) 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 92308377fc2..e24c3b7e8bf 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 @@ -615,7 +615,7 @@ describe('DraftGenerationStepsComponent', () => { }); }); - describe('additional training source', () => { + describe('two training sources', () => { const availableBooks = [{ bookNum: 2 }, { bookNum: 3 }]; const allBooks = [{ bookNum: 1 }, ...availableBooks, { bookNum: 6 }, { bookNum: 7 }, { bookNum: 8 }]; const draftingSourceBooks = availableBooks.concat({ bookNum: 7 }); 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 fda1cfbc7b0..cdd4386bd0e 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 @@ -196,9 +196,7 @@ export class DraftGenerationStepsComponent implements OnInit { } const trainingSourceBooks: Set = new Set(trainingSources[0]?.texts.map(t => t.bookNum)); - const additionalTrainingSourceBooks: Set = new Set( - trainingSources[1]?.texts.map(t => t.bookNum) - ); + const secondTrainingSourceBooks: Set = new Set(trainingSources[1]?.texts.map(t => t.bookNum)); for (const source of this.draftingSources) { this.availableTranslateBooks[source?.projectRef] = []; @@ -284,7 +282,7 @@ export class DraftGenerationStepsComponent implements OnInit { } else { this.unusableTrainingSourceBooks.push(bookNum); } - if (trainingSources[1] != null && additionalTrainingSourceBooks.has(bookNum)) { + if (trainingSources[1] != null && secondTrainingSourceBooks.has(bookNum)) { this.availableTrainingBooks[trainingSources[1].projectRef].push({ number: bookNum, selected: selected }); isPresentInASource = true; } diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation.component.html b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation.component.html index 0984df2538e..ca65494334d 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation.component.html +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation.component.html @@ -101,13 +101,13 @@

> } - @if (!canAccessDraftSourceIfAvailable(additionalTrainingSource)) { + @if (!canAccessDraftSourceIfAvailable(secondTrainingSource)) { 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 0aef8afc0ee..1d329c4ea87 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 @@ -438,8 +438,8 @@ describe('DraftGenerationComponent', () => { }); }); - describe('user must have access to training source project (aka alternate training source project)', () => { - it('should show warning when no access to training source project', () => { + describe('user must have access to the first training source project', () => { + it('should show warning when no access', () => { const env = new TestEnvironment(() => { mockDraftSourcesService.getDraftProjectSources.and.returnValue( of({ @@ -447,8 +447,7 @@ describe('DraftGenerationComponent', () => { trainingSources: [ { noAccess: true - } as DraftSource, - undefined + } as DraftSource ], trainingTargets: [{} as DraftSource] } as DraftSourcesAsArrays) @@ -460,7 +459,7 @@ describe('DraftGenerationComponent', () => { expect(env.getElementByTestId('warning-training-source-no-access')).not.toBeNull(); }); - it('should not show warning when no access to alternate source project and not back translation nor pre-translate approved', () => { + it('should not show warning when no access and not back translation nor pre-translate approved', () => { const env = new TestEnvironment(() => { mockDraftSourcesService.getDraftProjectSources.and.returnValue( of({ @@ -468,8 +467,7 @@ describe('DraftGenerationComponent', () => { trainingSources: [ { noAccess: true - } as DraftSource, - undefined + } as DraftSource ], trainingTargets: [{} as DraftSource] } as DraftSourcesAsArrays) @@ -490,8 +488,7 @@ describe('DraftGenerationComponent', () => { trainingSources: [ { noAccess: true - } as DraftSource, - undefined + } as DraftSource ], trainingTargets: [{} as DraftSource] } as DraftSourcesAsArrays) @@ -502,7 +499,7 @@ describe('DraftGenerationComponent', () => { expect(env.getElementByTestId('warning-training-source-no-access')).toBeNull(); }); - it('should not show warning when source project is not set', () => { + it('should not show warning when the drafting source is not set', () => { // Because then we merely direct the user to configure sources. const env = new TestEnvironment(() => { mockDraftSourcesService.getDraftProjectSources.and.returnValue( @@ -511,8 +508,7 @@ describe('DraftGenerationComponent', () => { trainingSources: [ { noAccess: true - } as DraftSource, - undefined + } as DraftSource ], trainingTargets: [{} as DraftSource] } as DraftSourcesAsArrays) @@ -523,7 +519,7 @@ describe('DraftGenerationComponent', () => { expect(env.getElementByTestId('warning-training-source-no-access')).toBeNull(); }); - it('should show warning even when no access to alternate source project', () => { + it('should show warning even when no access to drafting source project', () => { const env = new TestEnvironment(() => { mockDraftSourcesService.getDraftProjectSources.and.returnValue( of({ @@ -535,8 +531,7 @@ describe('DraftGenerationComponent', () => { trainingSources: [ { noAccess: true - } as DraftSource, - undefined + } as DraftSource ], trainingTargets: [{} as DraftSource] } as DraftSourcesAsArrays) @@ -547,7 +542,7 @@ describe('DraftGenerationComponent', () => { expect(env.getElementByTestId('warning-training-source-no-access')).not.toBeNull(); }); - it('should not show warning when access to alternate training source project', () => { + it('should not show warning when user has access to the project', () => { const env = new TestEnvironment(() => { mockDraftSourcesService.getDraftProjectSources.and.returnValue( of({ @@ -555,8 +550,7 @@ describe('DraftGenerationComponent', () => { trainingSources: [ { noAccess: false - } as DraftSource, - undefined + } as DraftSource ], trainingTargets: [{} as DraftSource] } as DraftSourcesAsArrays) @@ -568,10 +562,8 @@ describe('DraftGenerationComponent', () => { }); }); - describe('user must have access to additional training source project', () => { - // i.e. in addition to the "alternate training source project". - - it('should show warning when no access to additional training source project', () => { + describe('user must have access to the second training source project', () => { + it('should show warning when no access', () => { const env = new TestEnvironment(() => { mockDraftSourcesService.getDraftProjectSources.and.returnValue( of({ @@ -591,7 +583,7 @@ describe('DraftGenerationComponent', () => { expect(env.getElementByTestId('warning-mix-source-no-access')).not.toBeNull(); }); - it('should not show warning when no access to additional training source project and not back translation nor pre-translate approved', () => { + it('should not show warning when no access and not back translation nor pre-translate approved', () => { const env = new TestEnvironment(() => { mockDraftSourcesService.getDraftProjectSources.and.returnValue( of({ @@ -633,8 +625,7 @@ describe('DraftGenerationComponent', () => { expect(env.getElementByTestId('warning-mix-source-no-access')).toBeNull(); }); - it('should not show warning when source project is not set', () => { - // Because we merely direct the user to configure sources. + it('should not show warning when the drafting source is not set', () => { const env = new TestEnvironment(() => { mockDraftSourcesService.getDraftProjectSources.and.returnValue( of({ @@ -654,7 +645,7 @@ describe('DraftGenerationComponent', () => { expect(env.getElementByTestId('warning-mix-source-no-access')).toBeNull(); }); - it('should show warning even when no access to source project', () => { + it('should show warning even when no access to the drafting source', () => { const env = new TestEnvironment(() => { mockDraftSourcesService.getDraftProjectSources.and.returnValue( of({ @@ -678,7 +669,7 @@ describe('DraftGenerationComponent', () => { expect(env.getElementByTestId('warning-mix-source-no-access')).not.toBeNull(); }); - it('should show warning even when no access to training source project', () => { + it('should show warning even when no access to the first training source project', () => { const env = new TestEnvironment(() => { mockDraftSourcesService.getDraftProjectSources.and.returnValue( of({ @@ -700,7 +691,7 @@ describe('DraftGenerationComponent', () => { expect(env.getElementByTestId('warning-mix-source-no-access')).not.toBeNull(); }); - it('should not show warning when access to additional training source project', () => { + it('should not show warning when user has access to the second training source project', () => { const env = new TestEnvironment(() => { mockDraftSourcesService.getDraftProjectSources.and.returnValue( of({ 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 51edaf2a881..e55719368a5 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 @@ -78,8 +78,6 @@ export class DraftGenerationComponent extends DataLoadingComponent implements On supportedLanguagesUrl: RouterLink = { route: [], fragment: 'supported-languages' }; draftHelp = this.i18n.interpolate('draft_generation.instructions_help'); - additionalTrainingSourceLanguage?: string; - alternateTrainingSourceLanguage?: string; sourceLanguage?: string; targetLanguage?: string; targetLanguageDisplayName?: string; @@ -88,7 +86,7 @@ export class DraftGenerationComponent extends DataLoadingComponent implements On source?: DraftSource; trainingSource?: DraftSource; - additionalTrainingSource?: DraftSource; + secondTrainingSource?: DraftSource; jobSubscription?: Subscription; isOnline = true; @@ -156,7 +154,7 @@ export class DraftGenerationComponent extends DataLoadingComponent implements On this.isPreviewSupported && this.canAccessDraftSourceIfAvailable(this.trainingSource) && this.canAccessDraftSourceIfAvailable(this.source) && - this.canAccessDraftSourceIfAvailable(this.additionalTrainingSource) + this.canAccessDraftSourceIfAvailable(this.secondTrainingSource) ); } @@ -177,7 +175,7 @@ export class DraftGenerationComponent extends DataLoadingComponent implements On /** Have drafting sources been adequately configured that a draft can be generated? */ get isSourcesConfigurationComplete(): boolean { - return this.source != null && (this.trainingSource != null || this.additionalTrainingSource != null); + return this.source != null && (this.trainingSource != null || this.secondTrainingSource != null); } get hasConfigureSourcePermission(): boolean { @@ -226,7 +224,7 @@ export class DraftGenerationComponent extends DataLoadingComponent implements On tap(({ trainingSources, draftingSources }) => { this.source = draftingSources[0]; this.trainingSource = trainingSources[0]; - this.additionalTrainingSource = trainingSources[1]; + this.secondTrainingSource = trainingSources[1]; }) ) ]) diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-sources.service.spec.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-sources.service.spec.ts index 38558917b5b..30949c0128a 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-sources.service.spec.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-sources.service.spec.ts @@ -67,36 +67,37 @@ describe('DraftSourcesService', () => { } }, draftConfig: { - alternateSource: { - projectRef: 'alternate_source_project', - paratextId: 'PT_ASP', - name: 'Alternate Source Project', - shortName: 'ASP', - writingSystem: { - tag: 'en_NZ' + draftingSources: [ + { + projectRef: 'first_drafting_source', + paratextId: 'PT_FDS', + name: 'First Drafting Source', + shortName: 'FDS', + writingSystem: { + tag: 'en_NZ' + } } - }, - alternateTrainingSource: { - projectRef: 'alternate_training_source_project', - paratextId: 'PT_ATSP', - name: 'Alternate Training Source Project', - shortName: 'ATSP', - writingSystem: { - tag: 'en_AU' - } - }, - alternateSourceEnabled: true, - alternateTrainingSourceEnabled: true, - additionalTrainingSource: { - projectRef: 'additional_training_source_project', - paratextId: 'PT_ADSP', - name: 'Additional Training Source Project', - shortName: 'ADSP', - writingSystem: { - tag: 'en_UK' + ], + trainingSources: [ + { + projectRef: 'first_training_source', + paratextId: 'PT_FTS', + name: 'First Training Source', + shortName: 'FTS', + writingSystem: { + tag: 'en_AU' + } + }, + { + projectRef: 'second_training_source', + paratextId: 'PT_STS', + name: 'Second Training Source', + shortName: 'STS', + writingSystem: { + tag: 'en_UK' + } } - }, - additionalTrainingSourceEnabled: true + ] } } }); @@ -112,38 +113,20 @@ describe('DraftSourcesService', () => { trainingTargets: [{ ...targetProject, projectRef: 'project01' }], draftingSources: [ { - name: 'Alternate Source Project', - projectRef: 'alternate_source_project', - shortName: 'ASP', - paratextId: 'PT_ASP', + ...targetProject.translateConfig.draftConfig.draftingSources[0], texts: [], - writingSystem: { - tag: 'en_NZ' - }, noAccess: true } as DraftSource ], trainingSources: [ { - name: 'Alternate Training Source Project', - projectRef: 'alternate_training_source_project', - shortName: 'ATSP', - paratextId: 'PT_ATSP', + ...targetProject.translateConfig.draftConfig.trainingSources[0], texts: [], - writingSystem: { - tag: 'en_AU' - }, noAccess: true } as DraftSource, { - name: 'Additional Training Source Project', - projectRef: 'additional_training_source_project', - shortName: 'ADSP', - paratextId: 'PT_ADSP', + ...targetProject.translateConfig.draftConfig.trainingSources[1], texts: [], - writingSystem: { - tag: 'en_UK' - }, noAccess: true } as DraftSource ] @@ -159,18 +142,8 @@ describe('DraftSourcesService', () => { projectRef: 'source_project' }, draftConfig: { - alternateSource: { - projectRef: 'alternate_source_project' - }, - alternateTrainingSource: { - projectRef: 'alternate_training_source_project' - }, - alternateSourceEnabled: true, - alternateTrainingSourceEnabled: true, - additionalTrainingSource: { - projectRef: 'additional_training_source_project' - }, - additionalTrainingSourceEnabled: true + draftingSources: [{ projectRef: 'first_drafting_source' }], + trainingSources: [{ projectRef: 'first_training_source' }, { projectRef: 'second_training_source' }] } } }); @@ -182,25 +155,25 @@ describe('DraftSourcesService', () => { tag: 'en_US' } }); - const alternateSourceProject = createTestProjectProfile({ - name: 'Alternate Source Project', - shortName: 'ASP', + const firstDraftingSourceProject = createTestProjectProfile({ + name: 'First Drafting Source Project', + shortName: 'FDS', texts: [{ bookNum: 1 }], writingSystem: { tag: 'en_NZ' } }); - const alternateTrainingSourceProject = createTestProjectProfile({ - name: 'Alternate Training Source Project', - shortName: 'ATSP', + const firstTrainingSourceProject = createTestProjectProfile({ + name: 'First Training Source Project', + shortName: 'FTS', texts: [{ bookNum: 1 }], writingSystem: { tag: 'en_AU' } }); - const additionalTrainingSourceProject = createTestProjectProfile({ - name: 'Additional Training Source Project', - shortName: 'ADSP', + const secondTrainingSourceProject = createTestProjectProfile({ + name: 'Second Training Source Project', + shortName: 'STS', texts: [{ bookNum: 1 }], writingSystem: { tag: 'en_UK' @@ -215,26 +188,21 @@ describe('DraftSourcesService', () => { when(mockProjectService.getProfile('source_project')).thenResolve({ data: sourceProject } as SFProjectProfileDoc); - when(mockProjectService.getProfile('alternate_source_project')).thenResolve({ - data: alternateSourceProject + when(mockProjectService.getProfile('first_drafting_source')).thenResolve({ + data: firstDraftingSourceProject } as SFProjectProfileDoc); - when(mockProjectService.getProfile('alternate_training_source_project')).thenResolve({ - data: alternateTrainingSourceProject + when(mockProjectService.getProfile('first_training_source')).thenResolve({ + data: firstTrainingSourceProject } as SFProjectProfileDoc); - when(mockProjectService.getProfile('additional_training_source_project')).thenResolve({ - data: additionalTrainingSourceProject + when(mockProjectService.getProfile('second_training_source')).thenResolve({ + data: secondTrainingSourceProject } as SFProjectProfileDoc); when(mockUserService.getCurrentUser()).thenResolve({ data: createTestUser( { sites: { [environment.siteId]: { - projects: [ - 'source_project', - 'alternate_source_project', - 'alternate_training_source_project', - 'additional_training_source_project' - ] + projects: ['source_project', 'first_drafting_source', 'first_training_source', 'second_training_source'] } } }, @@ -245,172 +213,14 @@ describe('DraftSourcesService', () => { service.getDraftProjectSources().subscribe(result => { expect(result).toEqual({ trainingTargets: [{ ...targetProject, projectRef: 'project01' }], - draftingSources: [{ ...alternateSourceProject, projectRef: 'alternate_source_project' }], + draftingSources: [{ ...firstDraftingSourceProject, projectRef: 'first_drafting_source' }], trainingSources: [ - { ...alternateTrainingSourceProject, projectRef: 'alternate_training_source_project' }, - { ...additionalTrainingSourceProject, projectRef: 'additional_training_source_project' } + { ...firstTrainingSourceProject, projectRef: 'first_training_source' }, + { ...secondTrainingSourceProject, projectRef: 'second_training_source' } ] } as DraftSourcesAsArrays); done(); }); }); - - it('should not pass the alternate source project if disabled', done => { - const targetProject = createTestProjectProfile({ - translateConfig: { - draftConfig: { - alternateSource: { - projectRef: 'alternate_source_project', - name: 'Alternate Source Project', - shortName: 'ASP', - writingSystem: { - tag: 'en_NZ' - } - }, - alternateSourceEnabled: false - } - } - }); - when(mockActivatedProjectService.changes$).thenReturn( - new BehaviorSubject({ - id: 'project01', - data: targetProject - } as SFProjectProfileDoc) - ); - - service.getDraftProjectSources().subscribe(result => { - expectTargetOnly({ ...targetProject, projectRef: 'project01' }, result); - done(); - }); - }); - - it('should not pass the alternate source project if enabled but missing', done => { - const targetProject = createTestProjectProfile({ - translateConfig: { - draftConfig: { - alternateSourceEnabled: true - } - } - }); - when(mockActivatedProjectService.changes$).thenReturn( - new BehaviorSubject({ - id: 'project01', - data: targetProject - } as SFProjectProfileDoc) - ); - - service.getDraftProjectSources().subscribe(result => { - expectTargetOnly({ ...targetProject, projectRef: 'project01' }, result); - done(); - }); - }); - - it('should not pass the alternate training source project if disabled', done => { - const targetProject = createTestProjectProfile({ - translateConfig: { - draftConfig: { - alternateTrainingSource: { - projectRef: 'alternate_training_source_project', - name: 'Alternate Training Source Project', - shortName: 'ATSP', - writingSystem: { - tag: 'en_AU' - } - }, - alternateTrainingSourceEnabled: false - } - } - }); - when(mockActivatedProjectService.changes$).thenReturn( - new BehaviorSubject({ - id: 'project01', - data: targetProject - } as SFProjectProfileDoc) - ); - - service.getDraftProjectSources().subscribe(result => { - expectTargetOnly({ ...targetProject, projectRef: 'project01' }, result); - done(); - }); - }); - - it('should not pass the alternate training source project if enabled but missing', done => { - const targetProject = createTestProjectProfile({ - translateConfig: { - draftConfig: { - alternateTrainingSourceEnabled: true - } - } - }); - when(mockActivatedProjectService.changes$).thenReturn( - new BehaviorSubject({ - id: 'project01', - data: targetProject - } as SFProjectProfileDoc) - ); - - service.getDraftProjectSources().subscribe(result => { - expectTargetOnly({ ...targetProject, projectRef: 'project01' }, result); - done(); - }); - }); - - it('should not pass the additional training source project if disabled', done => { - const targetProject = createTestProjectProfile({ - translateConfig: { - draftConfig: { - additionalTrainingSource: { - projectRef: 'additional_training_source_project', - name: 'Additional Training Source Project', - shortName: 'ADSP', - writingSystem: { - tag: 'en_UK' - } - }, - additionalTrainingSourceEnabled: false - } - } - }); - when(mockActivatedProjectService.changes$).thenReturn( - new BehaviorSubject({ - id: 'project01', - data: targetProject - } as SFProjectProfileDoc) - ); - - service.getDraftProjectSources().subscribe(result => { - expectTargetOnly({ ...targetProject, projectRef: 'project01' }, result); - done(); - }); - }); - - it('should not pass the additional training source project if enabled but missing', done => { - const targetProject = createTestProjectProfile({ - translateConfig: { - draftConfig: { - additionalTrainingSourceEnabled: true - } - } - }); - when(mockActivatedProjectService.changes$).thenReturn( - new BehaviorSubject({ - id: 'project01', - data: targetProject - } as SFProjectProfileDoc) - ); - - service.getDraftProjectSources().subscribe(result => { - expectTargetOnly({ ...targetProject, projectRef: 'project01' }, result); - done(); - }); - }); }); - - function expectTargetOnly(targetProject: DraftSource, result: DraftSourcesAsArrays): void { - expect(result).toEqual({ - trainingSources: [], - trainingTargets: [targetProject], - draftingSources: [] - } as DraftSourcesAsArrays); - } }); diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-sources/draft-sources.component.spec.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-sources/draft-sources.component.spec.ts index 6edd9c3d36d..0cc06dfe590 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-sources/draft-sources.component.spec.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-sources/draft-sources.component.spec.ts @@ -147,15 +147,12 @@ describe('DraftSourcesComponent', () => { // Suppose the user loads up the sources configuration page, changes no projects, and clicks Save. The settings // change request will just correspond to what the project already has for its settings. const expectedSettingsChangeRequest: SFProjectSettings = { - alternateSourceEnabled: true, - alternateSourceParatextId: - env.activatedProjectDoc.data!.translateConfig.draftConfig.alternateSource!.paratextId, - alternateTrainingSourceEnabled: true, - alternateTrainingSourceParatextId: - env.activatedProjectDoc.data!.translateConfig.draftConfig.alternateTrainingSource!.paratextId, - additionalTrainingSourceEnabled: true, - additionalTrainingSourceParatextId: - env.activatedProjectDoc.data!.translateConfig.draftConfig.additionalTrainingSource!.paratextId, + draftingSourcesParatextIds: env.activatedProjectDoc.data!.translateConfig.draftConfig.draftingSources.map( + s => s.paratextId + ), + trainingSourcesParatextIds: env.activatedProjectDoc.data!.translateConfig.draftConfig.trainingSources.map( + s => s.paratextId + ), additionalTrainingDataFiles: env.activatedProjectDoc.data!.translateConfig.draftConfig.lastSelectedTrainingDataFiles }; @@ -186,15 +183,12 @@ describe('DraftSourcesComponent', () => { // Suppose the user loads up the page, clears the second training/reference project box, and clicks Save. The // settings change request will show a requested change for unsetting the additional-training-source. const expectedSettingsChangeRequest: SFProjectSettings = { - alternateSourceEnabled: true, - alternateSourceParatextId: - env.activatedProjectDoc.data!.translateConfig.draftConfig.alternateSource!.paratextId, - alternateTrainingSourceEnabled: true, - alternateTrainingSourceParatextId: - env.activatedProjectDoc.data!.translateConfig.draftConfig.alternateTrainingSource!.paratextId, - // The second training source, the "additional training source", should not be set. - additionalTrainingSourceEnabled: false, - additionalTrainingSourceParatextId: DraftSourcesComponent.projectSettingValueUnset, + draftingSourcesParatextIds: env.activatedProjectDoc.data!.translateConfig.draftConfig.draftingSources.map( + s => s.paratextId + ), + trainingSourcesParatextIds: [ + env.activatedProjectDoc.data!.translateConfig.draftConfig.trainingSources[0].paratextId + ], additionalTrainingDataFiles: env.activatedProjectDoc.data!.translateConfig.draftConfig.lastSelectedTrainingDataFiles }; @@ -229,18 +223,14 @@ describe('DraftSourcesComponent', () => { // Suppose the user comes to the page, leaves the second reference/training project selection alone, and clears // the first reference/training project selection. Let's respond by clearing the additional-training-source, and - // setting the alternate-training-source to the remaining reference/training project that is still specified. + // setting the first-training-source to the remaining reference/training project that is still specified. const expectedSettingsChangeRequest: SFProjectSettings = { - alternateSourceEnabled: true, - alternateSourceParatextId: - env.activatedProjectDoc.data!.translateConfig.draftConfig.alternateSource!.paratextId, - // The first training source should be set and should be equal to what the second training source _was_. - alternateTrainingSourceEnabled: true, - alternateTrainingSourceParatextId: - env.activatedProjectDoc.data!.translateConfig.draftConfig.additionalTrainingSource!.paratextId, - // And the second training source, the "additional training source", should not be set. - additionalTrainingSourceEnabled: false, - additionalTrainingSourceParatextId: DraftSourcesComponent.projectSettingValueUnset, + draftingSourcesParatextIds: env.activatedProjectDoc.data!.translateConfig.draftConfig.draftingSources.map( + s => s.paratextId + ), + trainingSourcesParatextIds: [ + env.activatedProjectDoc.data!.translateConfig.draftConfig.trainingSources[1].paratextId + ], additionalTrainingDataFiles: env.activatedProjectDoc.data!.translateConfig.draftConfig.lastSelectedTrainingDataFiles }; @@ -313,15 +303,12 @@ describe('DraftSourcesComponent', () => { env.clickLanguageCodesConfirmationCheckbox(); const expectedSettingsChangeRequest: SFProjectSettings = { - alternateSourceEnabled: true, - alternateSourceParatextId: - env.activatedProjectDoc.data!.translateConfig.draftConfig.alternateSource!.paratextId, - alternateTrainingSourceEnabled: true, - alternateTrainingSourceParatextId: - env.activatedProjectDoc.data!.translateConfig.draftConfig.alternateTrainingSource!.paratextId, - additionalTrainingSourceEnabled: true, - additionalTrainingSourceParatextId: - env.activatedProjectDoc.data!.translateConfig.draftConfig.additionalTrainingSource!.paratextId, + draftingSourcesParatextIds: env.activatedProjectDoc.data!.translateConfig.draftConfig.draftingSources.map( + s => s.paratextId + ), + trainingSourcesParatextIds: env.activatedProjectDoc.data!.translateConfig.draftConfig.trainingSources.map( + s => s.paratextId + ), additionalTrainingDataFiles: ['test1', 'test2'] }; @@ -521,13 +508,9 @@ describe('DraftSourcesComponent', () => { ); expect(result).toEqual({ - additionalTrainingSourceEnabled: false, - additionalTrainingSourceParatextId: 'unset', - alternateSourceEnabled: false, - alternateSourceParatextId: 'unset', - alternateTrainingSourceEnabled: false, - alternateTrainingSourceParatextId: 'unset', - additionalTrainingDataFiles: [] + additionalTrainingDataFiles: [], + trainingSourcesParatextIds: [], + draftingSourcesParatextIds: [] }); }); @@ -546,13 +529,9 @@ describe('DraftSourcesComponent', () => { currentProjectParatextId ); expect(result).toEqual({ - additionalTrainingSourceEnabled: false, - additionalTrainingSourceParatextId: 'unset', - alternateSourceEnabled: false, - alternateSourceParatextId: 'unset', - alternateTrainingSourceEnabled: true, - alternateTrainingSourceParatextId: mockProject1.paratextId, - additionalTrainingDataFiles: [] + additionalTrainingDataFiles: [], + trainingSourcesParatextIds: [mockProject1.paratextId], + draftingSourcesParatextIds: [] }); }); @@ -571,13 +550,9 @@ describe('DraftSourcesComponent', () => { currentProjectParatextId ); expect(result).toEqual({ - additionalTrainingSourceEnabled: true, - additionalTrainingSourceParatextId: mockProject2.paratextId, - alternateSourceEnabled: false, - alternateSourceParatextId: 'unset', - alternateTrainingSourceEnabled: true, - alternateTrainingSourceParatextId: mockProject1.paratextId, - additionalTrainingDataFiles: [] + additionalTrainingDataFiles: [], + trainingSourcesParatextIds: [mockProject1.paratextId, mockProject2.paratextId], + draftingSourcesParatextIds: [] }); }); @@ -596,13 +571,9 @@ describe('DraftSourcesComponent', () => { currentProjectParatextId ); expect(result).toEqual({ - additionalTrainingSourceEnabled: false, - additionalTrainingSourceParatextId: 'unset', - alternateSourceEnabled: true, - alternateSourceParatextId: mockProject1.paratextId, - alternateTrainingSourceEnabled: false, - alternateTrainingSourceParatextId: 'unset', - additionalTrainingDataFiles: [] + additionalTrainingDataFiles: [], + trainingSourcesParatextIds: [], + draftingSourcesParatextIds: [mockProject1.paratextId] }); }); @@ -621,13 +592,9 @@ describe('DraftSourcesComponent', () => { currentProjectParatextId ); expect(result).toEqual({ - additionalTrainingSourceEnabled: true, - additionalTrainingSourceParatextId: mockProject2.paratextId, - alternateSourceEnabled: true, - alternateSourceParatextId: mockProject1.paratextId, - alternateTrainingSourceEnabled: true, - alternateTrainingSourceParatextId: mockProject1.paratextId, - additionalTrainingDataFiles: [] + additionalTrainingDataFiles: [], + trainingSourcesParatextIds: [mockProject1.paratextId, mockProject2.paratextId], + draftingSourcesParatextIds: [mockProject1.paratextId] }); }); @@ -664,13 +631,9 @@ describe('DraftSourcesComponent', () => { currentProjectParatextId ); expect(result).toEqual({ - additionalTrainingSourceEnabled: false, - additionalTrainingSourceParatextId: 'unset', - alternateSourceEnabled: false, - alternateSourceParatextId: 'unset', - alternateTrainingSourceEnabled: false, - alternateTrainingSourceParatextId: 'unset', - additionalTrainingDataFiles: [] + additionalTrainingDataFiles: [], + trainingSourcesParatextIds: [], + draftingSourcesParatextIds: [] }); }); @@ -833,12 +796,8 @@ class TestEnvironment { // the sf project 0's translate config values. const sfProject0: SFProject = this.activatedProjectDoc.data; sfProject0.translateConfig.source = usersSFResources[0]; - sfProject0.translateConfig.draftConfig.alternateSourceEnabled = true; - sfProject0.translateConfig.draftConfig.alternateSource = usersSFProjects[1]; - sfProject0.translateConfig.draftConfig.alternateTrainingSourceEnabled = true; - sfProject0.translateConfig.draftConfig.alternateTrainingSource = usersSFResources[2]; - sfProject0.translateConfig.draftConfig.additionalTrainingSourceEnabled = true; - sfProject0.translateConfig.draftConfig.additionalTrainingSource = usersSFProjects[2]; + sfProject0.translateConfig.draftConfig.draftingSources = [usersSFProjects[1]]; + sfProject0.translateConfig.draftConfig.trainingSources = [usersSFResources[2], usersSFProjects[2]]; sfProject0.translateConfig.translationSuggestionsEnabled = false; sfProject0.translateConfig.preTranslate = true; diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-sources/draft-sources.component.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-sources/draft-sources.component.ts index 747ece13307..7c9be1ddea1 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-sources/draft-sources.component.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-sources/draft-sources.component.ts @@ -465,13 +465,9 @@ export class DraftSourcesComponent extends DataLoadingComponent implements Confi } export interface DraftSourcesSettingsChange { - additionalTrainingSourceEnabled: boolean; - additionalTrainingSourceParatextId?: string; - alternateSourceEnabled: boolean; - alternateSourceParatextId?: string; - alternateTrainingSourceEnabled: boolean; - alternateTrainingSourceParatextId?: string; additionalTrainingDataFiles: string[]; + draftingSourcesParatextIds: string[]; + trainingSourcesParatextIds: string[]; } /** Convert some arrays of drafting sources to a settings object that can be applied to a SF project. */ @@ -500,28 +496,9 @@ export function sourceArraysToSettingsChange( throw new Error('Training target must be the current project'); } - const alternateTrainingSource: SelectableProject | undefined = trainingSources[0]; - const additionalTrainingSource: SelectableProject | undefined = trainingSources[1]; - const alternateSource: SelectableProject | undefined = draftingSources[0]; - - const alternateTrainingEnabled = alternateTrainingSource != null; - const additionalTrainingEnabled = additionalTrainingSource != null; - const alternateSourceEnabled = alternateSource != null; - - const config: DraftSourcesSettingsChange = { - additionalTrainingSourceEnabled: additionalTrainingEnabled, - additionalTrainingSourceParatextId: additionalTrainingEnabled - ? additionalTrainingSource?.paratextId - : DraftSourcesComponent.projectSettingValueUnset, - alternateSourceEnabled: alternateSourceEnabled, - alternateSourceParatextId: alternateSourceEnabled - ? alternateSource?.paratextId - : DraftSourcesComponent.projectSettingValueUnset, - alternateTrainingSourceEnabled: alternateTrainingEnabled, - alternateTrainingSourceParatextId: alternateTrainingEnabled - ? alternateTrainingSource?.paratextId - : DraftSourcesComponent.projectSettingValueUnset, - additionalTrainingDataFiles: selectedTrainingFileIds + return { + additionalTrainingDataFiles: selectedTrainingFileIds, + trainingSourcesParatextIds: trainingSources.map(s => s.paratextId), + draftingSourcesParatextIds: draftingSources.map(s => s.paratextId) }; - return config; } diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-sources/draft-sources.stories.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-sources/draft-sources.stories.ts index 5d788c2d740..03216b7c20f 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-sources/draft-sources.stories.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-sources/draft-sources.stories.ts @@ -45,30 +45,31 @@ const projectDocWithExistingSources = { translationSuggestionsEnabled: false, preTranslate: true, draftConfig: { - additionalTrainingSourceEnabled: true, - alternateSourceEnabled: true, - alternateTrainingSourceEnabled: true, - alternateTrainingSource: { - paratextId: 'pt1', - projectRef: 'sf1', - name: 'Alternate Training Source', - shortName: 'ALT-TS', - writingSystem: { script: 'Latn', tag: 'es' } - }, - additionalTrainingSource: { - paratextId: 'pt2', - projectRef: 'sf2', - name: 'Additional Training Source', - shortName: 'ADD-TS', - writingSystem: { script: 'Latn', tag: 'es' } - }, - alternateSource: { - paratextId: 'pt3', - projectRef: 'sf3', - name: 'Alternate Source', - shortName: 'AS', - writingSystem: { script: 'Latn', tag: 'es' } - } + draftingSources: [ + { + paratextId: 'pt3', + projectRef: 'sf3', + name: 'First Drafting Source', + shortName: 'FDS', + writingSystem: { script: 'Latn', tag: 'es' } + } + ], + trainingSources: [ + { + paratextId: 'pt1', + projectRef: 'sf1', + name: 'First Training Source', + shortName: 'FTS', + writingSystem: { script: 'Latn', tag: 'es' } + }, + { + paratextId: 'pt2', + projectRef: 'sf2', + name: 'Second Training Source', + shortName: 'STS', + writingSystem: { script: 'Latn', tag: 'es' } + } + ] }, source: { paratextId: 'pt0', diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-utils.spec.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-utils.spec.ts index 9c33d0d3654..92fcba9decf 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-utils.spec.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-utils.spec.ts @@ -20,170 +20,25 @@ describe('DraftUtils', () => { expect(result).toEqual({ draftingSources: [], trainingSources: [], trainingTargets: [testProject] }); }); - it('defaults to using a regular source as both source and target', () => { - const testSource: TranslateSource = translateSource('test source'); - const testProject = createTestProjectProfile({ translateConfig: { source: testSource } }); - - const result = projectToDraftSources(testProject); - expect(result).toEqual({ - draftingSources: [testSource], - trainingSources: [testSource], - trainingTargets: [testProject] - }); - }); - - it('handles overriding training source', () => { - const testSource: TranslateSource = translateSource('test source'); - const testTrainingSource: TranslateSource = translateSource('test training source'); - const testProject = createTestProjectProfile({ - translateConfig: { - source: testSource, - draftConfig: { alternateTrainingSourceEnabled: true, alternateTrainingSource: testTrainingSource } - } - }); - - const result = projectToDraftSources(testProject); - expect(result).toEqual({ - draftingSources: [testSource], - trainingSources: [testTrainingSource], - trainingTargets: [testProject] - }); - }); - - it('handles overriding drafting source', () => { - const testSource: TranslateSource = translateSource('test source'); - const testDraftingSource: TranslateSource = translateSource('test drafting source'); - const testProject = createTestProjectProfile({ - translateConfig: { - source: testSource, - draftConfig: { alternateSourceEnabled: true, alternateSource: testDraftingSource } - } - }); - - const result = projectToDraftSources(testProject); - expect(result).toEqual({ - draftingSources: [testDraftingSource], - trainingSources: [testSource], - trainingTargets: [testProject] - }); - }); - - it('handles setting additional training source', () => { - const testSource = translateSource('test source'); - const testAdditionalTrainingSource = translateSource('test additional training source'); - const testProject = createTestProjectProfile({ - translateConfig: { - source: testSource, - draftConfig: { additionalTrainingSourceEnabled: true, additionalTrainingSource: testAdditionalTrainingSource } - } - }); - - const result = projectToDraftSources(testProject); - expect(result).toEqual({ - draftingSources: [testSource], - trainingSources: [testSource, testAdditionalTrainingSource], - trainingTargets: [testProject] - }); - }); - - it('handles setting additional training source and alternate training source', () => { - const testSource = translateSource('test source'); - const testAdditionalTrainingSource = translateSource('test additional training source'); - const testAlternateTrainingSource = translateSource('test alternate training source'); - const testProject = createTestProjectProfile({ - translateConfig: { - source: testSource, - draftConfig: { - additionalTrainingSourceEnabled: true, - additionalTrainingSource: testAdditionalTrainingSource, - alternateTrainingSourceEnabled: true, - alternateTrainingSource: testAlternateTrainingSource - } - } - }); - - const result = projectToDraftSources(testProject); - expect(result).toEqual({ - draftingSources: [testSource], - trainingSources: [testAlternateTrainingSource, testAdditionalTrainingSource], - trainingTargets: [testProject] - }); - }); - - it('handles setting additional training source and alternate training source and alternate source', () => { - const testSource = translateSource('test source'); - const testAdditionalTrainingSource = translateSource('test additional training source'); - const testAlternateTrainingSource = translateSource('test alternate training source'); - const testAlternateSource = translateSource('test alternate source'); - const testProject = createTestProjectProfile({ - translateConfig: { - source: testSource, - draftConfig: { - alternateSourceEnabled: true, - alternateSource: testAlternateSource, - additionalTrainingSourceEnabled: true, - additionalTrainingSource: testAdditionalTrainingSource, - alternateTrainingSourceEnabled: true, - alternateTrainingSource: testAlternateTrainingSource - } - } - }); - - const result = projectToDraftSources(testProject); - expect(result).toEqual({ - draftingSources: [testAlternateSource], - trainingSources: [testAlternateTrainingSource, testAdditionalTrainingSource], - trainingTargets: [testProject] - }); - }); - - it('handles sources that are set but disabled', () => { - const testSource = translateSource('test source'); - const testAdditionalTrainingSource = translateSource('test additional training source'); - const testAlternateTrainingSource = translateSource('test alternate training source'); - const testAlternateSource = translateSource('test alternate source'); - const testProject = createTestProjectProfile({ - translateConfig: { - source: testSource, - draftConfig: { - alternateSourceEnabled: false, - alternateSource: testAlternateSource, - additionalTrainingSourceEnabled: false, - additionalTrainingSource: testAdditionalTrainingSource, - alternateTrainingSourceEnabled: false, - alternateTrainingSource: testAlternateTrainingSource - } - } - }); - - const result = projectToDraftSources(testProject); - expect(result).toEqual({ - draftingSources: [testSource], - trainingSources: [testSource], - trainingTargets: [testProject] - }); - }); - - it('handles sources that are enabled but set to undefined', () => { + it('handles setting training and drafting sources', () => { const testSource = translateSource('test source'); + const testFirstDraftingSource = translateSource('test first drafting source'); + const testFirstTrainingSource = translateSource('test first training source'); + const testSecondTrainingSource = translateSource('test second training source'); const testProject = createTestProjectProfile({ translateConfig: { source: testSource, draftConfig: { - alternateSourceEnabled: true, - alternateSource: undefined, - additionalTrainingSourceEnabled: true, - additionalTrainingSource: undefined, - alternateTrainingSourceEnabled: true, - alternateTrainingSource: undefined + draftingSources: [testFirstDraftingSource], + trainingSources: [testFirstTrainingSource, testSecondTrainingSource] } } }); const result = projectToDraftSources(testProject); expect(result).toEqual({ - draftingSources: [testSource], - trainingSources: [testSource], + draftingSources: [testFirstDraftingSource], + trainingSources: [testFirstTrainingSource, testSecondTrainingSource], trainingTargets: [testProject] }); }); 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 e4ff4098385..3cc680b3046 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 @@ -1,7 +1,6 @@ import { SFProjectProfile } from 'realtime-server/lib/esm/scriptureforge/models/sf-project'; import { TranslateSource } from 'realtime-server/lib/esm/scriptureforge/models/translate-config'; import language_code_mapping from '../../../../../language_code_mapping.json'; -import { hasArrayProp } from '../../../type-utils'; import { SelectableProjectWithLanguageCode } from '../../core/paratext.service'; /** Represents draft sources as a set of two {@link TranslateSource} arrays, and one {@link SFProjectProfile} array. */ @@ -51,57 +50,16 @@ export function draftSourcesAsTranslateSourceArraysToDraftSourcesAsSelectablePro /** * Takes a SFProjectProfile and returns the training and drafting sources for the project as three arrays. * - * This considers properties such as alternateTrainingSourceEnabled and alternateTrainingSource and makes sure to only - * include a source if it's enabled and not null. It also considers whether the project source is implicitly the - * training and/or drafting source. + * This method is also intended to be act as an abstraction layer to allow changing the data model in the future to + * allow multiple training targets without needing to change all the places that use this method. * - * This method is also intended to be act as an abstraction layer to allow changing the data model in the future without - * needing to change all the places that use this method. - * - * Currently this method provides guarantees via the type system that there will be at most 2 training sources, exactly - * 1 training target, and at most 1 drafting source. Consumers of this method that cannot accept an arbitrary length for - * each of these arrays are encouraged to write their code in such a way that it will noticeably break (preferably at - * build time) if these guarantees are changed, to make it easier to find code that relies on the current limit on the - * number of sources in each category. * @param project The project to get the sources for * @returns An object with three arrays: trainingSources, trainingTargets, and draftingSources */ export function projectToDraftSources(project: SFProjectProfile): DraftSourcesAsTranslateSourceArrays { - const trainingSources: TranslateSource[] & ({ length: 0 } | { length: 1 } | { length: 2 }) = []; - const draftingSources: TranslateSource[] & ({ length: 0 } | { length: 1 }) = []; - const trainingTargets: [SFProjectProfile] = [project]; - const draftConfig = project.translateConfig.draftConfig; - // Forward compatibility with the upcoming SF-3163 change to allow draft sources as arrays - if ( - hasArrayProp(project.translateConfig.draftConfig, 'trainingSources') && - hasArrayProp(project.translateConfig.draftConfig, 'draftingSources') - ) { - const trainingSources: TranslateSource[] = [...project.translateConfig.draftConfig.trainingSources] as any[]; - const draftingSources: TranslateSource[] = [...project.translateConfig.draftConfig.draftingSources] as any[]; - const trainingTargets: SFProjectProfile[] = [project]; - return { trainingSources, trainingTargets, draftingSources }; - } - let trainingSource: TranslateSource | undefined; - if (draftConfig.alternateTrainingSourceEnabled && draftConfig.alternateTrainingSource != null) { - trainingSource = draftConfig.alternateTrainingSource; - } else { - trainingSource = project.translateConfig.source; - } - if (trainingSource != null) { - trainingSources.push(trainingSource); - } - if (draftConfig.additionalTrainingSourceEnabled && draftConfig.additionalTrainingSource != null) { - trainingSources.push(draftConfig.additionalTrainingSource); - } - let draftingSource: TranslateSource | undefined; - if (draftConfig.alternateSourceEnabled && draftConfig.alternateSource != null) { - draftingSource = draftConfig.alternateSource; - } else { - draftingSource = project.translateConfig.source; - } - if (draftingSource != null) { - draftingSources.push(draftingSource); - } + const trainingSources: TranslateSource[] = [...project.translateConfig.draftConfig.trainingSources]; + const draftingSources: TranslateSource[] = [...project.translateConfig.draftConfig.draftingSources]; + const trainingTargets: SFProjectProfile[] = [project]; return { trainingSources, trainingTargets, draftingSources }; } diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/language-codes-confirmation/language-codes-confirmation.stories.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/language-codes-confirmation/language-codes-confirmation.stories.ts index 5a2bca5cf65..40256aec549 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/language-codes-confirmation/language-codes-confirmation.stories.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/language-codes-confirmation/language-codes-confirmation.stories.ts @@ -15,23 +15,23 @@ const mockActivatedProject = mock(ActivatedProjectService); const defaultDraftSources: DraftSourcesAsSelectableProjectArrays = { draftingSources: [ { - shortName: 'ADS', - name: 'Alternate Drafting Source', - paratextId: 'alternate-drafting-source', + shortName: 'FDS', + name: 'First Drafting Source', + paratextId: 'first-drafting-source', languageTag: 'es' } ], trainingSources: [ { - shortName: 'ALT-TS', - name: 'Alternate Training Source', - paratextId: 'alternate-training-source', + shortName: 'FTS', + name: 'First Training Source', + paratextId: 'first-training-source', languageTag: 'spa' }, { - shortName: 'ADD-TS', - name: 'Additional Training Source', - paratextId: 'additional-training-source', + shortName: 'STS', + name: 'Second Training Source', + paratextId: 'second-training-source', languageTag: 'es' } ], diff --git a/src/SIL.XForge.Scripture/Controllers/SFProjectsRpcController.cs b/src/SIL.XForge.Scripture/Controllers/SFProjectsRpcController.cs index c59a7348277..8f9f48c97de 100644 --- a/src/SIL.XForge.Scripture/Controllers/SFProjectsRpcController.cs +++ b/src/SIL.XForge.Scripture/Controllers/SFProjectsRpcController.cs @@ -188,12 +188,24 @@ public async Task UpdateSettings(string projectId, SFProjectSe { "CheckingAnswerExport", settings?.CheckingAnswerExport }, { "SourceParatextId", settings?.SourceParatextId }, { "BiblicalTermsEnabled", settings?.BiblicalTermsEnabled?.ToString() }, - { "AdditionalTrainingDataFiles", settings?.AdditionalTrainingDataFiles?.ToString() }, - { "AlternateSourceParatextId", settings?.AlternateSourceParatextId }, - { "AlternateTrainingSourceEnabled", settings?.AlternateTrainingSourceEnabled?.ToString() }, - { "AlternateTrainingSourceParatextId", settings?.AlternateTrainingSourceParatextId }, - { "AdditionalTrainingSourceEnabled", settings?.AdditionalTrainingSourceEnabled?.ToString() }, - { "AdditionalTrainingSourceParatextId", settings?.AdditionalTrainingSourceParatextId }, + { + "AdditionalTrainingDataFiles", + settings?.AdditionalTrainingDataFiles is null + ? null + : string.Join(',', settings.AdditionalTrainingDataFiles) + }, + { + "DraftingSourcesParatextIds", + settings?.DraftingSourcesParatextIds is null + ? null + : string.Join(',', settings.DraftingSourcesParatextIds) + }, + { + "TrainingSourcesParatextIds", + settings?.TrainingSourcesParatextIds is null + ? null + : string.Join(',', settings.TrainingSourcesParatextIds) + }, { "CheckingEnabled", settings?.CheckingEnabled?.ToString() }, { "TranslationSuggestionsEnabled", settings?.TranslationSuggestionsEnabled?.ToString() }, { "UsersSeeEachOthersResponses", settings?.UsersSeeEachOthersResponses?.ToString() }, diff --git a/src/SIL.XForge.Scripture/Models/DraftConfig.cs b/src/SIL.XForge.Scripture/Models/DraftConfig.cs index 4674ad80251..c2244c50893 100644 --- a/src/SIL.XForge.Scripture/Models/DraftConfig.cs +++ b/src/SIL.XForge.Scripture/Models/DraftConfig.cs @@ -4,12 +4,8 @@ namespace SIL.XForge.Scripture.Models; public class DraftConfig { - public bool AdditionalTrainingSourceEnabled { get; set; } - public TranslateSource? AdditionalTrainingSource { get; set; } - public bool AlternateSourceEnabled { get; set; } - public TranslateSource? AlternateSource { get; set; } - public bool AlternateTrainingSourceEnabled { get; set; } - public TranslateSource? AlternateTrainingSource { get; set; } + public IList DraftingSources { get; set; } = []; + public IList TrainingSources { get; set; } = []; public IList LastSelectedTrainingScriptureRanges { get; set; } = []; public IList LastSelectedTrainingDataFiles { get; set; } = []; public IList LastSelectedTranslationScriptureRanges { get; set; } = []; diff --git a/src/SIL.XForge.Scripture/Models/SFProjectSettings.cs b/src/SIL.XForge.Scripture/Models/SFProjectSettings.cs index f2247a4e26d..66552837a3a 100644 --- a/src/SIL.XForge.Scripture/Models/SFProjectSettings.cs +++ b/src/SIL.XForge.Scripture/Models/SFProjectSettings.cs @@ -13,27 +13,16 @@ public class SFProjectSettings public bool? TranslationSuggestionsEnabled { get; set; } public string? SourceParatextId { get; set; } public bool? BiblicalTermsEnabled { get; set; } - - [Obsolete("For backwards compatibility with older frontend clients. Deprecated November 2024.")] - public bool? TranslateShareEnabled { get; set; } - - // pre-translation settings - [Obsolete("For backwards compatibility with older frontend clients. Deprecated June 2025.")] - public bool? AdditionalTrainingData { get; set; } public IEnumerable? AdditionalTrainingDataFiles { get; set; } - public bool? AdditionalTrainingSourceEnabled { get; set; } - public string? AdditionalTrainingSourceParatextId { get; set; } + + [Obsolete("For backwards compatibility with older frontend clients. Deprecated October 2025.")] public bool? AlternateSourceEnabled { get; set; } - public string? AlternateSourceParatextId { get; set; } - public bool? AlternateTrainingSourceEnabled { get; set; } - public string? AlternateTrainingSourceParatextId { get; set; } + public IEnumerable? DraftingSourcesParatextIds { get; set; } + public IEnumerable? TrainingSourcesParatextIds { get; set; } // checking settings public bool? CheckingEnabled { get; set; } public bool? UsersSeeEachOthersResponses { get; set; } - - [Obsolete("For backwards compatibility with older frontend clients. Deprecated November 2024.")] - public bool? CheckingShareEnabled { get; set; } public string? CheckingAnswerExport { get; set; } public bool? HideCommunityCheckingText { get; set; } diff --git a/src/SIL.XForge.Scripture/Services/MachineApiService.cs b/src/SIL.XForge.Scripture/Services/MachineApiService.cs index 85770f82adc..f549831bce1 100644 --- a/src/SIL.XForge.Scripture/Services/MachineApiService.cs +++ b/src/SIL.XForge.Scripture/Services/MachineApiService.cs @@ -1991,48 +1991,12 @@ await projectDoc.SubmitJson0OpAsync(op => // We use project service, as it provides permission and token checks string jobId = await projectService.SyncAsync(curUserId, buildConfig.ProjectId); - // Store the project ids in a hashset to prevent duplicates - HashSet syncProjectIds = []; - - // If we have an alternate source, sync that first - string alternateSourceProjectId = projectDoc.Data.TranslateConfig.DraftConfig.AlternateSource?.ProjectRef; - if ( - projectDoc.Data.TranslateConfig.DraftConfig.AlternateSourceEnabled - && !string.IsNullOrWhiteSpace(alternateSourceProjectId) - ) - { - syncProjectIds.Add(alternateSourceProjectId); - } - - // If we have an alternate training source, sync that next - string alternateTrainingSourceProjectId = projectDoc - .Data - .TranslateConfig - .DraftConfig - .AlternateTrainingSource - ?.ProjectRef; - if ( - projectDoc.Data.TranslateConfig.DraftConfig.AlternateTrainingSourceEnabled - && !string.IsNullOrWhiteSpace(alternateTrainingSourceProjectId) - ) - { - syncProjectIds.Add(alternateTrainingSourceProjectId); - } - - // If we have an additional training source, sync that next - string additionalTrainingSourceProjectId = projectDoc - .Data - .TranslateConfig - .DraftConfig - .AdditionalTrainingSource - ?.ProjectRef; - if ( - projectDoc.Data.TranslateConfig.DraftConfig.AdditionalTrainingSourceEnabled - && !string.IsNullOrWhiteSpace(additionalTrainingSourceProjectId) - ) - { - syncProjectIds.Add(additionalTrainingSourceProjectId); - } + // Store the project ids to sync in a hashset to prevent duplicates + HashSet syncProjectIds = + [ + .. projectDoc.Data.TranslateConfig.DraftConfig.TrainingSources.Select(s => s.ProjectRef), + .. projectDoc.Data.TranslateConfig.DraftConfig.DraftingSources.Select(s => s.ProjectRef), + ]; // Remove the source project, as it was synced when the target was synced string sourceProjectId = projectDoc.Data.TranslateConfig.Source?.ProjectRef; diff --git a/src/SIL.XForge.Scripture/Services/MachineProjectService.cs b/src/SIL.XForge.Scripture/Services/MachineProjectService.cs index 27a61261322..b59d696c7f4 100644 --- a/src/SIL.XForge.Scripture/Services/MachineProjectService.cs +++ b/src/SIL.XForge.Scripture/Services/MachineProjectService.cs @@ -470,7 +470,7 @@ Uri websiteUrl } /// - /// Updates the language configuration for the additional and alternate sources. + /// Updates the language configuration for the training and drafting sources. /// /// The current user identifier. /// The Scripture Forge project identifier. @@ -492,99 +492,76 @@ public async Task UpdateTranslationSourcesAsync(string curUserId, string sfProje throw new DataNotFoundException("The project does not exist."); } - // If there is an alternate source, ensure that name, writing system and RTL is correct - if (projectDoc.Data.TranslateConfig.DraftConfig.AlternateSource is not null) + // Ensure that name, writing system and RTL are correct for training sources + for (int index = 0; index < projectDoc.Data.TranslateConfig.DraftConfig.TrainingSources.Count; index++) { - ParatextSettings? alternateSourceSettings = paratextService.GetParatextSettings( + // Retrieve the Paratext settings for the training source + ParatextSettings? translateSourceSettings = paratextService.GetParatextSettings( userSecret, - projectDoc.Data.TranslateConfig.DraftConfig.AlternateSource.ParatextId + projectDoc.Data.TranslateConfig.DraftConfig.TrainingSources[index].ParatextId ); - if (alternateSourceSettings is not null) - { - await projectDoc.SubmitJson0OpAsync(op => - { - op.Set( - pd => pd.TranslateConfig.DraftConfig.AlternateSource.IsRightToLeft, - alternateSourceSettings.IsRightToLeft - ); - if (alternateSourceSettings.LanguageTag is not null) - { - op.Set( - pd => pd.TranslateConfig.DraftConfig.AlternateSource.WritingSystem.Tag, - alternateSourceSettings.LanguageTag - ); - } - if (alternateSourceSettings.FullName is not null) - { - op.Set( - pd => pd.TranslateConfig.DraftConfig.AlternateSource.Name, - alternateSourceSettings.FullName - ); - } - }); - } - } - // If there is an alternate training source, ensure that name, writing system and RTL is correct - if (projectDoc.Data.TranslateConfig.DraftConfig.AlternateTrainingSource is not null) - { - ParatextSettings? alternateTrainingSourceSettings = paratextService.GetParatextSettings( - userSecret, - projectDoc.Data.TranslateConfig.DraftConfig.AlternateTrainingSource.ParatextId - ); - if (alternateTrainingSourceSettings is not null) + // Copy the index so that it is captured correctly in the lambda + int pos = index; + if (translateSourceSettings is not null) { await projectDoc.SubmitJson0OpAsync(op => { op.Set( - pd => pd.TranslateConfig.DraftConfig.AlternateTrainingSource.IsRightToLeft, - alternateTrainingSourceSettings.IsRightToLeft + pd => pd.TranslateConfig.DraftConfig.TrainingSources[pos].IsRightToLeft, + translateSourceSettings.IsRightToLeft ); - if (alternateTrainingSourceSettings.LanguageTag is not null) + if (translateSourceSettings.LanguageTag is not null) { op.Set( - pd => pd.TranslateConfig.DraftConfig.AlternateTrainingSource.WritingSystem.Tag, - alternateTrainingSourceSettings.LanguageTag + pd => pd.TranslateConfig.DraftConfig.TrainingSources[pos].WritingSystem.Tag, + translateSourceSettings.LanguageTag ); } - if (alternateTrainingSourceSettings.FullName is not null) + + if (translateSourceSettings.FullName is not null) { op.Set( - pd => pd.TranslateConfig.DraftConfig.AlternateTrainingSource.Name, - alternateTrainingSourceSettings.FullName + pd => pd.TranslateConfig.DraftConfig.TrainingSources[pos].Name, + translateSourceSettings.FullName ); } }); } } - // If there is an additional training source, ensure that name, writing system and RTL is correct - if (projectDoc.Data.TranslateConfig.DraftConfig.AdditionalTrainingSource is not null) + // Ensure that name, writing system and RTL are correct for drafting sources + for (int index = 0; index < projectDoc.Data.TranslateConfig.DraftConfig.DraftingSources.Count; index++) { - ParatextSettings? additionalTrainingSourceSettings = paratextService.GetParatextSettings( + // Retrieve the Paratext settings for the drafting source + ParatextSettings? translateSourceSettings = paratextService.GetParatextSettings( userSecret, - projectDoc.Data.TranslateConfig.DraftConfig.AdditionalTrainingSource.ParatextId + projectDoc.Data.TranslateConfig.DraftConfig.DraftingSources[index].ParatextId ); - if (additionalTrainingSourceSettings is not null) + + // Copy the index so that it is captured correctly in the lambda + int pos = index; + if (translateSourceSettings is not null) { await projectDoc.SubmitJson0OpAsync(op => { op.Set( - pd => pd.TranslateConfig.DraftConfig.AdditionalTrainingSource.IsRightToLeft, - additionalTrainingSourceSettings.IsRightToLeft + pd => pd.TranslateConfig.DraftConfig.DraftingSources[pos].IsRightToLeft, + translateSourceSettings.IsRightToLeft ); - if (additionalTrainingSourceSettings.LanguageTag is not null) + if (translateSourceSettings.LanguageTag is not null) { op.Set( - pd => pd.TranslateConfig.DraftConfig.AdditionalTrainingSource.WritingSystem.Tag, - additionalTrainingSourceSettings.LanguageTag + pd => pd.TranslateConfig.DraftConfig.DraftingSources[pos].WritingSystem.Tag, + translateSourceSettings.LanguageTag ); } - if (additionalTrainingSourceSettings.FullName is not null) + + if (translateSourceSettings.FullName is not null) { op.Set( - pd => pd.TranslateConfig.DraftConfig.AdditionalTrainingSource.Name, - additionalTrainingSourceSettings.FullName + pd => pd.TranslateConfig.DraftConfig.DraftingSources[pos].Name, + translateSourceSettings.FullName ); } }); @@ -1021,7 +998,7 @@ CancellationToken cancellationToken // which is not present until after the first sync (not from the Registry). // If the source or target writing system tag is missing, get them from the ScrText - // We do not need to do this for the alternate source as this would have been populated correctly + // We do not need to do this for the drafting sources as these would have been populated correctly if ( string.IsNullOrWhiteSpace(projectDoc.Data?.WritingSystem.Tag) || ( @@ -1146,15 +1123,11 @@ protected internal virtual string GetSourceLanguage(SFProject? project, bool pre throw new DataNotFoundException("The project does not exist."); } - string alternateSourceLanguage = project.TranslateConfig.DraftConfig.AlternateSource?.WritingSystem.Tag; - bool useAlternateSourceLanguage = - project.TranslateConfig.DraftConfig.AlternateSourceEnabled - && !string.IsNullOrWhiteSpace(alternateSourceLanguage) - && preTranslate; - return useAlternateSourceLanguage - ? alternateSourceLanguage - : project.TranslateConfig.Source?.WritingSystem.Tag - ?? throw new InvalidDataException("The source project's language is not specified."); + return ( + preTranslate + ? project.TranslateConfig.DraftConfig.DraftingSources.FirstOrDefault()?.WritingSystem.Tag + : project.TranslateConfig.Source?.WritingSystem.Tag + ) ?? throw new InvalidDataException("The source project's language is not specified."); } /// @@ -1713,37 +1686,33 @@ out SFProjectSecret projectSecret throw new DataNotFoundException("The translation engine ID cannot be found."); } - // See if there is an alternate source to use for drafting - bool hasAlternateSource = - project.TranslateConfig.DraftConfig.AlternateSourceEnabled - && project.TranslateConfig.DraftConfig.AlternateSource is not null - && project.TranslateConfig.PreTranslate; - - // See if there is an alternate training source corpus - bool hasAlternateTrainingSource = - project.TranslateConfig.DraftConfig.AlternateTrainingSourceEnabled - && project.TranslateConfig.DraftConfig.AlternateTrainingSource is not null - && project.TranslateConfig.PreTranslate; - - // See if there is an additional training source - bool hasAdditionalTrainingSource = - project.TranslateConfig.DraftConfig.AdditionalTrainingSourceEnabled - && project.TranslateConfig.DraftConfig.AdditionalTrainingSource is not null - && project.TranslateConfig.PreTranslate; - - // Ensure we have a source if we are running an SMT build or have a source or an alternate source when NMT - if ((!preTranslate || !hasAlternateSource) && project.TranslateConfig.Source is null) + // Ensure we have a source if we are running an SMT build + if (!preTranslate && project.TranslateConfig.Source is null) { throw new InvalidDataException("The project source is not specified."); } + // Ensure we have drafting and training sources if we are running an NMT build + if ( + preTranslate + && ( + project.TranslateConfig.DraftConfig.DraftingSources.Count == 0 + || project.TranslateConfig.DraftConfig.TrainingSources.Count == 0 + ) + ) + { + throw new InvalidDataException("The drafting or training sources are not specified."); + } + // Build the list of corpora and files to upload List<(string projectId, string paratextId, string writingSystemTag)> projects = [ - // Target Project + // All builds will contain the target project (project.Id, project.ParatextId, project.WritingSystem.Tag), ]; + // Add the source to the corpora, as it may be used in SMT builds if present + // Not including it will result in its deletion from Serval if (project.TranslateConfig.Source is not null) { projects.Add( @@ -1755,38 +1724,15 @@ out SFProjectSecret projectSecret ); } - if (hasAlternateSource) - { - projects.Add( - ( - project.TranslateConfig.DraftConfig.AlternateSource.ProjectRef, - project.TranslateConfig.DraftConfig.AlternateSource.ParatextId, - project.TranslateConfig.DraftConfig.AlternateSource.WritingSystem.Tag + // Add the drafting and training sources to the corpora as they may be used in NMT builds if present + // Not including it will result in its deletion from Serval + projects.AddRange( + project + .TranslateConfig.DraftConfig.DraftingSources.Concat(project.TranslateConfig.DraftConfig.TrainingSources) + .Select(translateSource => + (translateSource.ProjectRef, translateSource.ParatextId, translateSource.WritingSystem.Tag) ) - ); - } - - if (hasAlternateTrainingSource) - { - projects.Add( - ( - project.TranslateConfig.DraftConfig.AlternateTrainingSource.ProjectRef, - project.TranslateConfig.DraftConfig.AlternateTrainingSource.ParatextId, - project.TranslateConfig.DraftConfig.AlternateTrainingSource.WritingSystem.Tag - ) - ); - } - - if (hasAdditionalTrainingSource) - { - projects.Add( - ( - project.TranslateConfig.DraftConfig.AdditionalTrainingSource.ProjectRef, - project.TranslateConfig.DraftConfig.AdditionalTrainingSource.ParatextId, - project.TranslateConfig.DraftConfig.AdditionalTrainingSource.WritingSystem.Tag - ) - ); - } + ); // Create and upload the Serval Corpus Files List servalCorpusFiles = []; @@ -1836,14 +1782,16 @@ await corporaClient.UpdateAsync( ); } - // Get the source project for the NMT/SMT translation corpus - string sourceProjectId = - hasAlternateSource && preTranslate - ? project.TranslateConfig.DraftConfig.AlternateSource.ProjectRef - : project.TranslateConfig.Source.ProjectRef; + // Get the drafting source projects for the NMT/SMT translation corpus + string[] sourceProjectIds = preTranslate + ? [.. project.TranslateConfig.DraftConfig.DraftingSources.Select(s => s.ProjectRef)] + : [project.TranslateConfig.Source!.ProjectRef]; // Set up the parallel corpus for NMT/SMT translation - List sourceCorpora = [servalCorpusFiles.Single(f => f.ProjectId == sourceProjectId)]; + List sourceCorpora = + [ + .. servalCorpusFiles.Where(f => sourceProjectIds.Contains(f.ProjectId)), + ]; List targetCorpora = [servalCorpusFiles.Single(f => f.ProjectId == project.Id)]; List sourceCorpusIds = [.. sourceCorpora.Select(f => f.CorpusId)]; List targetCorpusIds = [.. targetCorpora.Select(f => f.CorpusId)]; @@ -1876,25 +1824,13 @@ await corporaClient.UpdateAsync( ServalAdditionalTrainingData? additionalTrainingData = projectSecret.ServalData.AdditionalTrainingData; if (preTranslate) { - // Build the source corpus ids for training - // First try the alternate training source, then the source, then the source specified for translation above - sourceProjectId = hasAlternateTrainingSource - ? project.TranslateConfig.DraftConfig.AlternateTrainingSource.ProjectRef - : (project.TranslateConfig.Source?.ProjectRef ?? sourceProjectId); - - sourceCorpora = [servalCorpusFiles.Single(f => f.ProjectId == sourceProjectId)]; - - // Add the additional training source, if present and we are pre-translating - if (hasAdditionalTrainingSource) - { - string additionalTrainingSourceProjectId = project - .TranslateConfig - .DraftConfig - .AdditionalTrainingSource - .ProjectRef; - sourceCorpora.Add(servalCorpusFiles.Single(f => f.ProjectId == additionalTrainingSourceProjectId)); - } - + // Build the source corpora for training + sourceCorpora = + [ + .. servalCorpusFiles.Where(f => + project.TranslateConfig.DraftConfig.TrainingSources.Select(s => s.ProjectRef).Contains(f.ProjectId) + ), + ]; sourceCorpusIds = [.. sourceCorpora.Select(f => f.CorpusId)]; // Build the target corpus ids for training diff --git a/src/SIL.XForge.Scripture/Services/SFProjectService.cs b/src/SIL.XForge.Scripture/Services/SFProjectService.cs index 71118182d78..0bc13de48f7 100644 --- a/src/SIL.XForge.Scripture/Services/SFProjectService.cs +++ b/src/SIL.XForge.Scripture/Services/SFProjectService.cs @@ -361,9 +361,7 @@ public async Task UpdateSettingsAsync(string curUserId, string projectId, SFProj { // Throw an exception if obsolete settings are specified #pragma warning disable CS0618 // Type or member is obsolete - if (settings.CheckingShareEnabled is not null) - throw new ForbiddenException(); - if (settings.TranslateShareEnabled is not null) + if (settings.AlternateSourceEnabled is not null) throw new ForbiddenException(); #pragma warning restore CS0618 // Type or member is obsolete @@ -376,20 +374,14 @@ public async Task UpdateSettingsAsync(string curUserId, string projectId, SFProj throw new ForbiddenException(); bool unsetSourceProject = settings.SourceParatextId == ProjectSettingValueUnset; - bool unsetAlternateSourceProject = settings.AlternateSourceParatextId == ProjectSettingValueUnset; - bool unsetAlternateTrainingSourceProject = - settings.AlternateTrainingSourceParatextId == ProjectSettingValueUnset; - bool unsetAdditionalTrainingSourceProject = - settings.AdditionalTrainingSourceParatextId == ProjectSettingValueUnset; - // Get the list of projects for setting the source or alternate source + // Get the list of projects for setting the source, drafting sources, or training sources IReadOnlyList ptProjects = []; IReadOnlyList resources = []; if ( (settings.SourceParatextId != null && !unsetSourceProject) - || (settings.AlternateSourceParatextId != null && !unsetAlternateSourceProject) - || (settings.AlternateTrainingSourceParatextId != null && !unsetAlternateTrainingSourceProject) - || (settings.AdditionalTrainingSourceParatextId != null && !unsetAdditionalTrainingSourceProject) + || settings.DraftingSourcesParatextIds?.Any() == true + || settings.TrainingSourcesParatextIds?.Any() == true ) { Attempt userSecretAttempt = await _userSecrets.TryGetAsync(curUserId); @@ -401,9 +393,8 @@ public async Task UpdateSettingsAsync(string curUserId, string projectId, SFProj // Only get the resources if at least one of the paratext ids is a resource id if ( _paratextService.IsResource(settings.SourceParatextId) - || _paratextService.IsResource(settings.AlternateSourceParatextId) - || _paratextService.IsResource(settings.AlternateTrainingSourceParatextId) - || _paratextService.IsResource(settings.AdditionalTrainingSourceParatextId) + || settings.DraftingSourcesParatextIds?.Any(_paratextService.IsResource) == true + || settings.TrainingSourcesParatextIds?.Any(_paratextService.IsResource) == true ) { resources = await _paratextService.GetResourcesAsync(curUserId); @@ -412,6 +403,7 @@ public async Task UpdateSettingsAsync(string curUserId, string projectId, SFProj // Get the source - any creation or permission updates are handled in GetTranslateSourceAsync TranslateSource source = null; + List sourceParatextIds = []; if (settings.SourceParatextId != null && !unsetSourceProject) { source = await GetTranslateSourceAsync( @@ -425,6 +417,7 @@ public async Task UpdateSettingsAsync(string curUserId, string projectId, SFProj resources, projectDoc.Data.UserRoles ); + sourceParatextIds.Add(source.ParatextId); if (source.ProjectRef == projectId) { // A project cannot reference itself @@ -432,75 +425,55 @@ public async Task UpdateSettingsAsync(string curUserId, string projectId, SFProj } } - // Get the alternate source for pre-translation drafting - TranslateSource alternateSource = null; - if (settings.AlternateSourceParatextId != null && !unsetAlternateSourceProject) - { - alternateSource = await GetTranslateSourceAsync( - conn, - curUserId, - projectId, - settings.AlternateSourceParatextId, - skipSync: false, - // Only update permissions if this project is different to the preceding project - updatePermissions: settings.SourceParatextId != settings.AlternateSourceParatextId, - ptProjects, - resources, - projectDoc.Data.UserRoles - ); - if (alternateSource.ProjectRef == projectId) - { - // A project cannot reference itself - alternateSource = null; - } - } - - // Get the alternate training source for pre-translation drafting - TranslateSource alternateTrainingSource = null; - if (settings.AlternateTrainingSourceParatextId != null && !unsetAlternateTrainingSourceProject) + // Get the drafting sources + List? draftingSources = settings.DraftingSourcesParatextIds is null ? null : []; + foreach (string paratextId in settings.DraftingSourcesParatextIds ?? []) { - alternateTrainingSource = await GetTranslateSourceAsync( + TranslateSource translateSource = await GetTranslateSourceAsync( conn, curUserId, projectId, - settings.AlternateTrainingSourceParatextId, + paratextId, skipSync: false, // Only update permissions if this project is different to the preceding projects - updatePermissions: settings.SourceParatextId != settings.AlternateTrainingSourceParatextId - && settings.AlternateSourceParatextId != settings.AlternateTrainingSourceParatextId, + updatePermissions: !sourceParatextIds.Contains(paratextId), ptProjects, resources, projectDoc.Data.UserRoles ); - if (alternateTrainingSource.ProjectRef == projectId) + + // A project cannot reference itself + if (translateSource.ProjectRef != projectId) { - // A project cannot reference itself - alternateTrainingSource = null; + draftingSources ??= []; + draftingSources.Add(translateSource); + sourceParatextIds.Add(translateSource.ParatextId); } } - // Get the additional training source for pre-translation drafting - TranslateSource additionalTrainingSource = null; - if (settings.AdditionalTrainingSourceParatextId != null && !unsetAdditionalTrainingSourceProject) + // Get the training sources + List? trainingSources = settings.TrainingSourcesParatextIds is null ? null : []; + foreach (string paratextId in settings.TrainingSourcesParatextIds ?? []) { - additionalTrainingSource = await GetTranslateSourceAsync( + TranslateSource translateSource = await GetTranslateSourceAsync( conn, curUserId, projectId, - settings.AdditionalTrainingSourceParatextId, + paratextId, skipSync: false, // Only update permissions if this project is different to the preceding projects - updatePermissions: settings.SourceParatextId != settings.AdditionalTrainingSourceParatextId - && settings.AlternateSourceParatextId != settings.AdditionalTrainingSourceParatextId - && settings.AlternateTrainingSourceParatextId != settings.AdditionalTrainingSourceParatextId, + updatePermissions: !sourceParatextIds.Contains(paratextId), ptProjects, resources, projectDoc.Data.UserRoles ); - if (additionalTrainingSource.ProjectRef == projectId) + + // A project cannot reference itself + if (translateSource.ProjectRef != projectId) { - // A project cannot reference itself - additionalTrainingSource = null; + trainingSources ??= []; + trainingSources.Add(translateSource); + sourceParatextIds.Add(translateSource.ParatextId); } } @@ -514,39 +487,8 @@ await projectDoc.SubmitJson0OpAsync(op => ); UpdateSetting(op, p => p.BiblicalTermsConfig.BiblicalTermsEnabled, settings.BiblicalTermsEnabled); UpdateSetting(op, p => p.TranslateConfig.Source, source, unsetSourceProject); - UpdateSetting( - op, - p => p.TranslateConfig.DraftConfig.AlternateSourceEnabled, - settings.AlternateSourceEnabled - ); - UpdateSetting( - op, - p => p.TranslateConfig.DraftConfig.AlternateSource, - alternateSource, - unsetAlternateSourceProject - ); - UpdateSetting( - op, - p => p.TranslateConfig.DraftConfig.AlternateTrainingSourceEnabled, - settings.AlternateTrainingSourceEnabled - ); - UpdateSetting( - op, - p => p.TranslateConfig.DraftConfig.AlternateTrainingSource, - alternateTrainingSource, - unsetAlternateTrainingSourceProject - ); - UpdateSetting( - op, - p => p.TranslateConfig.DraftConfig.AdditionalTrainingSourceEnabled, - settings.AdditionalTrainingSourceEnabled - ); - UpdateSetting( - op, - p => p.TranslateConfig.DraftConfig.AdditionalTrainingSource, - additionalTrainingSource, - unsetAdditionalTrainingSourceProject - ); + UpdateSetting(op, p => p.TranslateConfig.DraftConfig.TrainingSources, trainingSources); + UpdateSetting(op, p => p.TranslateConfig.DraftConfig.DraftingSources, draftingSources); UpdateSetting( op, p => p.TranslateConfig.DraftConfig.LastSelectedTrainingDataFiles, @@ -1115,25 +1057,14 @@ public SFProjectSecret GetProjectSecretByShareKey(string shareKey) } /// Determine if the specified project is an active source project. - public bool IsSourceProject(string projectId) - { - IQueryable projectQuery = RealtimeService.QuerySnapshots(); - return projectQuery.Any(p => - (p.TranslateConfig.Source != null && (p.TranslateConfig.Source.ProjectRef == projectId)) - || ( - p.TranslateConfig.DraftConfig.AlternateSource != null - && (p.TranslateConfig.DraftConfig.AlternateSource.ProjectRef == projectId) - ) - || ( - p.TranslateConfig.DraftConfig.AlternateTrainingSource != null - && (p.TranslateConfig.DraftConfig.AlternateTrainingSource.ProjectRef == projectId) - ) - || ( - p.TranslateConfig.DraftConfig.AdditionalTrainingSource != null - && (p.TranslateConfig.DraftConfig.AdditionalTrainingSource.ProjectRef == projectId) - ) - ); - } + public bool IsSourceProject(string projectId) => + RealtimeService + .QuerySnapshots() + .Any(p => + (p.TranslateConfig.Source != null && p.TranslateConfig.Source.ProjectRef == projectId) + || p.TranslateConfig.DraftConfig.DraftingSources.Any(s => s.ProjectRef == projectId) + || p.TranslateConfig.DraftConfig.TrainingSources.Any(s => s.ProjectRef == projectId) + ); public async Task> TransceleratorQuestionsAsync( string curUserId, @@ -1361,24 +1292,16 @@ await projectDoc.SubmitJson0OpAsync(op => // Add to the source projects, if required await AddUserToSourceProjectAsync(conn, projectDoc.Data.TranslateConfig.Source, userDoc, shareKey); - await AddUserToSourceProjectAsync( - conn, - projectDoc.Data.TranslateConfig.DraftConfig.AlternateSource, - userDoc, - shareKey - ); - await AddUserToSourceProjectAsync( - conn, - projectDoc.Data.TranslateConfig.DraftConfig.AlternateTrainingSource, - userDoc, - shareKey - ); - await AddUserToSourceProjectAsync( - conn, - projectDoc.Data.TranslateConfig.DraftConfig.AdditionalTrainingSource, - userDoc, - shareKey - ); + IEnumerable translateSources = projectDoc + .Data.TranslateConfig.DraftConfig.DraftingSources.Concat( + projectDoc.Data.TranslateConfig.DraftConfig.TrainingSources + ) + .GroupBy(s => s.ProjectRef) + .Select(g => g.First()); + foreach (TranslateSource translateSource in translateSources) + { + await AddUserToSourceProjectAsync(conn, translateSource, userDoc, shareKey); + } } /// @@ -2093,7 +2016,7 @@ private async Task GetTranslateSourceAsync( } // If the project is created or the last sync was not successful, sync it unless explicitly skipped. - // This is usually because this is an alternate source for drafting + // This is usually because this is a drafting or training source bool syncNeeded = projectCreated || (sourceProject is not null && sourceProject.Sync.LastSyncSuccessful == false); if (syncNeeded && !skipSync) diff --git a/test/SIL.XForge.Scripture.Tests/Controllers/SFProjectsRpcControllerTests.cs b/test/SIL.XForge.Scripture.Tests/Controllers/SFProjectsRpcControllerTests.cs index 58c850556f5..8fec7056db0 100644 --- a/test/SIL.XForge.Scripture.Tests/Controllers/SFProjectsRpcControllerTests.cs +++ b/test/SIL.XForge.Scripture.Tests/Controllers/SFProjectsRpcControllerTests.cs @@ -1203,16 +1203,11 @@ public void UpdateSettings_UnknownError() var env = new TestEnvironment(); var settings = new SFProjectSettings { - AlternateSourceParatextId = string.Empty, BiblicalTermsEnabled = true, CheckingAnswerExport = string.Empty, CheckingEnabled = true, HideCommunityCheckingText = true, SourceParatextId = string.Empty, - AlternateTrainingSourceEnabled = true, - AlternateTrainingSourceParatextId = string.Empty, - AdditionalTrainingSourceEnabled = true, - AdditionalTrainingSourceParatextId = string.Empty, TranslationSuggestionsEnabled = true, UsersSeeEachOthersResponses = true, }; diff --git a/test/SIL.XForge.Scripture.Tests/Services/MachineApiServiceTests.cs b/test/SIL.XForge.Scripture.Tests/Services/MachineApiServiceTests.cs index 05e0bd86ec4..73181a1e3ca 100644 --- a/test/SIL.XForge.Scripture.Tests/Services/MachineApiServiceTests.cs +++ b/test/SIL.XForge.Scripture.Tests/Services/MachineApiServiceTests.cs @@ -3802,7 +3802,7 @@ public async Task StartBuildAsync_Success() } [Test] - public async Task StartPreTranslationBuildAsync_AlternateSource() + public async Task StartPreTranslationBuildAsync_TheSameTrainingAndDraftingSource() { // Set up test environment var env = new TestEnvironment(); @@ -3824,7 +3824,7 @@ await env } [Test] - public async Task StartPreTranslationBuildAsync_AlternateSourceAndAlternateTrainingSource() + public async Task StartPreTranslationBuildAsync_DifferentTrainingAndDraftingSources() { // Set up test environment var env = new TestEnvironment(); @@ -3835,10 +3835,8 @@ await env.Projects.UpdateAsync( s => s.TranslateConfig.DraftConfig, new DraftConfig { - AlternateSourceEnabled = true, - AlternateSource = new TranslateSource { ProjectRef = Project03 }, - AlternateTrainingSourceEnabled = true, - AlternateTrainingSource = new TranslateSource { ProjectRef = Project01 }, + DraftingSources = [new TranslateSource { ProjectRef = Project03 }], + TrainingSources = [new TranslateSource { ProjectRef = Project01 }], } ) ); @@ -3987,74 +3985,6 @@ await env.Service.StartPreTranslationBuildAsync( ); } - [Test] - public async Task StartPreTranslationBuildAsync_AlternateTrainingSource() - { - // Set up test environment - var env = new TestEnvironment(); - await env.Projects.UpdateAsync( - p => p.Id == Project02, - u => - u.Set( - s => s.TranslateConfig.DraftConfig, - new DraftConfig - { - AlternateTrainingSourceEnabled = true, - AlternateTrainingSource = new TranslateSource { ProjectRef = Project01 }, - } - ) - ); - - // SUT - await env.Service.StartPreTranslationBuildAsync( - User01, - new BuildConfig { ProjectId = Project02 }, - CancellationToken.None - ); - - await env - .SyncService.Received(1) - .SyncAsync(Arg.Is(s => s.ProjectId == Project01 && s.TargetOnly && s.UserId == User01)); - env.BackgroundJobClient.Received(1).Create(Arg.Any(), Arg.Any()); - Assert.AreEqual(JobId, env.ProjectSecrets.Get(Project02).ServalData!.PreTranslationJobId); - Assert.IsNotNull(env.ProjectSecrets.Get(Project02).ServalData?.PreTranslationQueuedAt); - Assert.IsNull(env.ProjectSecrets.Get(Project02).ServalData?.PreTranslationErrorMessage); - } - - [Test] - public async Task StartPreTranslationBuildAsync_AdditionalTrainingSource() - { - // Set up test environment - var env = new TestEnvironment(); - await env.Projects.UpdateAsync( - p => p.Id == Project02, - u => - u.Set( - s => s.TranslateConfig.DraftConfig, - new DraftConfig - { - AdditionalTrainingSourceEnabled = true, - AdditionalTrainingSource = new TranslateSource { ProjectRef = Project01 }, - } - ) - ); - - // SUT - await env.Service.StartPreTranslationBuildAsync( - User01, - new BuildConfig { ProjectId = Project02 }, - CancellationToken.None - ); - - await env - .SyncService.Received(1) - .SyncAsync(Arg.Is(s => s.ProjectId == Project01 && s.TargetOnly && s.UserId == User01)); - env.BackgroundJobClient.Received(1).Create(Arg.Any(), Arg.Any()); - Assert.AreEqual(JobId, env.ProjectSecrets.Get(Project02).ServalData!.PreTranslationJobId); - Assert.IsNotNull(env.ProjectSecrets.Get(Project02).ServalData?.PreTranslationQueuedAt); - Assert.IsNull(env.ProjectSecrets.Get(Project02).ServalData?.PreTranslationErrorMessage); - } - [Test] public async Task StartPreTranslationBuildAsync_SavesEchoAndFastTraining() { @@ -4124,12 +4054,12 @@ await env.Projects.UpdateAsync( { DraftConfig = new DraftConfig { - AdditionalTrainingSourceEnabled = true, - AdditionalTrainingSource = new TranslateSource { ProjectRef = Project01 }, - AlternateSourceEnabled = true, - AlternateSource = new TranslateSource { ProjectRef = Project01 }, - AlternateTrainingSourceEnabled = true, - AlternateTrainingSource = new TranslateSource { ProjectRef = Project01 }, + DraftingSources = [new TranslateSource { ProjectRef = Project01 }], + TrainingSources = + [ + new TranslateSource { ProjectRef = Project01 }, + new TranslateSource { ProjectRef = Project01 }, + ], }, } ) @@ -4164,12 +4094,8 @@ await env.Projects.UpdateAsync( { DraftConfig = new DraftConfig { - AdditionalTrainingSourceEnabled = true, - AdditionalTrainingSource = new TranslateSource { ProjectRef = Project01 }, - AlternateSourceEnabled = true, - AlternateSource = new TranslateSource { ProjectRef = Project01 }, - AlternateTrainingSourceEnabled = true, - AlternateTrainingSource = new TranslateSource { ProjectRef = Project01 }, + DraftingSources = [new TranslateSource { ProjectRef = Project01 }], + TrainingSources = [new TranslateSource { ProjectRef = Project01 }], }, Source = new TranslateSource { ProjectRef = Project01 }, } @@ -4653,8 +4579,8 @@ public TestEnvironment() { DraftConfig = new DraftConfig { - AlternateSourceEnabled = true, - AlternateSource = new TranslateSource { ProjectRef = Project03 }, + DraftingSources = [new TranslateSource { ProjectRef = Project03 }], + TrainingSources = [new TranslateSource { ProjectRef = Project03 }], LastSelectedTranslationScriptureRanges = [ new ProjectScriptureRange { ProjectId = Project03, ScriptureRange = "GEN" }, diff --git a/test/SIL.XForge.Scripture.Tests/Services/MachineProjectServiceTests.cs b/test/SIL.XForge.Scripture.Tests/Services/MachineProjectServiceTests.cs index 1ec3f931eb3..69436c1fea4 100644 --- a/test/SIL.XForge.Scripture.Tests/Services/MachineProjectServiceTests.cs +++ b/test/SIL.XForge.Scripture.Tests/Services/MachineProjectServiceTests.cs @@ -671,17 +671,17 @@ public async Task BuildProjectAsync_ThrowsExceptionWhenServalDataMissing() public async Task BuildProjectAsync_UsesTheServalConfigurationSpecifiedByTheServalAdmin() { // Set up test environment - var env = new TestEnvironment(); + var env = new TestEnvironment(new TestEnvironmentOptions { DraftingSources = 1, TrainingSources = 1 }); const string servalConfig = """{"max_steps":35}"""; await env.Projects.UpdateAsync( - Project01, + Project02, op => op.Set(p => p.TranslateConfig.DraftConfig.ServalConfig, servalConfig) ); // SUT await env.Service.BuildProjectAsync( User01, - new BuildConfig { ProjectId = Project01 }, + new BuildConfig { ProjectId = Project02 }, preTranslate: true, CancellationToken.None ); @@ -1427,108 +1427,66 @@ public void GetProjectZipAsync_ThrowsExceptionWhenProjectDocumentMissing() } [Test] - public void GetSourceLanguage_DoesNotUseTheAlternateSourceIfItIsDisabled() + public void GetSourceLanguage_ThrowsExceptionWhenProjectDoesNotHaveASource() { // Set up test environment var env = new TestEnvironment(); - const string alternateSourceWritingSystemTag = "alternate_source_writing_system_tag"; - const string sourceWritingSystemTag = "source_writing_system_tag"; - var project = new SFProject - { - TranslateConfig = - { - DraftConfig = new DraftConfig - { - AlternateSourceEnabled = false, - AlternateSource = new TranslateSource - { - WritingSystem = new WritingSystem { Tag = alternateSourceWritingSystemTag }, - }, - }, - Source = new TranslateSource { WritingSystem = new WritingSystem { Tag = sourceWritingSystemTag } }, - }, - }; + var project = new SFProject { TranslateConfig = { Source = null } }; // SUT - string actual = env.Service.GetSourceLanguage(project, preTranslate: true); - Assert.AreEqual(sourceWritingSystemTag, actual); + Assert.Throws(() => env.Service.GetSourceLanguage(project, preTranslate: false)); } [Test] - public void GetSourceLanguage_DoesNotUseTheAlternateSourceIfItsWritingTagIsEmpty() + public void GetSourceLanguage_ThrowsExceptionWhenProjectNull() { // Set up test environment var env = new TestEnvironment(); - const string sourceWritingSystemTag = "source_writing_system_tag"; - var project = new SFProject - { - TranslateConfig = - { - DraftConfig = new DraftConfig { AlternateSourceEnabled = true, AlternateSource = null }, - Source = new TranslateSource { WritingSystem = new WritingSystem { Tag = sourceWritingSystemTag } }, - }, - }; // SUT - string actual = env.Service.GetSourceLanguage(project, preTranslate: true); - Assert.AreEqual(sourceWritingSystemTag, actual); + Assert.Throws(() => env.Service.GetSourceLanguage(null, preTranslate: true)); } [Test] - public void GetSourceLanguage_UsesTheAlternateSourceIfPreTranslateIsFalse() + public void GetSourceLanguage_ThrowsExceptionWhenTheDraftingSourceDoesNotHaveAWritingTag() { // Set up test environment var env = new TestEnvironment(); - const string alternateSourceWritingSystemTag = "alternate_source_writing_system_tag"; - const string sourceWritingSystemTag = "source_writing_system_tag"; var project = new SFProject { TranslateConfig = { DraftConfig = new DraftConfig { - AlternateSourceEnabled = true, - AlternateSource = new TranslateSource - { - WritingSystem = new WritingSystem { Tag = alternateSourceWritingSystemTag }, - }, + DraftingSources = [new TranslateSource { WritingSystem = new WritingSystem { Tag = null } }], }, - Source = new TranslateSource { WritingSystem = new WritingSystem { Tag = sourceWritingSystemTag } }, }, }; - // SUT - string actual = env.Service.GetSourceLanguage(project, preTranslate: false); - Assert.AreEqual(sourceWritingSystemTag, actual); - } - - [Test] - public void GetSourceLanguage_ThrowsExceptionWhenProjectDoesNotHaveASource() - { - // Set up test environment - var env = new TestEnvironment(); - var project = new SFProject { TranslateConfig = { Source = null } }; - // SUT Assert.Throws(() => env.Service.GetSourceLanguage(project, preTranslate: true)); } [Test] - public void GetSourceLanguage_ThrowsExceptionWhenProjectNull() + public void GetSourceLanguage_ThrowsExceptionWhenTheSourceDoesNotHaveAWritingTag() { // Set up test environment var env = new TestEnvironment(); + var project = new SFProject + { + TranslateConfig = { Source = new TranslateSource { WritingSystem = new WritingSystem { Tag = null } } }, + }; // SUT - Assert.Throws(() => env.Service.GetSourceLanguage(null, preTranslate: true)); + Assert.Throws(() => env.Service.GetSourceLanguage(project, preTranslate: false)); } [Test] - public void GetSourceLanguage_UsesTheAlternateSourceIfItIsEnabledAndConfigured() + public void GetSourceLanguage_UsesTheDraftingSourceIfPreTranslateIsTrue() { // Set up test environment var env = new TestEnvironment(); - const string alternateSourceWritingSystemTag = "alternate_source_writing_system_tag"; + const string draftingSourceWritingSystemTag = "drafting_source_writing_system_tag"; const string sourceWritingSystemTag = "source_writing_system_tag"; var project = new SFProject { @@ -1536,11 +1494,13 @@ public void GetSourceLanguage_UsesTheAlternateSourceIfItIsEnabledAndConfigured() { DraftConfig = new DraftConfig { - AlternateSourceEnabled = true, - AlternateSource = new TranslateSource - { - WritingSystem = new WritingSystem { Tag = alternateSourceWritingSystemTag }, - }, + DraftingSources = + [ + new TranslateSource + { + WritingSystem = new WritingSystem { Tag = draftingSourceWritingSystemTag }, + }, + ], }, Source = new TranslateSource { WritingSystem = new WritingSystem { Tag = sourceWritingSystemTag } }, }, @@ -1548,21 +1508,37 @@ public void GetSourceLanguage_UsesTheAlternateSourceIfItIsEnabledAndConfigured() // SUT string actual = env.Service.GetSourceLanguage(project, preTranslate: true); - Assert.AreEqual(alternateSourceWritingSystemTag, actual); + Assert.AreEqual(draftingSourceWritingSystemTag, actual); } [Test] - public void GetSourceLanguage_ThrowsExceptionWhenTheSourceDoesNotHaveAWritingTag() + public void GetSourceLanguage_UsesTheSourceIfPreTranslateIsFalse() { // Set up test environment var env = new TestEnvironment(); + const string draftingSourceWritingSystemTag = "drafting_source_writing_system_tag"; + const string sourceWritingSystemTag = "source_writing_system_tag"; var project = new SFProject { - TranslateConfig = { Source = new TranslateSource { WritingSystem = new WritingSystem { Tag = null } } }, + TranslateConfig = + { + DraftConfig = new DraftConfig + { + DraftingSources = + [ + new TranslateSource + { + WritingSystem = new WritingSystem { Tag = draftingSourceWritingSystemTag }, + }, + ], + }, + Source = new TranslateSource { WritingSystem = new WritingSystem { Tag = sourceWritingSystemTag } }, + }, }; // SUT - Assert.Throws(() => env.Service.GetSourceLanguage(project, preTranslate: false)); + string actual = env.Service.GetSourceLanguage(project, preTranslate: false); + Assert.AreEqual(sourceWritingSystemTag, actual); } [Test] @@ -3123,19 +3099,29 @@ await env.Projects.UpdateAsync( op.Set(p => p.TranslateConfig.Source.WritingSystem.Tag, "fr_be"); } - if (options.AlternateSource) - { - op.Set(p => p.TranslateConfig.DraftConfig.AlternateSource.WritingSystem.Tag, "fr_ca"); - } - - if (options.AlternateTrainingSource) + switch (options.DraftingSources) { - op.Set(p => p.TranslateConfig.DraftConfig.AlternateTrainingSource.WritingSystem.Tag, "fr_ch"); + case 0: + break; + case 1: + op.Set(p => p.TranslateConfig.DraftConfig.DraftingSources[0].WritingSystem.Tag, "fr_ca"); + break; + default: + throw new NotSupportedException("Unsupported number of drafting sources"); } - if (options.AdditionalTrainingSource) + switch (options.TrainingSources) { - op.Set(p => p.TranslateConfig.DraftConfig.AdditionalTrainingSource.WritingSystem.Tag, "fr_lu"); + case 0: + break; + case 1: + op.Set(p => p.TranslateConfig.DraftConfig.TrainingSources[0].WritingSystem.Tag, "fr_ch"); + break; + case 2: + op.Set(p => p.TranslateConfig.DraftConfig.TrainingSources[1].WritingSystem.Tag, "fr_lu"); + goto case 1; + default: + throw new NotSupportedException("Unsupported number of training sources"); } } ); @@ -3242,11 +3228,10 @@ public async Task SyncProjectCorporaAsync_ThrowsExceptionWhenSourceMissingForSmt } [Test] - public async Task SyncProjectCorporaAsync_ThrowsExceptionWhenNoSourceOrAlternateSourceForNmt() + public async Task SyncProjectCorporaAsync_ThrowsExceptionWhenNoDraftingSourceForNmt() { // Set up test environment var env = new TestEnvironment(); - await env.Projects.UpdateAsync(Project01, op => op.Unset(p => p.TranslateConfig.Source)); await env.SetupProjectSecretAsync(Project01, new ServalData { PreTranslationEngineId = TranslationEngine01 }); // SUT @@ -3485,7 +3470,7 @@ public void TranslationEngineExistsAsync_ThrowsOtherErrors() } [Test] - public async Task UpdateTranslationSourcesAsync_DoesNotUpdateIfNoAlternateSources() + public async Task UpdateTranslationSourcesAsync_DoesNotUpdateIfNoDraftingSources() { // Set up test environment var env = new TestEnvironment(); @@ -3520,35 +3505,7 @@ public void UpdateTranslationSourcesAsync_ThrowsMissingUserSecretException() } [Test] - public async Task UpdateTranslationSourcesAsync_UpdatesAlternateSource() - { - // Set up test environment - var env = new TestEnvironment(); - await env.Projects.UpdateAsync( - p => p.Id == Project01, - u => - u.Set( - s => s.TranslateConfig.DraftConfig, - new DraftConfig - { - AlternateSourceEnabled = true, - AlternateSource = new TranslateSource { ParatextId = Paratext01 }, - } - ) - ); - - // SUT - await env.Service.UpdateTranslationSourcesAsync(User01, Project01); - env.ParatextService.Received(1).GetParatextSettings(Arg.Any(), Paratext01); - Assert.IsTrue(env.Projects.Get(Project01).TranslateConfig.DraftConfig.AlternateSource?.IsRightToLeft); - Assert.AreEqual( - LanguageTag, - env.Projects.Get(Project01).TranslateConfig.DraftConfig.AlternateSource?.WritingSystem.Tag - ); - } - - [Test] - public async Task UpdateTranslationSourcesAsync_UpdatesAlternateTrainingSource() + public async Task UpdateTranslationSourcesAsync_UpdatesDraftingSources() { // Set up test environment var env = new TestEnvironment(); @@ -3557,26 +3514,22 @@ await env.Projects.UpdateAsync( u => u.Set( s => s.TranslateConfig.DraftConfig, - new DraftConfig - { - AlternateTrainingSourceEnabled = true, - AlternateTrainingSource = new TranslateSource { ParatextId = Paratext01 }, - } + new DraftConfig { DraftingSources = [new TranslateSource { ParatextId = Paratext01 }] } ) ); // SUT await env.Service.UpdateTranslationSourcesAsync(User01, Project01); env.ParatextService.Received(1).GetParatextSettings(Arg.Any(), Paratext01); - Assert.IsTrue(env.Projects.Get(Project01).TranslateConfig.DraftConfig.AlternateTrainingSource?.IsRightToLeft); + Assert.IsTrue(env.Projects.Get(Project01).TranslateConfig.DraftConfig.DraftingSources[0].IsRightToLeft); Assert.AreEqual( LanguageTag, - env.Projects.Get(Project01).TranslateConfig.DraftConfig.AlternateTrainingSource?.WritingSystem.Tag + env.Projects.Get(Project01).TranslateConfig.DraftConfig.DraftingSources[0]?.WritingSystem.Tag ); } [Test] - public async Task UpdateTranslationSourcesAsync_UpdatesAdditionalTrainingSource() + public async Task UpdateTranslationSourcesAsync_UpdatesTrainingSources() { // Set up test environment var env = new TestEnvironment(); @@ -3585,21 +3538,17 @@ await env.Projects.UpdateAsync( u => u.Set( s => s.TranslateConfig.DraftConfig, - new DraftConfig - { - AdditionalTrainingSourceEnabled = true, - AdditionalTrainingSource = new TranslateSource { ParatextId = Paratext01 }, - } + new DraftConfig { TrainingSources = [new TranslateSource { ParatextId = Paratext01 }] } ) ); // SUT await env.Service.UpdateTranslationSourcesAsync(User01, Project01); env.ParatextService.Received(1).GetParatextSettings(Arg.Any(), Paratext01); - Assert.IsTrue(env.Projects.Get(Project01).TranslateConfig.DraftConfig.AdditionalTrainingSource!.IsRightToLeft); + Assert.IsTrue(env.Projects.Get(Project01).TranslateConfig.DraftConfig.TrainingSources[0].IsRightToLeft); Assert.AreEqual( LanguageTag, - env.Projects.Get(Project01).TranslateConfig.DraftConfig.AdditionalTrainingSource!.WritingSystem.Tag + env.Projects.Get(Project01).TranslateConfig.DraftConfig.TrainingSources[0].WritingSystem.Tag ); } @@ -3980,24 +3929,23 @@ public static IEnumerable SyncProjectCorporaAsyncOptions { foreach (bool source in boolValues) { - foreach (bool alternateSource in boolValues) + foreach (bool oneDraftingAndTrainingSource in boolValues) { - foreach (bool alternateTrainingSource in boolValues) + foreach (bool twoTrainingSources in boolValues) { - foreach (bool additionalTrainingSource in boolValues) + var options = new TestEnvironmentOptions { - var options = new TestEnvironmentOptions - { - AlternateSource = alternateSource, - AlternateTrainingSource = alternateTrainingSource, - AdditionalTrainingSource = additionalTrainingSource, - PreTranslate = preTranslate, - Source = source, - }; - if (options.WillSucceed) - { - yield return options; - } + DraftingSources = oneDraftingAndTrainingSource ? 1 : 0, + TrainingSources = + twoTrainingSources ? 2 + : oneDraftingAndTrainingSource ? 1 + : 0, + PreTranslate = preTranslate, + Source = source, + }; + if (options.WillSucceed) + { + yield return options; } } } @@ -4006,8 +3954,9 @@ public static IEnumerable SyncProjectCorporaAsyncOptions // Emit special test cases with pre-translate enabled or disabled yield return new TestEnvironmentOptions { - AlternateTrainingSource = true, - AlternateTrainingSourceAndSourceAreTheSame = true, + DraftingSources = 1, + TrainingSources = 1, + DraftingSourceAndTrainingSourceAreTheSame = true, PreTranslate = preTranslate, Source = true, }; @@ -4017,22 +3966,21 @@ public static IEnumerable SyncProjectCorporaAsyncOptions public record TestEnvironmentOptions { - public bool AdditionalTrainingSource { get; init; } - public bool AlternateSource { get; init; } - public bool AlternateTrainingSource { get; init; } - public bool AlternateTrainingSourceAndSourceAreTheSame { get; init; } + public bool DraftingSourceAndTrainingSourceAreTheSame { get; init; } public bool HasTranslationEngineForNmt { get; init; } public bool HasTranslationEngineForSmt { get; init; } public bool LegacyCorpora { get; init; } public bool PreTranslate { get; init; } public bool Source { get; init; } + public int DraftingSources { get; init; } + public int TrainingSources { get; init; } /// /// Determines if the test has the minimum required configuration to succeed. /// - public bool WillSucceed => AlternateSourceOrSourceAndNmt || SourceAndSmt; + public bool WillSucceed => TrainingAndDraftingSourceAndNmt || SourceAndSmt; - private bool AlternateSourceOrSourceAndNmt => (AlternateSource || Source) && PreTranslate; + private bool TrainingAndDraftingSourceAndNmt => DraftingSources > 0 && TrainingSources > 0 && PreTranslate; private bool SourceAndSmt => Source && !PreTranslate; } @@ -4226,42 +4174,56 @@ public TestEnvironment(TestEnvironmentOptions? options = null) : null, DraftConfig = new DraftConfig { - AlternateSourceEnabled = options.AlternateSource, - AlternateSource = options.AlternateSource - ? new TranslateSource - { - ProjectRef = Project03, - ParatextId = Paratext03, - WritingSystem = new WritingSystem { Tag = "en_GB" }, - } - : null, - AlternateTrainingSourceEnabled = options.AlternateTrainingSource, - AlternateTrainingSource = options.AlternateTrainingSource - ? new TranslateSource - { - ProjectRef = options.AlternateTrainingSourceAndSourceAreTheSame - ? Project01 - : Project04, - ParatextId = options.AlternateTrainingSourceAndSourceAreTheSame - ? Paratext01 - : Paratext04, - WritingSystem = new WritingSystem { Tag = "en_GB" }, - } - : null, - AdditionalTrainingSourceEnabled = options.AdditionalTrainingSource, - AdditionalTrainingSource = options.AdditionalTrainingSource - ? new TranslateSource - { - ProjectRef = Project05, - ParatextId = Paratext05, - WritingSystem = new WritingSystem { Tag = "en_GB" }, - } - : null, + DraftingSources = + options.DraftingSources == 1 + ? + [ + new TranslateSource + { + ProjectRef = Project03, + ParatextId = Paratext03, + WritingSystem = new WritingSystem { Tag = "en_GB" }, + }, + ] + : [], + TrainingSources = + options.TrainingSources > 0 + ? options.TrainingSources > 1 + ? + [ + new TranslateSource + { + ProjectRef = options.DraftingSourceAndTrainingSourceAreTheSame + ? Project03 + : Project04, + ParatextId = options.DraftingSourceAndTrainingSourceAreTheSame + ? Paratext03 + : Paratext04, + WritingSystem = new WritingSystem { Tag = "en_GB" }, + }, + new TranslateSource + { + ProjectRef = Project05, + ParatextId = Paratext05, + WritingSystem = new WritingSystem { Tag = "en_GB" }, + }, + ] + : + [ + new TranslateSource + { + ProjectRef = options.DraftingSourceAndTrainingSourceAreTheSame + ? Project03 + : Project04, + ParatextId = options.DraftingSourceAndTrainingSourceAreTheSame + ? Paratext03 + : Paratext04, + WritingSystem = new WritingSystem { Tag = "en_GB" }, + }, + ] + : [], }, - PreTranslate = - options.AlternateSource - || options.AlternateTrainingSource - || options.AdditionalTrainingSource, + PreTranslate = options.DraftingSources > 0 || options.TrainingSources > 0, }, WritingSystem = new WritingSystem { Tag = "en_US" }, }, @@ -4377,7 +4339,7 @@ await CorporaClient .CreateAsync(Arg.Is(c => c.Name == $"{Project02}_{Project02}")); Assert.AreEqual(options.PreTranslate ? 2 : 1, actual.Count(s => s.ProjectId == Project02)); - // Source + // SMT Source if (options.Source) { await CorporaClient @@ -4386,31 +4348,17 @@ await CorporaClient numberOfServalCorpusFiles++; // See how many times the source corpus was used in the parallel corpora - int expected = options switch - { - { PreTranslate: false } => 1, - { - PreTranslate: true, - AlternateTrainingSource: true, - AlternateTrainingSourceAndSourceAreTheSame: true, - } => 2, - { PreTranslate: true, AlternateTrainingSource: true, AlternateSource: true } => 0, - { PreTranslate: true, AlternateTrainingSource: true } => 1, - { PreTranslate: true, AlternateSource: true } => 1, - { PreTranslate: true } => 2, - }; - Assert.AreEqual(expected, actual.Count(s => s.ProjectId == Project01)); + Assert.AreEqual(options.PreTranslate ? 0 : 1, actual.Count(s => s.ProjectId == Project01)); } - // Alternate Source - if (options.AlternateSource) + // Drafting Source + if (options.DraftingSources > 0) { int expected = options switch { { PreTranslate: false } => 0, - { PreTranslate: true, AlternateTrainingSource: true } => 1, - { PreTranslate: true, Source: true } => 1, - { PreTranslate: true } => 2, + { PreTranslate: true, DraftingSourceAndTrainingSourceAreTheSame: true } => 2, + { PreTranslate: true } => 1, }; await CorporaClient .Received(createsServalCorpora ? 1 : 0) @@ -4419,9 +4367,9 @@ await CorporaClient numberOfServalCorpusFiles++; } - // Alternate Training Source + // One Training Source // This can be used to test that a duplicate corpus and file were not uploaded - if (options.AlternateTrainingSource && !options.AlternateTrainingSourceAndSourceAreTheSame) + if (options.TrainingSources > 0 && !options.DraftingSourceAndTrainingSourceAreTheSame) { await CorporaClient .Received(createsServalCorpora ? 1 : 0) @@ -4430,8 +4378,8 @@ await CorporaClient numberOfServalCorpusFiles++; } - // Additional Training Source - if (options.AdditionalTrainingSource) + // A Second Training Source + if (options.TrainingSources > 1) { await CorporaClient .Received(createsServalCorpora ? 1 : 0) diff --git a/test/SIL.XForge.Scripture.Tests/Services/SFProjectServiceTests.cs b/test/SIL.XForge.Scripture.Tests/Services/SFProjectServiceTests.cs index 36cc4389de9..75d96ce84ae 100644 --- a/test/SIL.XForge.Scripture.Tests/Services/SFProjectServiceTests.cs +++ b/test/SIL.XForge.Scripture.Tests/Services/SFProjectServiceTests.cs @@ -1946,6 +1946,43 @@ public async Task AddUserAsync_HasSourceProjectRole_AddedToSourceProject() Assert.That(user.Sites[SiteId].Projects, Is.EquivalentTo(new[] { Project01, Project03, SourceOnly })); } + [Test] + public async Task AddUserAsync_HasDraftingAndTrainingProjectRole_AddedToDraftingProject() + { + var env = new TestEnvironment(); + SFProject project03 = env.GetProject(Project03); + TranslateSource translateSource = project03.TranslateConfig.Source; + await env + .RealtimeService.GetRepository() + .UpdateAsync( + Project03, + u => + { + u.Set(p => p.TranslateConfig.DraftConfig.DraftingSources, [translateSource]); + u.Set(p => p.TranslateConfig.DraftConfig.TrainingSources, [translateSource]); + u.Unset(p => p.TranslateConfig.Source); + } + ); + + // Ensure the projects are correctly configured + SFProject source = env.GetProject(SourceOnly); + Assert.That(project03.UserRoles.ContainsKey(User03), Is.False, "setup"); + Assert.That(source.UserRoles.ContainsKey(User03), Is.False, "setup"); + User user = env.GetUser(User03); + Assert.That(user.Sites[SiteId].Projects, Is.EquivalentTo(new[] { Project01 })); + env.ParatextService.TryGetProjectRoleAsync(Arg.Any(), Arg.Any(), CancellationToken.None) + .Returns(Task.FromResult(Attempt.Success(SFProjectRole.Translator))); + + // SUT + await env.Service.AddUserAsync(User03, Project03, SFProjectRole.Translator); + project03 = env.GetProject(Project03); + source = env.GetProject(SourceOnly); + Assert.That(project03.UserRoles.ContainsKey(User03)); + Assert.That(source.UserRoles.ContainsKey(User03)); + user = env.GetUser(User03); + Assert.That(user.Sites[SiteId].Projects, Is.EquivalentTo(new[] { Project01, Project03, SourceOnly })); + } + [Test] public async Task ReserveLinkSharingKeyAsync_MarkAsReserved() { @@ -2256,7 +2293,7 @@ public async Task UpdatePermissionsAsync_SetsResourcePermissions() } [Test] - public async Task IsSourceProject_TrueWhenProjectIsAnAlternateSource() + public async Task IsSourceProject_TrueWhenProjectIsDraftingSource() { var env = new TestEnvironment(); const string paratextId = "paratext_" + Project01; @@ -2265,7 +2302,7 @@ public async Task IsSourceProject_TrueWhenProjectIsAnAlternateSource() await env.Service.UpdateSettingsAsync( User01, Project03, - new SFProjectSettings { AlternateSourceParatextId = paratextId } + new SFProjectSettings { DraftingSourcesParatextIds = [paratextId] } ); // SUT @@ -2273,7 +2310,7 @@ await env.Service.UpdateSettingsAsync( } [Test] - public async Task IsSourceProject_TrueWhenProjectIsAnAlternateTrainingSource() + public async Task IsSourceProject_TrueWhenProjectIsATrainingSource() { var env = new TestEnvironment(); const string paratextId = "paratext_" + Project01; @@ -2282,7 +2319,7 @@ public async Task IsSourceProject_TrueWhenProjectIsAnAlternateTrainingSource() await env.Service.UpdateSettingsAsync( User01, Project03, - new SFProjectSettings { AlternateTrainingSourceParatextId = paratextId } + new SFProjectSettings { TrainingSourcesParatextIds = [paratextId] } ); // SUT @@ -2290,24 +2327,7 @@ await env.Service.UpdateSettingsAsync( } [Test] - public async Task IsSourceProject_TrueWhenProjectIsAnAdditionalTrainingSourceProject() - { - var env = new TestEnvironment(); - const string paratextId = "paratext_" + Project01; - Assert.That(env.Service.IsSourceProject(Project01), Is.False); - - await env.Service.UpdateSettingsAsync( - User01, - Project03, - new SFProjectSettings { AdditionalTrainingSourceParatextId = paratextId } - ); - - // SUT - Assert.That(env.Service.IsSourceProject(Project01), Is.True); - } - - [Test] - public void IsSourceProject_TrueWhenProjectIsATranslationSource() + public void IsSourceProject_TrueWhenProjectIsASourceProject() { var env = new TestEnvironment(); Assert.That(env.Service.IsSourceProject(Resource01), Is.True); @@ -2336,34 +2356,30 @@ await env.Service.UpdateSettingsAsync( Project01, new SFProjectSettings { - AdditionalTrainingSourceParatextId = newResourceParatextId, - AlternateSourceParatextId = newResourceParatextId, - AlternateTrainingSourceParatextId = newResourceParatextId, + DraftingSourcesParatextIds = [newResourceParatextId], + TrainingSourcesParatextIds = [newResourceParatextId], } ); SFProject project = env.GetProject(Project01); - Assert.That(project.TranslateConfig.DraftConfig.AdditionalTrainingSource?.ProjectRef, Is.Not.Null); + Assert.That(project.TranslateConfig.DraftConfig.DraftingSources[0].ProjectRef, Is.Not.Null); Assert.That( - project.TranslateConfig.DraftConfig.AdditionalTrainingSource?.ParatextId, + project.TranslateConfig.DraftConfig.DraftingSources[0].ParatextId, Is.EqualTo(newResourceParatextId) ); - Assert.That(project.TranslateConfig.DraftConfig.AdditionalTrainingSource?.Name, Is.EqualTo("ResourceProject")); - Assert.That(project.TranslateConfig.DraftConfig.AlternateSource?.ProjectRef, Is.Not.Null); - Assert.That(project.TranslateConfig.DraftConfig.AlternateSource?.ParatextId, Is.EqualTo(newResourceParatextId)); - Assert.That(project.TranslateConfig.DraftConfig.AlternateSource?.Name, Is.EqualTo("ResourceProject")); - Assert.That(project.TranslateConfig.DraftConfig.AlternateTrainingSource?.ProjectRef, Is.Not.Null); + Assert.That(project.TranslateConfig.DraftConfig.DraftingSources[0].Name, Is.EqualTo("ResourceProject")); + Assert.That(project.TranslateConfig.DraftConfig.TrainingSources[0].ProjectRef, Is.Not.Null); Assert.That( - project.TranslateConfig.DraftConfig.AlternateTrainingSource?.ParatextId, + project.TranslateConfig.DraftConfig.TrainingSources[0].ParatextId, Is.EqualTo(newResourceParatextId) ); - Assert.That(project.TranslateConfig.DraftConfig.AlternateTrainingSource?.Name, Is.EqualTo("ResourceProject")); + Assert.That(project.TranslateConfig.DraftConfig.TrainingSources[0].Name, Is.EqualTo("ResourceProject")); - SFProject alternateSourceProject = env.GetProject( - project.TranslateConfig.DraftConfig.AlternateSource!.ProjectRef + SFProject draftingSourceProject = env.GetProject( + project.TranslateConfig.DraftConfig.DraftingSources[0].ProjectRef ); - Assert.That(alternateSourceProject.ParatextId, Is.EqualTo(newResourceParatextId)); - Assert.That(alternateSourceProject.Name, Is.EqualTo("ResourceProject")); + Assert.That(draftingSourceProject.ParatextId, Is.EqualTo(newResourceParatextId)); + Assert.That(draftingSourceProject.Name, Is.EqualTo("ResourceProject")); await env .MachineProjectService.DidNotReceive() @@ -2385,37 +2401,16 @@ await env } [Test] - public async Task UpdateSettingsAsync_ChangeAlternateSource_CannotUseTargetProject() - { - var env = new TestEnvironment(); - const string paratextId = "paratext_" + Project01; - - await env.Service.UpdateSettingsAsync( - User01, - Project01, - new SFProjectSettings { AlternateSourceParatextId = paratextId } - ); - - SFProject project = env.GetProject(Project01); - Assert.That(project.ParatextId, Is.EqualTo(paratextId)); - Assert.That(project.TranslateConfig.DraftConfig.AlternateSource?.ProjectRef, Is.Null); - Assert.That(project.TranslateConfig.DraftConfig.AlternateSource?.ParatextId, Is.Null); - Assert.That(project.TranslateConfig.DraftConfig.AlternateSource?.Name, Is.Null); - - await env - .MachineProjectService.DidNotReceive() - .RemoveProjectAsync(Arg.Any(), Arg.Any(), Arg.Any()); - await env - .MachineProjectService.DidNotReceive() - .AddSmtProjectAsync(Arg.Any(), Arg.Any()); - await env.SyncService.DidNotReceive().SyncAsync(Arg.Any()); - } - - [Test] - public async Task UpdateSettingsAsync_ChangeAlternateSource_CreatesProject() + public async Task UpdateSettingsAsync_ChangeDraftingSources_AnotherUserOnTheProjectCannotRefreshToken() { var env = new TestEnvironment(); const string newProjectParatextId = "changedId"; + env.ParatextService.TryGetProjectRoleAsync( + Arg.Is(u => u.Id == User02), + newProjectParatextId, + CancellationToken.None + ) + .ThrowsAsync(new UnauthorizedAccessException()); // Ensure that the new project does not exist Assert.That( @@ -2427,30 +2422,32 @@ public async Task UpdateSettingsAsync_ChangeAlternateSource_CreatesProject() await env.Service.UpdateSettingsAsync( User01, Project01, - new SFProjectSettings { AlternateSourceParatextId = newProjectParatextId } + new SFProjectSettings { DraftingSourcesParatextIds = [newProjectParatextId] } ); + // Verify the drafting source property of the target project SFProject project = env.GetProject(Project01); - Assert.That(project.TranslateConfig.DraftConfig.AlternateSource?.ProjectRef, Is.Not.Null); - Assert.That(project.TranslateConfig.DraftConfig.AlternateSource?.ParatextId, Is.EqualTo(newProjectParatextId)); - Assert.That(project.TranslateConfig.DraftConfig.AlternateSource?.Name, Is.EqualTo("NewSource")); + Assert.That(project.TranslateConfig.DraftConfig.DraftingSources[0].ProjectRef, Is.Not.Null); + Assert.That( + project.TranslateConfig.DraftConfig.DraftingSources[0].ParatextId, + Is.EqualTo(newProjectParatextId) + ); + Assert.That(project.TranslateConfig.DraftConfig.DraftingSources[0].Name, Is.EqualTo("NewSource")); + Assert.That(project.UserRoles, Contains.Key(User02)); - SFProject alternateSourceProject = env.GetProject( - project.TranslateConfig.DraftConfig.AlternateSource!.ProjectRef + // Verify the project document that was created for the drafting source + SFProject draftingSourceProject = env.GetProject( + project.TranslateConfig.DraftConfig.DraftingSources[0].ProjectRef ); - Assert.That(alternateSourceProject.ParatextId, Is.EqualTo(newProjectParatextId)); - Assert.That(alternateSourceProject.Name, Is.EqualTo("NewSource")); + Assert.That(draftingSourceProject.ParatextId, Is.EqualTo(newProjectParatextId)); + Assert.That(draftingSourceProject.Name, Is.EqualTo("NewSource")); + Assert.That(draftingSourceProject.UserRoles, Does.Not.ContainKey(User02)); - await env - .MachineProjectService.DidNotReceive() - .RemoveProjectAsync(Arg.Any(), Arg.Any(), Arg.Any()); - await env - .MachineProjectService.DidNotReceive() - .AddSmtProjectAsync(Arg.Any(), Arg.Any()); + // Verify that a sync is scheduled await env.SyncService.Received().SyncAsync(Arg.Any()); env.BackgroundJobClient.Received(1).Create(Arg.Any(), Arg.Any()); - // Check that the project was created + // Verify that the drafting source project was created Assert.That( env.RealtimeService.GetRepository().Query().Any(p => p.ParatextId == newProjectParatextId), Is.True @@ -2458,41 +2455,7 @@ await env } [Test] - public async Task UpdateSettingsAsync_ChangeAlternateSource_SyncProjectWhenSyncFailed() - { - var env = new TestEnvironment(); - const string newProjectParatextId = ResourceNeedsSyncPTId; - - // Ensure that the new project does not exist - Assert.That( - env.RealtimeService.GetRepository().Query().Any(p => p.ParatextId == newProjectParatextId), - Is.True - ); - - // SUT - await env.Service.UpdateSettingsAsync( - User01, - Project01, - new SFProjectSettings { AlternateSourceParatextId = newProjectParatextId } - ); - - SFProject project = env.GetProject(Project01); - Assert.That(project.TranslateConfig.DraftConfig.AlternateSource?.ProjectRef, Is.Not.Null); - Assert.That(project.TranslateConfig.DraftConfig.AlternateSource?.ParatextId, Is.EqualTo(newProjectParatextId)); - Assert.That(project.TranslateConfig.DraftConfig.AlternateSource?.Name, Is.EqualTo("Resource Needs Sync")); - - SFProject alternateSourceProject = env.GetProject( - project.TranslateConfig.DraftConfig.AlternateSource!.ProjectRef - ); - Assert.That(alternateSourceProject.ParatextId, Is.EqualTo(newProjectParatextId)); - Assert.That(alternateSourceProject.Name, Is.EqualTo("Resource Needs Sync")); - - await env.SyncService.Received(1).SyncAsync(Arg.Any()); - env.BackgroundJobClient.Received().Create(Arg.Any(), Arg.Any()); - } - - [Test] - public async Task UpdateSettingsAsync_ChangeAlternateTrainingSource_CannotUseTargetProject() + public async Task UpdateSettingsAsync_ChangeDraftingSources_CannotUseTargetProject() { var env = new TestEnvironment(); const string paratextId = "paratext_" + Project01; @@ -2500,14 +2463,12 @@ public async Task UpdateSettingsAsync_ChangeAlternateTrainingSource_CannotUseTar await env.Service.UpdateSettingsAsync( User01, Project01, - new SFProjectSettings { AlternateTrainingSourceParatextId = paratextId } + new SFProjectSettings { DraftingSourcesParatextIds = [paratextId] } ); SFProject project = env.GetProject(Project01); Assert.That(project.ParatextId, Is.EqualTo(paratextId)); - Assert.That(project.TranslateConfig.DraftConfig.AlternateTrainingSource?.ProjectRef, Is.Null); - Assert.That(project.TranslateConfig.DraftConfig.AlternateTrainingSource?.ParatextId, Is.Null); - Assert.That(project.TranslateConfig.DraftConfig.AlternateTrainingSource?.Name, Is.Null); + Assert.That(project.TranslateConfig.DraftConfig.DraftingSources, Is.Empty); await env .MachineProjectService.DidNotReceive() @@ -2519,7 +2480,7 @@ await env } [Test] - public async Task UpdateSettingsAsync_ChangeAlternateTrainingSource_CreatesProject() + public async Task UpdateSettingsAsync_ChangeDraftingSources_CreatesProject() { var env = new TestEnvironment(); const string newProjectParatextId = "changedId"; @@ -2530,22 +2491,26 @@ public async Task UpdateSettingsAsync_ChangeAlternateTrainingSource_CreatesProje Is.False ); + // SUT await env.Service.UpdateSettingsAsync( User01, Project01, - new SFProjectSettings { AlternateTrainingSourceParatextId = "changedId" } + new SFProjectSettings { DraftingSourcesParatextIds = [newProjectParatextId] } ); SFProject project = env.GetProject(Project01); - Assert.That(project.TranslateConfig.DraftConfig.AlternateTrainingSource?.ProjectRef, Is.Not.Null); - Assert.That(project.TranslateConfig.DraftConfig.AlternateTrainingSource?.ParatextId, Is.EqualTo("changedId")); - Assert.That(project.TranslateConfig.DraftConfig.AlternateTrainingSource?.Name, Is.EqualTo("NewSource")); + Assert.That(project.TranslateConfig.DraftConfig.DraftingSources[0].ProjectRef, Is.Not.Null); + Assert.That( + project.TranslateConfig.DraftConfig.DraftingSources[0].ParatextId, + Is.EqualTo(newProjectParatextId) + ); + Assert.That(project.TranslateConfig.DraftConfig.DraftingSources[0].Name, Is.EqualTo("NewSource")); - SFProject alternateTrainingSourceProject = env.GetProject( - project.TranslateConfig.DraftConfig.AlternateTrainingSource!.ProjectRef + SFProject draftingSourceProject = env.GetProject( + project.TranslateConfig.DraftConfig.DraftingSources[0].ProjectRef ); - Assert.That(alternateTrainingSourceProject.ParatextId, Is.EqualTo("changedId")); - Assert.That(alternateTrainingSourceProject.Name, Is.EqualTo("NewSource")); + Assert.That(draftingSourceProject.ParatextId, Is.EqualTo(newProjectParatextId)); + Assert.That(draftingSourceProject.Name, Is.EqualTo("NewSource")); await env .MachineProjectService.DidNotReceive() @@ -2564,86 +2529,69 @@ await env } [Test] - public async Task UpdateSettingsAsync_ChangeAdditionalTrainingSource_CannotUseTargetProject() + public async Task UpdateSettingsAsync_ChangeDraftingSources_SyncProjectWhenSyncFailed() { var env = new TestEnvironment(); - const string paratextId = "paratext_" + Project01; + const string newProjectParatextId = ResourceNeedsSyncPTId; + + // Ensure that the resource project exists in the database + Assert.That( + env.RealtimeService.GetRepository().Query().Any(p => p.ParatextId == newProjectParatextId), + Is.True + ); + // SUT await env.Service.UpdateSettingsAsync( User01, Project01, - new SFProjectSettings { AdditionalTrainingSourceParatextId = paratextId } + new SFProjectSettings { DraftingSourcesParatextIds = [newProjectParatextId] } ); SFProject project = env.GetProject(Project01); - Assert.That(project.ParatextId, Is.EqualTo(paratextId)); - Assert.That(project.TranslateConfig.DraftConfig.AdditionalTrainingSource, Is.Null); + Assert.That(project.TranslateConfig.DraftConfig.DraftingSources[0].ProjectRef, Is.Not.Null); + Assert.That( + project.TranslateConfig.DraftConfig.DraftingSources[0].ParatextId, + Is.EqualTo(newProjectParatextId) + ); + Assert.That(project.TranslateConfig.DraftConfig.DraftingSources[0].Name, Is.EqualTo("Resource Needs Sync")); - await env - .MachineProjectService.DidNotReceive() - .RemoveProjectAsync(Arg.Any(), Arg.Any(), Arg.Any()); - await env - .MachineProjectService.DidNotReceive() - .AddSmtProjectAsync(Arg.Any(), Arg.Any()); - await env.SyncService.DidNotReceive().SyncAsync(Arg.Any()); + SFProject draftingSourceProject = env.GetProject( + project.TranslateConfig.DraftConfig.DraftingSources[0].ProjectRef + ); + Assert.That(draftingSourceProject.ParatextId, Is.EqualTo(newProjectParatextId)); + Assert.That(draftingSourceProject.Name, Is.EqualTo("Resource Needs Sync")); + + await env.SyncService.Received(1).SyncAsync(Arg.Any()); + env.BackgroundJobClient.Received().Create(Arg.Any(), Arg.Any()); } [Test] - public async Task UpdateSettingsAsync_ChangeAlternateTrainingSource_AnotherUserOnTheProjectCannotRefreshToken() + public async Task UpdateSettingsAsync_ChangeTrainingSources_CannotUseTargetProject() { var env = new TestEnvironment(); - const string newProjectParatextId = "changedId"; - env.ParatextService.TryGetProjectRoleAsync( - Arg.Is(u => u.Id == User02), - newProjectParatextId, - CancellationToken.None - ) - .ThrowsAsync(new UnauthorizedAccessException()); - - // Ensure that the new project does not exist - Assert.That( - env.RealtimeService.GetRepository().Query().Any(p => p.ParatextId == newProjectParatextId), - Is.False - ); + const string paratextId = "paratext_" + Project01; - // SUT await env.Service.UpdateSettingsAsync( User01, Project01, - new SFProjectSettings { AlternateTrainingSourceParatextId = newProjectParatextId } + new SFProjectSettings { TrainingSourcesParatextIds = [paratextId] } ); - // Verify the alternate training source property of the target project SFProject project = env.GetProject(Project01); - Assert.That(project.TranslateConfig.DraftConfig.AlternateTrainingSource?.ProjectRef, Is.Not.Null); - Assert.That( - project.TranslateConfig.DraftConfig.AlternateTrainingSource?.ParatextId, - Is.EqualTo(newProjectParatextId) - ); - Assert.That(project.TranslateConfig.DraftConfig.AlternateTrainingSource?.Name, Is.EqualTo("NewSource")); - Assert.That(project.UserRoles, Contains.Key(User02)); - - // Verify the project document that was created for the alternate training source - SFProject alternateTrainingSourceProject = env.GetProject( - project.TranslateConfig.DraftConfig.AlternateTrainingSource!.ProjectRef - ); - Assert.That(alternateTrainingSourceProject.ParatextId, Is.EqualTo(newProjectParatextId)); - Assert.That(alternateTrainingSourceProject.Name, Is.EqualTo("NewSource")); - Assert.That(alternateTrainingSourceProject.UserRoles, Does.Not.ContainKey(User02)); - - // Verify that a sync is scheduled - await env.SyncService.Received().SyncAsync(Arg.Any()); - env.BackgroundJobClient.Received(1).Create(Arg.Any(), Arg.Any()); + Assert.That(project.ParatextId, Is.EqualTo(paratextId)); + Assert.That(project.TranslateConfig.DraftConfig.TrainingSources, Is.Empty); - // Verify that the alternate training source project was created - Assert.That( - env.RealtimeService.GetRepository().Query().Any(p => p.ParatextId == newProjectParatextId), - Is.True - ); + await env + .MachineProjectService.DidNotReceive() + .RemoveProjectAsync(Arg.Any(), Arg.Any(), Arg.Any()); + await env + .MachineProjectService.DidNotReceive() + .AddSmtProjectAsync(Arg.Any(), Arg.Any()); + await env.SyncService.DidNotReceive().SyncAsync(Arg.Any()); } [Test] - public async Task UpdateSettingsAsync_ChangeAdditionalTrainingSource_CreatesProject() + public async Task UpdateSettingsAsync_ChangeTrainingSources_CreatesProject() { var env = new TestEnvironment(); const string newProjectParatextId = "changedId"; @@ -2654,26 +2602,25 @@ public async Task UpdateSettingsAsync_ChangeAdditionalTrainingSource_CreatesProj Is.False ); - // SUT await env.Service.UpdateSettingsAsync( User01, Project01, - new SFProjectSettings { AdditionalTrainingSourceParatextId = newProjectParatextId } + new SFProjectSettings { TrainingSourcesParatextIds = [newProjectParatextId] } ); SFProject project = env.GetProject(Project01); - Assert.That(project.TranslateConfig.DraftConfig.AdditionalTrainingSource!.ProjectRef, Is.Not.Null); + Assert.That(project.TranslateConfig.DraftConfig.TrainingSources[0].ProjectRef, Is.Not.Null); Assert.That( - project.TranslateConfig.DraftConfig.AdditionalTrainingSource.ParatextId, + project.TranslateConfig.DraftConfig.TrainingSources[0].ParatextId, Is.EqualTo(newProjectParatextId) ); - Assert.That(project.TranslateConfig.DraftConfig.AdditionalTrainingSource.Name, Is.EqualTo("NewSource")); + Assert.That(project.TranslateConfig.DraftConfig.TrainingSources[0].Name, Is.EqualTo("NewSource")); - SFProject additionalTrainingSourceProject = env.GetProject( - project.TranslateConfig.DraftConfig.AdditionalTrainingSource.ProjectRef + SFProject trainingSourceProject = env.GetProject( + project.TranslateConfig.DraftConfig.TrainingSources[0].ProjectRef ); - Assert.That(additionalTrainingSourceProject.ParatextId, Is.EqualTo(newProjectParatextId)); - Assert.That(additionalTrainingSourceProject.Name, Is.EqualTo("NewSource")); + Assert.That(trainingSourceProject.ParatextId, Is.EqualTo(newProjectParatextId)); + Assert.That(trainingSourceProject.Name, Is.EqualTo("NewSource")); await env .MachineProjectService.DidNotReceive() @@ -2692,72 +2639,16 @@ await env } [Test] - public async Task UpdateSettingsAsync_EnableAdditionalTrainingSource_NoSync() - { - var env = new TestEnvironment(); - - await env.Service.UpdateSettingsAsync( - User01, - Project01, - new SFProjectSettings { AdditionalTrainingSourceEnabled = true } - ); - - SFProject project = env.GetProject(Project01); - Assert.That(project.TranslateConfig.DraftConfig.AdditionalTrainingSourceEnabled, Is.True); - - await env - .MachineProjectService.DidNotReceive() - .RemoveProjectAsync(Arg.Any(), Arg.Any(), Arg.Any()); - await env - .MachineProjectService.DidNotReceive() - .AddSmtProjectAsync(Arg.Any(), Arg.Any()); - await env.SyncService.DidNotReceive().SyncAsync(Arg.Any()); - } - - [Test] - public async Task UpdateSettingsAsync_EnableAlternateSource_NoSync() - { - var env = new TestEnvironment(); - - await env.Service.UpdateSettingsAsync( - User01, - Project01, - new SFProjectSettings { AlternateSourceEnabled = true } - ); - - SFProject project = env.GetProject(Project01); - Assert.That(project.TranslateConfig.DraftConfig.AlternateSourceEnabled, Is.True); - - await env - .MachineProjectService.DidNotReceive() - .RemoveProjectAsync(Arg.Any(), Arg.Any(), Arg.Any()); - await env - .MachineProjectService.DidNotReceive() - .AddSmtProjectAsync(Arg.Any(), Arg.Any()); - await env.SyncService.DidNotReceive().SyncAsync(Arg.Any()); - } - - [Test] - public async Task UpdateSettingsAsync_EnableAlternateTrainingSource_NoSync() + public void UpdateSettingsAsync_AlternateSourceEnabled_Forbidden() { var env = new TestEnvironment(); - await env.Service.UpdateSettingsAsync( - User01, - Project01, - new SFProjectSettings { AlternateTrainingSourceEnabled = true } + // SUT +#pragma warning disable CS0618 // Type or member is obsolete + Assert.ThrowsAsync(() => + env.Service.UpdateSettingsAsync(User01, Project01, new SFProjectSettings { AlternateSourceEnabled = true }) ); - - SFProject project = env.GetProject(Project01); - Assert.That(project.TranslateConfig.DraftConfig.AlternateTrainingSourceEnabled, Is.True); - - await env - .MachineProjectService.DidNotReceive() - .RemoveProjectAsync(Arg.Any(), Arg.Any(), Arg.Any()); - await env - .MachineProjectService.DidNotReceive() - .AddSmtProjectAsync(Arg.Any(), Arg.Any()); - await env.SyncService.DidNotReceive().SyncAsync(Arg.Any()); +#pragma warning restore CS0618 // Type or member is obsolete } [Test] @@ -2947,32 +2838,6 @@ await env await env.SyncService.Received().SyncAsync(Arg.Any()); } - [Test] - public void UpdateSettingsAsync_CheckingShareEnabled_Forbidden() - { - var env = new TestEnvironment(); - - // SUT -#pragma warning disable CS0618 // Type or member is obsolete - Assert.ThrowsAsync(() => - env.Service.UpdateSettingsAsync(User01, Project01, new SFProjectSettings { CheckingShareEnabled = true }) - ); -#pragma warning restore CS0618 // Type or member is obsolete - } - - [Test] - public void UpdateSettingsAsync_TranslateShareEnabled_Forbidden() - { - var env = new TestEnvironment(); - - // SUT -#pragma warning disable CS0618 // Type or member is obsolete - Assert.ThrowsAsync(() => - env.Service.UpdateSettingsAsync(User01, Project01, new SFProjectSettings { TranslateShareEnabled = true }) - ); -#pragma warning restore CS0618 // Type or member is obsolete - } - [Test] public async Task DeleteProjectAsync_MissingProject() {