Skip to content

Commit a82a151

Browse files
committed
SF-3649 Add UI to filter projects with custom serval config
1 parent 142f019 commit a82a151

File tree

9 files changed

+87
-4
lines changed

9 files changed

+87
-4
lines changed

src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/serval-projects.component.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
<input matInput (keyup)="updateSearchTerm($event.target)" id="project-filter" />
55
</mat-form-field>
66
</div>
7+
<mat-checkbox [(ngModel)]="showProjectsWithCustomServalConfig" (change)="updateServalConfigFilter()"
8+
>Projects with custom serval configs only</mat-checkbox
9+
>
710
@if (!isLoading) {
811
@if (length > 0) {
912
<table mat-table id="projects-table" [dataSource]="rows">

src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/serval-projects.component.spec.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import { HarnessLoader } from '@angular/cdk/testing';
2+
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
13
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
24
import { provideHttpClientTesting } from '@angular/common/http/testing';
35
import { DebugElement, getDebugNode } from '@angular/core';
46
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
7+
import { MatCheckboxHarness } from '@angular/material/checkbox/testing';
58
import { By } from '@angular/platform-browser';
69
import { provideNoopAnimations } from '@angular/platform-browser/animations';
710
import { provideRouter } from '@angular/router';
@@ -91,6 +94,23 @@ describe('ServalProjectsComponent', () => {
9194
expect(env.rows.length).toEqual(1);
9295
}));
9396

97+
it('should filter projects with custom serval config set', fakeAsync(async () => {
98+
const env = new TestEnvironment();
99+
env.setupProjectData();
100+
env.fixture.detectChanges();
101+
tick();
102+
env.fixture.detectChanges();
103+
104+
expect(env.rows.length).toEqual(3);
105+
// Toggle the checkbox to filter projects with servalConfig
106+
await env.toggleServalConfigFilter();
107+
// Only project01 has servalConfig set, so only 1 row should be displayed
108+
expect(env.rows.length).toEqual(1);
109+
110+
await env.toggleServalConfigFilter();
111+
expect(env.rows.length).toEqual(3);
112+
}));
113+
94114
it('should page', fakeAsync(() => {
95115
const env = new TestEnvironment();
96116
env.setupProjectData();
@@ -116,6 +136,7 @@ class TestProjectDoc extends ProjectDoc {
116136
class TestEnvironment {
117137
readonly component: ServalProjectsComponent;
118138
readonly fixture: ComponentFixture<ServalProjectsComponent>;
139+
readonly loader: HarnessLoader;
119140

120141
private readonly realtimeService: TestRealtimeService = TestBed.inject<TestRealtimeService>(TestRealtimeService);
121142

@@ -139,6 +160,7 @@ class TestEnvironment {
139160

140161
this.fixture = TestBed.createComponent(ServalProjectsComponent);
141162
this.component = this.fixture.componentInstance;
163+
this.loader = TestbedHarnessEnvironment.loader(this.fixture);
142164
}
143165

144166
get table(): DebugElement {
@@ -181,6 +203,14 @@ class TestEnvironment {
181203
this.fixture.detectChanges();
182204
}
183205

206+
async toggleServalConfigFilter(): Promise<void> {
207+
const checkbox = await this.loader.getHarness(MatCheckboxHarness);
208+
await checkbox.toggle();
209+
this.fixture.detectChanges();
210+
tick();
211+
this.fixture.detectChanges();
212+
}
213+
184214
setupProjectData(): void {
185215
this.realtimeService.addSnapshots<SFProject>(TestProjectDoc.COLLECTION, [
186216
{
@@ -204,7 +234,8 @@ class TestEnvironment {
204234
name: 'Project 04',
205235
shortName: 'P4'
206236
}
207-
]
237+
],
238+
servalConfig: '{ "custom": "value" }'
208239
},
209240
preTranslate: true,
210241
source: {

src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/serval-projects.component.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { Component, DestroyRef, OnInit } from '@angular/core';
2+
import { FormsModule } from '@angular/forms';
23
import { MatButton } from '@angular/material/button';
4+
import { MatCheckbox } from '@angular/material/checkbox';
35
import { MatFormField, MatLabel } from '@angular/material/form-field';
46
import { MatIcon } from '@angular/material/icon';
57
import { MatInput } from '@angular/material/input';
@@ -83,13 +85,15 @@ class Row {
8385
templateUrl: './serval-projects.component.html',
8486
styleUrls: ['./serval-projects.component.scss'],
8587
imports: [
88+
FormsModule,
8689
MatButton,
8790
MatTable,
8891
MatColumnDef,
8992
MatHeaderCell,
9093
MatHeaderCellDef,
9194
MatCell,
9295
MatCellDef,
96+
MatCheckbox,
9397
MatHeaderRow,
9498
MatHeaderRowDef,
9599
MatIcon,
@@ -109,6 +113,7 @@ export class ServalProjectsComponent extends DataLoadingComponent implements OnI
109113
length: number = 0;
110114
pageIndex: number = 0;
111115
pageSize: number = 50;
116+
showProjectsWithCustomServalConfig: boolean = false;
112117

113118
private projectDocs?: Readonly<SFProjectProfileDoc[]>;
114119

@@ -160,6 +165,11 @@ export class ServalProjectsComponent extends DataLoadingComponent implements OnI
160165
this.queryParameters$.next(this.getQueryParameters());
161166
}
162167

168+
updateServalConfigFilter(): void {
169+
this.pageIndex = 0;
170+
this.queryParameters$.next(this.getQueryParameters());
171+
}
172+
163173
viewDraftJobs(projectId: string): void {
164174
void this.router.navigate(['/serval-administration'], {
165175
queryParams: {
@@ -182,12 +192,19 @@ export class ServalProjectsComponent extends DataLoadingComponent implements OnI
182192
}
183193

184194
private getQueryParameters(): QueryParameters {
185-
return {
195+
const params: QueryParameters = {
186196
// Do not return resources
187197
[obj<SFProject>().pathStr(q => q.resourceConfig)]: null,
188198
$sort: { [obj<Project>().pathStr(p => p.name)]: 1 },
189199
$skip: this.pageIndex * this.pageSize,
190200
$limit: this.pageSize
191201
};
202+
203+
// Filter for projects with servalConfig set
204+
if (this.showProjectsWithCustomServalConfig) {
205+
params[obj<SFProject>().pathStr(q => q.translateConfig.draftConfig?.servalConfig)] = { $ne: null };
206+
}
207+
208+
return params;
192209
}
193210
}

src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/_draft-generation-steps-theme.scss

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
--mat-stepper-header-icon-foreground-color: #{mat.get-theme-color($theme, neutral, if($is-dark, 90, 98))};
99
--mat-stepper-header-edit-state-icon-background-color: #{mat.get-theme-color($theme, primary, if($is-dark, 30, 10))};
1010
--mat-stepper-header-edit-state-icon-foreground-color: #{mat.get-theme-color($theme, neutral, if($is-dark, 90, 98))};
11+
--mat-stepper-link-color: #{mat.get-theme-color($theme, primary, if($is-dark, 60, 50))};
12+
--mat-stepper-link-hover-color: #{mat.get-theme-color($theme, primary, if($is-dark, 70, 60))};
1113
}
1214

1315
@mixin theme($theme) {

src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/draft-generation-steps.component.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,16 @@ <h2>
318318
<div class="confirm-translate confirm-books">
319319
{{ selectedTranslateBooksAsString() }}
320320
</div>
321+
@if (servalConfig != null) {
322+
<div class="serval-config-toggle">
323+
<u (click)="showCustomConfig = !showCustomConfig">{{
324+
showCustomConfig ? t("hide_custom_serval_config") : t("show_custom_config")
325+
}}</u>
326+
</div>
327+
@if (showCustomConfig) {
328+
<pre class="custom-config">{{ servalConfig | json }}</pre>
329+
}
330+
}
321331
</mat-card>
322332
<h2>{{ t("summary_notifications") }}</h2>
323333
@if (currentUserEmail != null) {

src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/draft-generation-steps.component.scss

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,11 @@ app-notice {
159159
--mat-table-row-item-outline-width: 0;
160160
padding-top: 16px;
161161
}
162+
163+
.serval-config-toggle {
164+
color: var(--mat-stepper-link-color);
165+
:hover {
166+
cursor: pointer;
167+
color: var(--mat-stepper-link-hover-color);
168+
}
169+
}

src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/draft-generation-steps.component.spec.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1346,7 +1346,8 @@ describe('DraftGenerationStepsComponent', () => {
13461346
{ projectId: 'source1', scriptureRange: 'LEV;NUM;DEU;JOS' },
13471347
{ projectId: 'source2', scriptureRange: 'DEU;JOS;1SA' }
13481348
],
1349-
lastSelectedTranslationScriptureRanges: [{ projectId: 'draftingSource', scriptureRange: 'GEN;EXO' }]
1349+
lastSelectedTranslationScriptureRanges: [{ projectId: 'draftingSource', scriptureRange: 'GEN;EXO' }],
1350+
servalConfig: '{ "custom": "value" }'
13501351
}
13511352
}
13521353
})
@@ -1385,6 +1386,10 @@ describe('DraftGenerationStepsComponent', () => {
13851386
expect(trainingGroups[1].ranges[0]).toEqual('Deuteronomy');
13861387
expect(trainingGroups[1].ranges[1]).toEqual('1 Samuel');
13871388
});
1389+
1390+
it('records the custom serval config', () => {
1391+
expect(component['servalConfig']).toEqual('{ "custom": "value" }');
1392+
});
13881393
});
13891394

13901395
describe('pending updates', () => {

src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/draft-generation-steps.component.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { JsonPipe } from '@angular/common';
12
import { Component, DestroyRef, EventEmitter, OnInit, Output, ViewChild } from '@angular/core';
23
import { FormsModule } from '@angular/forms';
34
import { MatButton } from '@angular/material/button';
@@ -117,7 +118,8 @@ interface ProjectPendingUpdate {
117118
TranslocoModule,
118119
TranslocoMarkupModule,
119120
BookMultiSelectComponent,
120-
ConfirmSourcesComponent
121+
ConfirmSourcesComponent,
122+
JsonPipe
121123
]
122124
})
123125
export class DraftGenerationStepsComponent implements OnInit {
@@ -153,6 +155,7 @@ export class DraftGenerationStepsComponent implements OnInit {
153155
expandUnusableTranslateBooks = false;
154156
expandUnusableTrainingBooks = false;
155157
isStepsCompleted = false;
158+
showCustomConfig = false;
156159

157160
protected nextClickedOnLanguageVerification = false;
158161
protected hasLoaded = false;
@@ -161,6 +164,7 @@ export class DraftGenerationStepsComponent implements OnInit {
161164
protected trainingSources: DraftSource[] = [];
162165
protected trainingTargets: DraftSource[] = [];
163166
protected trainingDataFiles: Readonly<TrainingData>[] = [];
167+
protected servalConfig?: string;
164168

165169
private sourceProgress: Map<string, TextProgress[]> = new Map<string, TextProgress[]>();
166170

@@ -296,6 +300,7 @@ export class DraftGenerationStepsComponent implements OnInit {
296300
// See if there is an existing training scripture range
297301
const draftConfig: DraftConfig | undefined =
298302
this.activatedProject.projectDoc?.data?.translateConfig.draftConfig;
303+
this.servalConfig = draftConfig?.servalConfig;
299304
const hasPreviousTrainingRange: boolean =
300305
(draftConfig?.lastSelectedTrainingScriptureRanges ?? []).length > 0;
301306

src/SIL.XForge.Scripture/ClientApp/src/assets/i18n/non_checking_en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@
246246
"fast_training_warning": "Enabling this will save time but greatly reduce accuracy.",
247247
"fast_training": "Enable Fast Training",
248248
"generate_draft": "Generate draft",
249+
"hide_custom_serval_config": "Hide custom serval configuration",
249250
"loading": "Loading...",
250251
"loading_projects": "Loading project sync status...",
251252
"next": "Next",
@@ -256,6 +257,7 @@
256257
"reference_books": "Reference books",
257258
"remote_changes": "The source configuration for draft generation has been modified. Please start a new draft and review the latest source configuration.",
258259
"remote_changes_start_over": "Start over",
260+
"show_custom_config": "Show custom serval configuration",
259261
"summary_header": "Summary",
260262
"summary_notifications": "Notifications",
261263
"summary_title": "Summary",

0 commit comments

Comments
 (0)