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",