Skip to content

Commit

Permalink
SF-3162 Warn user when training and drafting sources are different
Browse files Browse the repository at this point in the history
  • Loading branch information
RaymondLuong3 committed Feb 14, 2025
1 parent 56609f8 commit 386f574
Show file tree
Hide file tree
Showing 16 changed files with 573 additions and 973 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,8 @@ <h2>{{ t("drafting_from_heading", { draftingSourceShortNames, draftingSourceLang
}
</div>

<app-notice mode="outline">
<strong>{{ t("incorrect_language_codes_reduce_quality") }}</strong>
<p>{{ t("how_to_change_language_codes") }}</p>
<mat-checkbox (change)="confirmationChanged($event)">{{ t("confirm_lang_codes_correct") }}</mat-checkbox>
</app-notice>
<app-language-codes-confirmation
class="confirm-codes"
(languageCodesVerified)="confirmationChanged($event)"
></app-language-codes-confirmation>
</ng-container>
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ h2 {
font-size: 0.9em;
}

app-notice {
margin-top: 4em;
.confirm-codes {
margin-top: 2em;
display: block;
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { Component, DestroyRef, EventEmitter, OnInit, Output } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatCheckboxChange, MatCheckboxModule } from '@angular/material/checkbox';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatIconModule } from '@angular/material/icon';
import { TranslocoModule } from '@ngneat/transloco';
import { SFProjectProfile } from 'realtime-server/lib/esm/scriptureforge/models/sf-project';
import { TranslateSource } from 'realtime-server/lib/esm/scriptureforge/models/translate-config';
import { I18nService } from 'xforge-common/i18n.service';
import { NoticeComponent } from '../../../shared/notice/notice.component';
import { DraftSourcesService } from '../draft-sources.service';
import { LanguageCodesConfirmationComponent } from '../language-codes-confirmation/language-codes-confirmation.component';

@Component({
selector: 'app-confirm-sources',
standalone: true,
imports: [TranslocoModule, NoticeComponent, MatCheckboxModule, MatIconModule],
imports: [TranslocoModule, MatCheckboxModule, MatIconModule, LanguageCodesConfirmationComponent],
templateUrl: './confirm-sources.component.html',
styleUrl: './confirm-sources.component.scss'
})
Expand All @@ -29,6 +29,22 @@ export class ConfirmSourcesComponent implements OnInit {
private readonly draftSourcesService: DraftSourcesService
) {}

get referenceLanguage(): string {
return this.displayNameForProjectsLanguages(this.trainingSources);
}

get targetLanguage(): string {
return this.displayNameForProjectsLanguages(this.trainingTargets);
}

get draftingSourceLanguage(): string {
return this.displayNameForProjectsLanguages(this.draftingSources);
}

get draftingSourceShortNames(): string {
return this.i18nService.enumerateList(this.draftingSources.filter(p => p != null).map(p => p.shortName));
}

ngOnInit(): void {
this.draftSourcesService
.getDraftProjectSources()
Expand All @@ -40,29 +56,13 @@ export class ConfirmSourcesComponent implements OnInit {
});
}

confirmationChanged(change: MatCheckboxChange): void {
this.languageCodesVerified.emit(change.checked);
confirmationChanged(checked: boolean): void {
this.languageCodesVerified.emit(checked);
}

displayNameForProjectsLanguages(projects: (TranslateSource | SFProjectProfile | undefined)[]): string {
const uniqueTags = Array.from(new Set(projects.filter(p => p != null).map(p => p.writingSystem.tag)));
const displayNames = uniqueTags.map(tag => this.i18nService.getLanguageDisplayName(tag) ?? tag);
return this.i18nService.enumerateList(displayNames);
}

get referenceLanguage(): string {
return this.displayNameForProjectsLanguages(this.trainingSources);
}

get targetLanguage(): string {
return this.displayNameForProjectsLanguages(this.trainingTargets);
}

get draftingSourceLanguage(): string {
return this.displayNameForProjectsLanguages(this.draftingSources);
}

get draftingSourceShortNames(): string {
return this.i18nService.enumerateList(this.draftingSources.filter(p => p != null).map(p => p.shortName));
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import { CommonModule } from '@angular/common';
import { TranslocoModule } from '@ngneat/transloco';
import { Meta, moduleMetadata, StoryObj } from '@storybook/angular';
import { SFProjectRole } from 'realtime-server/lib/esm/scriptureforge/models/sf-project-role';
import { createTestProjectProfile } from 'realtime-server/lib/esm/scriptureforge/models/sf-project-test-data';
import { of } from 'rxjs';
import { instance, mock, when } from 'ts-mockito';
import { ActivatedProjectService } from 'xforge-common/activated-project.service';
import { AuthService } from 'xforge-common/auth.service';
import { SFProjectProfileDoc } from '../../../core/models/sf-project-profile-doc';
import { DraftSource, DraftSourcesAsArrays, DraftSourcesService } from '../draft-sources.service';
import { LanguageCodesConfirmationComponent } from '../language-codes-confirmation/language-codes-confirmation.component';
import { ConfirmSourcesComponent } from './confirm-sources.component';

const mockDraftService = mock(DraftSourcesService);
const mockActivatedProject = mock(ActivatedProjectService);
const mockAuthService = mock(AuthService);

when(mockDraftService.getDraftProjectSources()).thenReturn(
of({
Expand Down Expand Up @@ -47,13 +55,24 @@ when(mockDraftService.getDraftProjectSources()).thenReturn(
} as DraftSourcesAsArrays)
);

when(mockActivatedProject.projectId).thenReturn('test-proj');
when(mockActivatedProject.projectDoc).thenReturn({
id: 'test-proj',
data: createTestProjectProfile({ userRoles: { user1: SFProjectRole.ParatextAdministrator } })
} as SFProjectProfileDoc);
when(mockAuthService.currentUserId).thenReturn('user1');

const meta: Meta = {
title: 'Translate/ConfirmSources',
component: ConfirmSourcesComponent,
decorators: [
moduleMetadata({
imports: [CommonModule, TranslocoModule],
providers: [{ provide: DraftSourcesService, useValue: instance(mockDraftService) }]
imports: [CommonModule, TranslocoModule, LanguageCodesConfirmationComponent],
providers: [
{ provide: DraftSourcesService, useValue: instance(mockDraftService) },
{ provide: ActivatedProjectService, useValue: instance(mockActivatedProject) },
{ provide: AuthService, useValue: instance(mockAuthService) }
]
})
]
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<ng-template matStepLabel>{{ t("overview") }}</ng-template>
<app-confirm-sources (languageCodesVerified)="languagesVerified = $event"></app-confirm-sources>
@if (nextClickedOnLanguageVerification && !languagesVerified) {
<app-notice type="error" mode="fill-dark">{{ t("confirm_codes_correct_to_continue") }}</app-notice>
<app-notice type="error">{{ t("confirm_codes_correct_to_continue") }}</app-notice>
}
<div class="button-strip">
<button mat-stroked-button class="backout-button" (click)="cancel.emit()">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { createTestProjectProfile } from 'realtime-server/lib/esm/scriptureforge
import { BehaviorSubject, of } from 'rxjs';
import { anything, instance, mock, verify, when } from 'ts-mockito';
import { ActivatedProjectService } from 'xforge-common/activated-project.service';
import { AuthService } from 'xforge-common/auth.service';
import { createTestFeatureFlag, FeatureFlagService } from 'xforge-common/feature-flags/feature-flag.service';
import { RealtimeQuery } from 'xforge-common/models/realtime-query';
import { NoticeService } from 'xforge-common/notice.service';
Expand Down Expand Up @@ -36,6 +37,7 @@ describe('DraftGenerationStepsComponent', () => {
const mockOnlineStatusService = mock(OnlineStatusService);
const mockNoticeService = mock(NoticeService);
const mockDraftSourceService = mock(DraftSourcesService);
const mockAuthService = mock(AuthService);

const mockTrainingDataQuery: RealtimeQuery<TrainingDataDoc> = mock(RealtimeQuery);
when(mockTrainingDataQuery.localChanges$).thenReturn(of());
Expand All @@ -56,7 +58,8 @@ describe('DraftGenerationStepsComponent', () => {
{ provide: ProgressService, useMock: mockProgressService },
{ provide: ActivatedRoute, useMock: mockActivatedRoute },
{ provide: OnlineStatusService, useMock: mockOnlineStatusService },
{ provide: NoticeService, useMock: mockNoticeService }
{ provide: NoticeService, useMock: mockNoticeService },
{ provide: AuthService, useMock: mockAuthService }
]
}));

Expand All @@ -75,6 +78,76 @@ describe('DraftGenerationStepsComponent', () => {
when(mockOnlineStatusService.isOnline).thenReturn(true);
}));

describe('training and drafting sources different', async () => {
const availableBooks = [{ bookNum: 1 }, { bookNum: 2 }, { bookNum: 3 }];
const config = {
trainingSources: [
{
projectRef: 'source1',
paratextId: 'PT_SP',
name: 'Source Project',
shortName: 'sP1',
writingSystem: { tag: 'eng' },
texts: availableBooks
},
{
projectRef: 'source2',
paratextId: 'PT_SP2',
name: 'Source Project 2',
shortName: 'sP2',
writingSystem: { tag: 'grc' },
texts: availableBooks
}
] as [DraftSource, DraftSource],
trainingTargets: [
{
projectRef: mockActivatedProjectService.projectId,
shortName: 'tT',
writingSystem: { tag: 'xyz' },
texts: availableBooks
}
] as [DraftSource],
draftingSources: [
{
projectRef: 'source2',
paratextId: 'PT_SP2',
shortName: 'sP2',
writingSystem: { tag: 'es' },
texts: availableBooks
}
] as [DraftSource]
};

beforeEach(fakeAsync(() => {
when(mockDraftSourceService.getDraftProjectSources()).thenReturn(of(config));
const mockTargetProjectDoc = {
id: 'project01',
data: createTestProjectProfile({
texts: availableBooks,
translateConfig: {
source: { projectRef: 'sourceProject', shortName: 'sP', writingSystem: { tag: 'xyz' } }
},
writingSystem: { tag: 'eng' }
})
} as SFProjectProfileDoc;
const targetProjectDoc$ = new BehaviorSubject<SFProjectProfileDoc>(mockTargetProjectDoc);

when(mockActivatedProjectService.projectDoc).thenReturn(mockTargetProjectDoc);
when(mockActivatedProjectService.projectDoc$).thenReturn(targetProjectDoc$);
when(mockTrainingDataService.queryTrainingDataAsync(anything(), anything())).thenResolve(
instance(mockTrainingDataQuery)
);
when(mockTrainingDataQuery.docs).thenReturn([]);
when(mockFeatureFlagService.allowFastTraining).thenReturn(createTestFeatureFlag(false));

fixture = TestBed.createComponent(DraftGenerationStepsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
tick();
fixture.detectChanges();
}));
});

describe('one training source', async () => {
const availableBooks = [{ bookNum: 1 }, { bookNum: 2 }, { bookNum: 3 }];
const allBooks = [...availableBooks, { bookNum: 6 }, { bookNum: 7 }];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,62 +85,6 @@ <h1 class="mat-headline-4">
<transloco key="draft_generation.non_pa_info_alert_source_text_not_selected"></transloco>
}
</app-notice>
} @else if (!isSourceAndTargetDifferent) {
<app-notice type="warning" icon="warning" data-test-id="warning-source-target-same">
@if (isProjectAdmin) {
<transloco
key="draft_generation.info_alert_same_source_and_target_language"
[params]="{ targetLanguageDisplayName, projectSettingsUrl: { route: projectSettingsUrl } }"
></transloco>
} @else {
<transloco
key="draft_generation.non_pa_info_alert_same_source_and_target_language"
[params]="{ targetLanguageDisplayName }"
></transloco>
}
</app-notice>
} @else if (!isSourceAndTrainingSourceLanguageIdentical) {
<app-notice type="warning" icon="warning" data-test-id="warning-source-training-different">
@if (isProjectAdmin) {
<transloco
key="draft_generation.info_alert_different_training_and_source_language"
[params]="{
alternateTrainingSourceLanguageDisplayName,
sourceLanguageDisplayName,
projectSettingsUrl: { route: projectSettingsUrl }
}"
></transloco>
} @else {
<transloco
key="draft_generation.non_pa_info_alert_different_training_and_source_language"
[params]="{
alternateTrainingSourceLanguageDisplayName,
sourceLanguageDisplayName
}"
></transloco>
}
</app-notice>
} @else if (!isSourceAndAdditionalTrainingSourceLanguageIdentical) {
<app-notice type="warning" icon="warning" data-test-id="warning-mix-source-different">
@if (isProjectAdmin) {
<transloco
key="draft_generation.info_alert_different_additional_training_and_source_language"
[params]="{
additionalTrainingSourceLanguageDisplayName,
alternateTrainingSourceLanguageDisplayName,
projectSettingsUrl: { route: projectSettingsUrl }
}"
></transloco>
} @else {
<transloco
key="draft_generation.non_pa_info_alert_different_additional_training_and_source_language"
[params]="{
additionalTrainingSourceLanguageDisplayName,
alternateTrainingSourceLanguageDisplayName
}"
></transloco>
}
</app-notice>
} @else if (!canAccessDraftSourceIfAvailable(source)) {
<app-notice type="warning" icon="warning" data-test-id="warning-source-no-access">
<transloco
Expand Down
Loading

0 comments on commit 386f574

Please sign in to comment.