diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/serval-projects.component.html b/src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/serval-projects.component.html index 58ca27eac8..94b7671e6c 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/serval-projects.component.html +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/serval-projects.component.html @@ -4,6 +4,9 @@ +Projects with custom serval configs only @if (!isLoading) { @if (length > 0) { 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 1ca66aa697..4da46569ec 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 @@ -1,7 +1,10 @@ +import { HarnessLoader } from '@angular/cdk/testing'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; import { provideHttpClientTesting } from '@angular/common/http/testing'; import { DebugElement, getDebugNode } from '@angular/core'; import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { MatCheckboxHarness } from '@angular/material/checkbox/testing'; import { By } from '@angular/platform-browser'; import { provideNoopAnimations } from '@angular/platform-browser/animations'; import { provideRouter } from '@angular/router'; @@ -91,6 +94,23 @@ describe('ServalProjectsComponent', () => { expect(env.rows.length).toEqual(1); })); + it('should filter projects with custom serval config set', fakeAsync(async () => { + const env = new TestEnvironment(); + env.setupProjectData(); + env.fixture.detectChanges(); + tick(); + env.fixture.detectChanges(); + + expect(env.rows.length).toEqual(3); + // Toggle the checkbox to filter projects with servalConfig + await env.toggleServalConfigFilter(); + // Only project01 has servalConfig set, so only 1 row should be displayed + expect(env.rows.length).toEqual(1); + + await env.toggleServalConfigFilter(); + expect(env.rows.length).toEqual(3); + })); + it('should page', fakeAsync(() => { const env = new TestEnvironment(); env.setupProjectData(); @@ -116,6 +136,7 @@ class TestProjectDoc extends ProjectDoc { class TestEnvironment { readonly component: ServalProjectsComponent; readonly fixture: ComponentFixture; + readonly loader: HarnessLoader; private readonly realtimeService: TestRealtimeService = TestBed.inject(TestRealtimeService); @@ -139,6 +160,7 @@ class TestEnvironment { this.fixture = TestBed.createComponent(ServalProjectsComponent); this.component = this.fixture.componentInstance; + this.loader = TestbedHarnessEnvironment.loader(this.fixture); } get table(): DebugElement { @@ -181,6 +203,14 @@ class TestEnvironment { this.fixture.detectChanges(); } + async toggleServalConfigFilter(): Promise { + const checkbox = await this.loader.getHarness(MatCheckboxHarness); + await checkbox.toggle(); + this.fixture.detectChanges(); + tick(); + this.fixture.detectChanges(); + } + setupProjectData(): void { this.realtimeService.addSnapshots(TestProjectDoc.COLLECTION, [ { @@ -204,7 +234,8 @@ class TestEnvironment { name: 'Project 04', shortName: 'P4' } - ] + ], + servalConfig: '{ "custom": "value" }' }, preTranslate: true, source: { diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/serval-projects.component.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/serval-projects.component.ts index e92439c759..ee83b24456 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/serval-projects.component.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/serval-projects.component.ts @@ -1,5 +1,7 @@ import { Component, DestroyRef, OnInit } from '@angular/core'; +import { FormsModule } from '@angular/forms'; import { MatButton } from '@angular/material/button'; +import { MatCheckbox } from '@angular/material/checkbox'; import { MatFormField, MatLabel } from '@angular/material/form-field'; import { MatIcon } from '@angular/material/icon'; import { MatInput } from '@angular/material/input'; @@ -83,6 +85,7 @@ class Row { templateUrl: './serval-projects.component.html', styleUrls: ['./serval-projects.component.scss'], imports: [ + FormsModule, MatButton, MatTable, MatColumnDef, @@ -90,6 +93,7 @@ class Row { MatHeaderCellDef, MatCell, MatCellDef, + MatCheckbox, MatHeaderRow, MatHeaderRowDef, MatIcon, @@ -109,6 +113,7 @@ export class ServalProjectsComponent extends DataLoadingComponent implements OnI length: number = 0; pageIndex: number = 0; pageSize: number = 50; + showProjectsWithCustomServalConfig: boolean = false; private projectDocs?: Readonly; @@ -160,6 +165,11 @@ export class ServalProjectsComponent extends DataLoadingComponent implements OnI this.queryParameters$.next(this.getQueryParameters()); } + updateServalConfigFilter(): void { + this.pageIndex = 0; + this.queryParameters$.next(this.getQueryParameters()); + } + viewDraftJobs(projectId: string): void { void this.router.navigate(['/serval-administration'], { queryParams: { @@ -182,12 +192,19 @@ export class ServalProjectsComponent extends DataLoadingComponent implements OnI } private getQueryParameters(): QueryParameters { - return { + const params: QueryParameters = { // Do not return resources [obj().pathStr(q => q.resourceConfig)]: null, $sort: { [obj().pathStr(p => p.name)]: 1 }, $skip: this.pageIndex * this.pageSize, $limit: this.pageSize }; + + // Filter for projects with servalConfig set + if (this.showProjectsWithCustomServalConfig) { + params[obj().pathStr(q => q.translateConfig.draftConfig?.servalConfig)] = { $ne: null }; + } + + return params; } } diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/_draft-generation-steps-theme.scss b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/_draft-generation-steps-theme.scss index cccb67e65e..590a9356e7 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/_draft-generation-steps-theme.scss +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/_draft-generation-steps-theme.scss @@ -8,6 +8,8 @@ --mat-stepper-header-icon-foreground-color: #{mat.get-theme-color($theme, neutral, if($is-dark, 90, 98))}; --mat-stepper-header-edit-state-icon-background-color: #{mat.get-theme-color($theme, primary, if($is-dark, 30, 10))}; --mat-stepper-header-edit-state-icon-foreground-color: #{mat.get-theme-color($theme, neutral, if($is-dark, 90, 98))}; + --mat-stepper-link-color: #{mat.get-theme-color($theme, primary, if($is-dark, 60, 50))}; + --mat-stepper-link-hover-color: #{mat.get-theme-color($theme, primary, if($is-dark, 70, 60))}; } @mixin theme($theme) { diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/draft-generation-steps.component.html b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/draft-generation-steps.component.html index 9398f805dd..4c233423dd 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/draft-generation-steps.component.html +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/draft-generation-steps.component.html @@ -318,6 +318,16 @@

{{ selectedTranslateBooksAsString() }}
+ @if (servalConfig != null) { +
+ {{ + showCustomConfig ? t("hide_custom_serval_config") : t("show_custom_config") + }} +
+ @if (showCustomConfig) { +
{{ servalConfig | json }}
+ } + }

{{ t("summary_notifications") }}

@if (currentUserEmail != null) { diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/draft-generation-steps.component.scss b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/draft-generation-steps.component.scss index fc81554d07..e7abe02be5 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/draft-generation-steps.component.scss +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/draft-generation-steps.component.scss @@ -159,3 +159,11 @@ app-notice { --mat-table-row-item-outline-width: 0; padding-top: 16px; } + +.serval-config-toggle { + color: var(--mat-stepper-link-color); + :hover { + cursor: pointer; + color: var(--mat-stepper-link-hover-color); + } +} 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 ff26417853..e3293228b3 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 @@ -1346,7 +1346,8 @@ describe('DraftGenerationStepsComponent', () => { { projectId: 'source1', scriptureRange: 'LEV;NUM;DEU;JOS' }, { projectId: 'source2', scriptureRange: 'DEU;JOS;1SA' } ], - lastSelectedTranslationScriptureRanges: [{ projectId: 'draftingSource', scriptureRange: 'GEN;EXO' }] + lastSelectedTranslationScriptureRanges: [{ projectId: 'draftingSource', scriptureRange: 'GEN;EXO' }], + servalConfig: '{ "custom": "value" }' } } }) @@ -1385,6 +1386,10 @@ describe('DraftGenerationStepsComponent', () => { expect(trainingGroups[1].ranges[0]).toEqual('Deuteronomy'); expect(trainingGroups[1].ranges[1]).toEqual('1 Samuel'); }); + + it('records the custom serval config', () => { + expect(component['servalConfig']).toEqual('{ "custom": "value" }'); + }); }); describe('pending updates', () => { 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 65ba8a4bc0..b7de446ea0 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 @@ -1,3 +1,4 @@ +import { JsonPipe } from '@angular/common'; import { Component, DestroyRef, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { MatButton } from '@angular/material/button'; @@ -117,7 +118,8 @@ interface ProjectPendingUpdate { TranslocoModule, TranslocoMarkupModule, BookMultiSelectComponent, - ConfirmSourcesComponent + ConfirmSourcesComponent, + JsonPipe ] }) export class DraftGenerationStepsComponent implements OnInit { @@ -153,6 +155,7 @@ export class DraftGenerationStepsComponent implements OnInit { expandUnusableTranslateBooks = false; expandUnusableTrainingBooks = false; isStepsCompleted = false; + showCustomConfig = false; protected nextClickedOnLanguageVerification = false; protected hasLoaded = false; @@ -161,6 +164,7 @@ export class DraftGenerationStepsComponent implements OnInit { protected trainingSources: DraftSource[] = []; protected trainingTargets: DraftSource[] = []; protected trainingDataFiles: Readonly[] = []; + protected servalConfig?: string; private sourceProgress: Map = new Map(); @@ -296,6 +300,7 @@ export class DraftGenerationStepsComponent implements OnInit { // See if there is an existing training scripture range const draftConfig: DraftConfig | undefined = this.activatedProject.projectDoc?.data?.translateConfig.draftConfig; + this.servalConfig = draftConfig?.servalConfig; const hasPreviousTrainingRange: boolean = (draftConfig?.lastSelectedTrainingScriptureRanges ?? []).length > 0; diff --git a/src/SIL.XForge.Scripture/ClientApp/src/assets/i18n/non_checking_en.json b/src/SIL.XForge.Scripture/ClientApp/src/assets/i18n/non_checking_en.json index 83a7e0b2c6..d86793dbc9 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/assets/i18n/non_checking_en.json +++ b/src/SIL.XForge.Scripture/ClientApp/src/assets/i18n/non_checking_en.json @@ -246,6 +246,7 @@ "fast_training_warning": "Enabling this will save time but greatly reduce accuracy.", "fast_training": "Enable Fast Training", "generate_draft": "Generate draft", + "hide_custom_serval_config": "Hide custom serval configuration", "loading": "Loading...", "loading_projects": "Loading project sync status...", "next": "Next", @@ -256,6 +257,7 @@ "reference_books": "Reference books", "remote_changes": "The source configuration for draft generation has been modified. Please start a new draft and review the latest source configuration.", "remote_changes_start_over": "Start over", + "show_custom_config": "Show custom serval configuration", "summary_header": "Summary", "summary_notifications": "Notifications", "summary_title": "Summary",